Coding Hell

Programming and stuff.

How and When (Not) to Use Exceptions

Did you ever ask yourself, how you should “design” your exceptions? Ever wondered if you understood the idea behind exceptions? Do you know, what you should use exceptions for? This article aims to clarify how and when (not) to use exceptions.

I will use some Ruby for the example code, but the principles are the same in all other programming languages (at least the ones I know).

Exception as in exceptional

Exceptions should be used for situation where a certain method or function could not execute normally. For example, when it encounters broken input or when a resource (e.g. a file) is unavailable. Use exceptions to signal the caller that you faced an error which you are unwilling or unable to handle. The exception is then passed to the caller who has the chance to either handle the exception or pass it on (chain-of-responsibility).

How to use exceptions
1
2
3
4
5
6
7
8
def update(identifier)
  unless @data.has_key?(identifier)
    raise UnknownIdentifierError, "Identifier '#{identifier}' is not registered"
  end
  # ...
  raise TimeoutError, "Timeout while connecting to server '#{server}'."
  # ...
end

Don’t use exceptions for your normal application flow. Take a look at the following example:

How NOT to use exceptions
1
2
3
4
5
6
7
8
9
def handle_client_input(input_stream)
  input_stream.each_line do |line|
    if line =~ /^hello$/i
      # ...
    elsif line =~ /^quit$/i
      raise ClientQuitEvent, "Client is about to disconnect."
    end
  end
end

In this example we use an exception to signal the caller that the client disconnected by sending a quit command. Something that will happen in every normal use case of this method. There are multiple reasons why you should not do this:

  1. Exceptions are not designed for this. Developers using this API or reading through the source code will be confused. It looks like a failure scenario but it’s just some sort of flow control.

  2. Exceptions are hard to follow. If you are using exceptions like this, you are using just another form of a goto statement. I don’t have to clarify why using goto is a bad idea, do I?

  3. Exceptional exceptions? If you use exceptions for normal situations, how do you signal unusual situations?

  4. Exceptions are slow. Because exception only rarely occur, performance is not a priority for the implementers of compilers nor the designers of the language. Throwing and catching exceptions is inefficient and many times slower than a simple check of a return value or a state field.

Don’t use exceptions to signal something completely normal. Don’t use exceptions to control your normal application flow. Use return values or state fields for flow control instead.

It’s all about the hierarchy

Another question that pops up frequently: What structure (hierarchy, inheritance, …) should I use for my own exceptions?

Exceptions should be as specific as possible. Especially if you are designing an API that will be used by third-party developers.

Your own exceptions should all inherit from a common exception in your namespace. And this exception class in turn should inherit from the standard library exception (e.g. StandardError in Ruby, Exception in Java).

How to structure your exceptions
1
2
3
4
5
6
7
module MyAwesomeModule
  class Error < StandardError; end
  class ClientError < Error; end
  class TimeoutError < ClientError; end
  class ParsingError < ClientError; end
  # ...
end

This allows the caller to decide which exceptions he would like to handle. If you don’t follow this advice, you might end up with an error design as broken as the one in Ruby’s Net::HTTP. Because there is no common error class, a caller willing to catch all module specific exceptions must list all possible exceptions that may occur:

Net::HTTP’s broken error design
1
2
3
4
5
begin
  response = Net::HTTP.post_form(...)
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
  # ...
end

Be verbose

When throwing an exception, you should supply an error message describing what exactly went wrong. This is especially useful for logging and debugging.

1
raise MyAwesomeModule::ParsingError, "Character #{character} at position #{position} is not allowed"

The error messages are only intended for humans. They should not be used by the calling method to determine what probably went wrong.

tl;dr

Exceptions should be used to signal serious errors from which your method is unable or unwilling to recover. In a best case scenario, no exceptions should occur during the whole application flow.

Be sure to structure your exceptions and use a hierarchy to simplify exception handling for the caller.

Use the optional error message to supply additional debug information.

Comments