
Error handling and reporting was always a thing that I knew I wasn’t doing it right. Leaded by PHP’s on-the-fly type casting I used to make functions just return false or null but that was not right because I was hurting the return-type integrity of them.
Let’s assume we have a function that returns users IDs. What to expect for return type? Integers! Hell yeah! But what if the requested user ID is not found? Would you return false, null? Wrong!
In strict languages like C/C++ or Java, you can’t return a boolean in an integer function and so on. PHP gives the coder this “freedom” (that I interpret as “room for mistakes and laziness”).
You can go a little tidier when using type strict languages returning 0 or -1 (thus needing a signed int return type).
Exceptions are handled in a different way (will explain further) and you won’t need anymore to chunk your funky error codes/messages into your return codes.
NOTE: All the examples are in PHP and functions are merely illustrative.
Using PHP’s trigger_error
This function can help you to standardize your errors with PHP’s native errors like notices and fatal errors. I used this for long time attached to an error handler class. This worked out most for system errors and notices like “unable to open file” and such but they mostly were treated by issuing fatal errors and forcing user to a custom-built Error 500 – Internal Server Error page (in case of web code) or exiting to system with a custom error code.
Testing with if/else
As mentioned above, I started handling errors with false-ing and null’ing the return of functions and checking them for that. Example:
function find_id_by_name($name) { $query = "SELECT id FROM users WHERE name = '{$name}'"; $result = mysql_query($query); if (mysql_num_rows($result) == 0) return false; else return mysql_result($result, 0); } // end :: function :: find_id_by_name
And then checked when calling:
if ($id = find_id_by_name("John Doe")): # do stuff else: # print some message on screen endif;
NOTE: Returning zero (0) would trigger a false condition too.
Although this works pretty well it has several problems:
Obtrusion
If the checking code block prints messages on screen directly with print or echo it will be obtrusive for other functions calling it.
Multiple fail points
If a function can fail many ways, if you take the false/null approach you will know only that the function has failed but will have to insert debugging checkpoints on the function to know where it failed exactly.
Returning error codes may aid this point but cannot be used in integer functions because it would mix up with valid return entries and would still hurt our return type integrity.
Return Codes
As mentioned above, one could return integer error codes in order to identify the type of error that occurred. Although is a great leap in front of the null/false initiative.
define('ERROR_NO_USER',1); define('ERROR_INVALID_PASSWORD',2); function auth_user($username, $password) { $user = find_user($username); if (!$user) return ERROR_NO_USER; if ($user->password !== hash_function($password)) return ERROR_INVALID_PASSWORD return $user; } // end :: function :: auth_user
Note that this function returns an Object while it returns integers on failure.
To check if user has successfully authed, we would use something like:
if (is_numeric($user = auth_user("jdoe", "passw0rd"))) # hmmm... error occurred! else: $_SESSION['user'] = $user; endif;
But this still doesn’t seems right, and you still have to cluch a switch statement inside the error treating area to take actions depending on error code.
Returning error codes in functions with integer return type
In functions with integer return type you can’t return positive error codes because it would be confused with valid non-error entries unless you used negatively signed integers for that.
function find_id_by_name($name) { # ... some code to get user data ... # User entry exists? if (!$user) return -1; # User account is inactive? if ($user->isInactive()) return -2; return $user->id; } // end :: function :: find_id_by_name
Signaling failures in a return-safe manner
You can always use a identifier of the same return type to illustrate failures. This technique can only be used to signalize entire function failure and does not gives you the fine grained error control that try gives.
Integer return types
Procedural languages like C has done this forever. In integer returned functions you can return -1 to signalize function failure.
function find_id_by_name($name) { # ... some code if ($ids) return $id; else return -1; } // end :: function :: find_id_by_name
Returning empty lists
When dealing with function that returns lists (lists/arrays, dictionaries and such) you can also use an empty list in return signaling that no entries are found. This persists the function return-type and will flow naturally if parent function uses the returned value into a foreach or for statement.
function find_users_by_country($country_id) { # ... some code if ($users) return $users; else return array(); } // end :: function :: find_users_by_country
Using Try/Catch and Exceptions
One of the greatest practices I learned in my life as a developer was to introduce the power of try/catch into my code. This long-known practice by Java users was introduced in PHP version 5 and I think it’s present in almost every OOPw implementation like Java, PHP5, C++, Python and even in Flash AS3 (since its inherited from Java structure).
It gives you the ability to raise real-time errors during execution without the need to interfere within your returns. It helps you keeping your functions returning only data its expecting.
Try/Catch overview
The try block surrounds a set of statements that must “not throw an exception” to proceed. If any of the inner statements throws any exception, function execution proceeds but its routed to the catch block corresponding to the kind of exception that was raised.
But hell, what are exceptions?
As corrected by SeanJA from SquirrelHacker Exceptions are the Object-oriented form special errors that aren’t expected in the flow.
Exception are good to distinguish errors from each other from their class type. Exceptions can also have custom properties and methods for each type. You just have to define them in your exception class. They also extends a main Exception class with methods generally available to all classes such as the getMessage method in PHP.
In most implementation, exception classes often receives a custom message as first parameter that would be read later on by the corresponding getMessage method.
Example:
try { do_something(); do_other_thing(); } catch (Exception $e) { print $e->getMessage(); } // end :: try
Throwing exceptions (errors)
Inside your function you can use the throw statement to generate exceptions that will be caught in the catch block of the caller function. Lets rewrite our function used in the early example with exceptions.
function find_id_by_name($name) { # ... some code to get user data ... # Connection is up? if (!$this->conn) throw new Exception("Connection was dropped."); return $user->id; } // end :: function :: find_id_by_name
Custom-built exceptions
Working with generic exceptions is already very useful but it stills give you few control about the type of error generated. Generic exceptions are good if you will only catch and forward the error messages or if your error handling is independent of the error type.
You can easily generate custom exceptions by extending the main Exception class (varies according to the language). In PHP, this is the Exception class.
class FileLockedException extends Exception { } // end :: class :: FileLockedException
That’s it, if you don’t need no additional methods or proprieties, you’re done! You may successful use your new exceptions while treating errors with catch. If you need it, just treat your Exception class as any normal OOP class.
class FileLockedException extends Exception { public $timestamp; } // end :: class :: FileLockedException some_function() { $ex = new FileLockedException(); $ex->timestamp = date("Y-m-d H:i:s"); throw $ex; } // end :: function :: some_function
Catching exceptions
As seen on the example on try/catch overview, catching just requires a catch statement for the corresponding Exception class. All exceptions that doesn’t have an catch statement for its own class type will fall back to the main exception class.
try { do_something(); do_other_thing(); } catch (FileLockedException $e) { print "Cannot open file for writing, locked!"; } catch (Exception $e) { print $e->getMessage(); } // end :: try
Rethrow, bubbling up exceptions
Exceptions can be bubbled up by rethrowing with a throw statement under a catch block. This is used to delegate the control of parsing the error to a parent function and to force triggering the catch blocks on them.
function do_something() { try { do_stuff(); } catch (Exception $e) { throw $e; } // end :: try } // end :: function :: do_something function do_stuff() { if ($somethingWentWrong) throw new Exception("Something went wrong!"); } // end :: function :: do_stuff
Overhead & Overkill or When to use exceptions?
SeanJA wrote an excellent article about that clarifing some points (thanks SeanJA!).
To differ errors from exceptions, you need to point out if what you are returning is expected or not. You can achieve this by focusing on the purpose of your function.
To keep up with previous examples, lets assume our login function. Which is its purpose? The function is asked “This user credentials are valid?” and it expects “Yes” or “No”.
Credentials are a tuple of username and password so if one of these are wrong, your function should naturally return false because one of them are wrong, but that is expected.
Unexpected data is something that is beyond your control, like a locked file, a database timeout and such. Check out SeanJA if you need more clarification on this subject.
Once I got in love with Exceptions I can’t live without it. It gets you even more in depth within OOP, and I’m satisfied with it.
Treat your errors well and they will not betray you late in the nite!
The only problem is that you are now wrapping everything you do in try{} catch(SomeExceptionType $e){} so you can catch the errors… You should throw exceptions only in exceptional cases. For example: they gave you ‘asdf’ instead of an integer when they called find_user_by_id(). See: http://stackoverflow.com/questions/77127/when-to-throw-an-exception
SeanJA
April 20, 2010 at 11:54 pm
Thanks Sean, very good clarification. Updated the post!
Jan Seidl
April 21, 2010 at 10:54 am
[...] I saw this post on Better Error Handling, while there is nothing horribly horribly wrong with it I couldn’t help thinking that he has [...]
Exceptions Are Not For Flow Control · Squirrel Hacker
April 21, 2010 at 9:01 am