I’ve put a lot of thought into best practices for exception handling. The guidelines described here are intended to maximize readability and maintainability of the code, as well as exposing failure information without looking up source code. In professional and personal projects, the experience with this method has been a very positive one.

Summary

  1. Centralize error message construction to ensure consistency
  2. Catch generic errors and throw specific errors in their place
  3. Include as much information as possible about the error in the message itself
  4. Exceptions are a tool to help solve problems, they are not problems themselves
  5. Always throw an exception the moment the application state becomes invalid, rather than waiting for it to become a problem

Error Messages

It’s often difficult to maintain a set of clear and consistent error messages. Flash developers are intimately familiar with the frustration of poor wording or vagueness. It’s difficult to prevent this from happening once the code base exceeds a certain size. A Factory can be employed to centralize the composition of error messages and make it easier to maintain them. Compare these two implementations:

public function getCell(in_x:int, in_y:int):MapCell
{
	if(!_isInitialized)
	{
		throw(new MapError("Map not initialized prior to referencing a cell", MapError.NOT_INITIALIZED));
	}
	if(outOfBounds(in_x, in_y))
	{
		throw(new MapError("Requested cell at ("+in_x+","+in_y+") is out of map bounds", MapError.OUT_OF_RANGE));
	}
	return MapCell(_map.cells.get(in_x, in_y));
}
public function getCell(in_x:int, in_y:int):MapCell
{
	if(!_isInitialized)
	{
		throw(MapErrorFactory.fromNotInitialized());
	}
	if(outOfBounds(in_x, in_y))
	{
		throw(MapErrorFactory.fromOutOfBounds(in_x, in_y));
	}
	return MapCell(_map.cells.get(in_x, in_y));
}

The code becomes much more readable, and the errors have consistent information every time they are thrown. It is also apparent whether a new error message should be created or an existing message reused, based on the exception name and the parameters provided to it. Here are the MapErrorFactory and MapError classes that accompany this example.

package com.andrewtraviss.illfate.errors
{
	public class MapErrorFactory
	{
		public static function fromNotInitialized():MapError
		{
			return new MapError("Mapper has not been initialized", MapError.NOT_INITIALIZED);
		}
		public static function fromOutOfBounds(in_x:int, in_y:int):MapError
		{
			return new MapError("Requested cell at ("+in_x+","+in_y+") is out of map bounds", MapError.OUT_OF_RANGE)
		}
	}
}

package com.andrewtraviss.illfate.errors
{
	public class MapError extends Error
	{
		public function MapError(in_message:String, id:int=0)
		{
			super("MapError #"+String(id)+": "+in_message, id);
		}
		public static const NOT_INITIALIZED:int = 100;
		public static const OUT_OF_RANGE:int = 101;
	}
}

Note that the x and y coordinates of the cell requested are included in the error message, rather than the message simply being “Requested cell was out of bounds”. Providing specific information about the failure makes diagnosis much easier. Implementing a distinct error message for every potential failure scenario involving the same data puts a lot of responsibility on the originating code to understand and explain the nature of each failure. It’s better to just provide the information to the developer to identify the problem. A class also cannot always judge what the meaning of a failure is just based on its own data.

Generic Exceptions

The Flash Player generates quite a number of exception on its own. The vast majority of these exceptions are useless for figuring out anything more than the fact that your application is broken. This is also true of generic messages that developers include in data structures, messaging, and other workhorse classes. If the specific impact of a generic exception can be understood by the calling method or class, the exception should be caught, and a new error thrown that describes the specific natuer of the failure, rather than allowing the generic error to be displayed.

private function fooBar():void
{
	// throws a null object reference error
	foo.bar();
}
private function fooBar():void
{
	if(!foo)
	{
		throw(FooErrorFactory.fromFooNotSet());
	}
	foo.bar();
}

When to Let Everything Crash Down

Do yourself a favour, if an exception unexpectedly takes down your whole application, let it happen. It’s far better to track down the root cause and prevent the exception from occurring in the first place. It’s very tempting to just steamroller over an exception that doesn’t seem to cause any problems just to get the product out the door, but it a dangerous call to make. I have seen the following more times than I care to count.

private function fooBar():void
{
	foo = getFoo();
	if(foo != null)
	{
		updateCriticalApplicationConfiguration(foo);
	}
}

Exceptions are only a symptom. Don’t treat the symptom, treat the illness! If you’re lucky, several function calls later, some other function is going to throw a null object reference error because this variable was not set properly. If you’re unlucky, there will be “unexpected behaviour” in a loosely related scenario somewhere far down the line. If at all possible, ensure that an exception is thrown immediately when the application becomes invalid; it saves you time versus having to track down the real point of failure from wherever it triggered an even worse failure.

This is most often a problem in the more generic parts of an application. Generic code cannot make a judgement about specific failures and whether or not they are safe. Obviously, there is the occasional case where an exception really is safe to ignore, but generic code should rarely be making that call.