Exceptional Error Handling

When it comes to error handling, most developers either skim over it or forget about it all together. Error handling is a difficult thing to get right. In this article I’m going to show an effective way to add error handling to your business software by making use of exceptions.

Why exceptions?

Exceptions are very powerful. There is a lot of debate out there on how to use exceptions. One group of developers claims that exceptions should only be used in “exceptional” circumstances, however, it’s always unclear what an “exceptional” circumstance is. It’s a very binary way of looking at things and I think it’s important to understand how various types of software use exceptions in different ways and in different circumstances.

What we’re focusing on here is business logic software, i.e. code that represents business rules and higher level processes, as in, we’re not writing operating systems here and we’re not dealing with hardware failures, in this article we’re writing application level software.

The way I like to think about exceptions is:

An exception occurs when a program deviates from its intended code path.

In practise this means that a method or function is supposed to perform a single responsibility (SOLID) and anything that prevents this method or function from performing this responsibility would be an “exception” to this responsibility.

E.g. to create a user in my system I might have a UserManager class and this class might have a createUser method which returns a user object. To create a new user I have to pass in a username and a password as parameters:

namespace Acme\Example\User;
 
class UserManager {
 
  public function createUser( $username, $password )
  {
    $user = new User();
    $user->setUsername( $username );
    $user->setPassword( $password );
 
    return $user;
  }
}

As you can see, the createUser method has the single responsibility of creating a user and returning this new object. However, there are some constraints to creating a user. Let’s assume that the user must have a unique user name and that the length of the password must be at least 8 characters long.

When our constraints are met, our normal program flow, or code path, should always end at the return $user, except when any of our constraints fail, in which case we’ll throw an exception:

namespace Acme\Example\User;
 
class UserManager {
 
  public function createUser( $username, $password )
  {
    $user = $this->userRepository->getUserByUsername( $username );
 
    if( ! empty( $user ) )
    {
      throw new Exception("A user with username {$username} already exists.");
    }
 
    if( strlen( $password ) < 8 )
    {
      throw new Exception("The password must be at least 8 characters long.");
    }
 
    $user = new User();
    $user->setUsername( $username );
    $user->setPassword( $password );
 
    return $user;
  }
}

In the above code sample we introduced a reference to a userRepository, this is simply an object that talks to the database, no need to worry about the internals for the purpose of this article, it simply has a method that returns a user object by its given username, if it exists. I.e. the UserRepository accesses the database whereas our UserManager performs all logic surrounding users.

As the code above demonstrates, in the event that a user with the specified username already exists or when the password is less then 8 characters, an exception is thrown. It would have been possible to return an error code instead, however there are a few problems when returning error codes:

  1. The burden of determining the error is on each and every caller of the method or function.
  2. The method or function will return different types of values.

We like to keep things DRY in which case the first issue is a problem. If we were to return an error code instead of throwing an exception, the burden of determining the error would be every caller’s responsibility. In the example of creating a user it’s probably only called once somewhere in the business logic but there might be other examples of which this is not the case. In those scenarios you will have to implement your error handling at various different places. Also, when using error codes you will have the burden of the administration of those error codes which, in a large code base, can be quite cumbersome, time consuming and error prone, not to mention, it’s also up to the caller to determine the error message.

Just because PHP has the ability to return different types for a single method or function, it is not a good idea to actually do this. When returning error codes instead of throwing exceptions, we would be e.g. returning an integer in case of an error and a User object in case of success. The method or function should only be doing one thing, in our case that is returning a User object. By returning an error code the method would be hiding it’s intent. The method name is createUser which implies that the method creates, and therefore returns, a User object and it’s exactly what it should do, and otherwise, it shouldn’t do it at all.

Another side effect of returning error codes is that it’s easy to simply ignore them. By throwing an exception it will be caught at some point, regardless, and that’s the entire point of errors in the first place, to handle them, not to ignore them.

Naming exceptions

The true power of exceptions becomes apparent when we start naming our exceptions appropriately. By simply throwing an Exception we’re missing out of a lot of the benefits exceptions can really give us. The most important benefit of naming exceptions is that a well named exception can instantly tell us what the context of the problem is.

Let’s take another look at our createUser method but this time we use appropriately named exceptions:

namespace Acme\Example\User\Exception;
 
abstract class UserManagerException extends \RuntimeException {
}
 
class DuplicateUserException extends UserManagerException {
 
  public function __construct( $username, \Exception $previous = NULL )
  {
    parent::__construct("A user with username {$username} already exists.", 0, $previous);
  }
 
}
 
class PasswordLengthException extends UserManagerException {
 
  public function __construct( $length, \Exception $previous = NULL )
  {
    parent::__construct("The password must be at least 8 characters long, {$length} given.", 0, $previous);
  }
 
}
namespace Acme\Example\User;
 
class UserManager {
 
  public function createUser( $username, $password )
  {
    $user = $this->repository->getUserByUsername( $username );
 
    if( ! empty( $user ) )
    {
      throw new Exception\DuplicateUserException( $username );
    }
 
    $passwordLength = strlen( $password );
 
    if( $passwordLength < 8 )
    {
      throw new Exception\PasswordLengthException( $passwordLength );
    }
 
    $user = new User();
    $user->setUsername( $username );
    $user->setPassword( $password );
 
    return $user;
  }
}

The example above demonstrates a few important principles. The first is the hierarchy of the exceptions. As the example demonstrates, we have declared an abstract UserManagerException from which our other exceptions inherit. The reason we do this is because so the caller has the opportunity to only implement a single catch handler that catches all UserManagerException‘s:

try
{
  $user = $userManager->createUser( $username, $password );
}
catch( \Acme\Example\User\Exception\UserManagerException $e )
{
  // Handle exception.
}

The above will catch all UserManagerException‘s, however, we have to opportunity to implement a specific handler if we wanted to:

try
{
  $user = $userManager->createUser( $username, $password );
}
catch( \Acme\Example\User\Exception\DuplicateUserException $e )
{
  // Handle exception.
}
catch( \Acme\Example\User\Exception\UserManagerException $e )
{
  // Handle exception.
}

This is an important principle because it gives our caller’s the opportunity to do fine grained error handling if it’s required.

The reason the UserManagerException is abstract is because we never want to throw this kind of exception because it’s simply not descriptive enough, but, we do want to catch this type of exception as we demonstrated above.

You might ask, why are you not simply catching Exception? The reason is that we only want to catch Exception at the highest level of our application. E.g. in our createUser we call the getUserByUsername method on the userRepository to access the database. Within that method an exception could be thrown, maybe the database connection was lost, who knows. However, if that’s the case, it’s not really the responsibility of the createUser method or it’s callers to cater for this particular problem. However, if an exception is thrown that would make sense to be handled at that level, it would make sense to handle it at that level, otherwise, let it bubble up.

The second principle our example demonstrates is that the error message is contained within the exception class. If for some reason we would throw the same exceptions somewhere else within our UserManager (e.g. there might be an updateUser method) the error message stays consistent. I.e. by just throwing a Exception with an in-line error message, it introduces a fragility to our error messages which we don’t want. By encapsulating the message in the exception class we have kept our error messages DRY

There is a lot more to say about exceptions but I hope this article has made you think about exceptions differently.

This entry was posted in Programming and tagged . Bookmark the permalink. Both comments and trackbacks are currently closed.