The Java APIs have a plethora of exception classes, many of which you will find fit your own purposes (particularly some of those in java.lang). Prior to the introduction of chained exceptions in 1.4, it wasn’t uncommon for Java programmers to implement their own exception classes that supported chaining and derive all context-specific exceptions from those. There are still uses for custom exception trees. This tip will show you one of them.
In the current game project I am working on (which I recently moved to Java from C) one of my first tasks was to create a resuable framework which can be used to get games started with little effort. This framework handles initialization and management of all core systems that support a game. It’s not a game engine, per se, but a harness to which a specific game engine could be attached. One of the core components of the framework is a top-level exception handler which wraps the initialization and game loop code (cleanup happens in a finally block). The purpose is to catch any and all Throwables, including RuntimeExceptions, that propagate up the stack in order to let the player know that his computer isn’t exploding. An exception stack trace is not very user-friendly, after all.
To facilitate the process I implemented two base exception classes - UncheckedException, which derives from RuntimeException, and CheckedException, which derives from Exception. Both classes add an additional message member - userMessage. Anytime I construct a new class from my exception tree, I have the option of passing a user-friendly message to the constructor in addition to the normal message, which I see as being more of a diagnostic for the developer. When the exception is caught by the top-level handler, the user message is extracted and displayed in a message box for the player to see while the stack trace and diagnostic messages are logged. If there is no user message, or the exception is not from my custom tree but from Java instead, then a default user message is displayed.
My CheckedException class is also abstract with a single protected constructor. This comes from my philosophy on exceptions, which not everyone will agree with. I believe exceptions are for exceptional circumstances only. They are not meant to be used in place of return codes, or boolean success values. Thus, most of the time, when I throw an exception it is of the UncheckedType, meaning the program should most certainly exit. However, there are times when an exception can be overcome and the app can continue. In cases where that is possible, I throw CheckedExceptions instead. But throwing just CheckedExceptions all of the time says nothing about the context in which it is thrown. By making CheckedException an abstract class I am always reminded to create context-specific exceptions. UncheckedException is not abstract because when I throw those it is as an indicator to exit the program. The diagnostic messages give the context. I still create context-specific subclasses for special cases.
Here’s some code:
public abstract class CheckedException extends Exception {
private String userMessage;
protected CheckedException(String message, String userMessage, Throwable cause) {
super(message, cause);
}
public final String getUserMessage() {
return userMessage;
}
}
public class UncheckedException extends RuntimeException {
private String userMessage;
public UncheckedException(String message) {
this(message, (String)null, (Throwable)null);
}
public UncheckedException(String message, Throwable cause) {
this(message, (String)null, cause);
}
public UncheckedException(String message, String userMessage, Throwable cause) {
super(message, cause);
}
public final String getUserMessage() {
return userMessage;
}
}
CheckedException only has the one constructor because it cannot be instantiated directly, so subclasses must call super with the appropriate args. It that case, it doesn’t make sense to have three different constructors just for convenience. UncheckedException has three constructors because it can be instantiated directly. There is no default constructor because I want to always have, at a minimum, a diagnostic message. Nearly all exceptions thrown by my framwork and game code derive from these two classes. The only cases where this is not true are for some illegal argument or illegal state exceptions I throw. Those are usually thrown during development and testing, but if they are thrown while a player is playing my game then the default user message describes the situation fine.
There are several different philosophies on when and how to make use of exceptions. Whichever you follow, you may find custom exception trees useful. The two classes I presented here only add one little feature to the default exception classes - user messages. You may find a more complex use for your own.
Technorati Tags: Java, game programming, game development, exceptions, Java programming
Post a Comment