RuntimeExceptions and Gurus failing to meditate
October 18th, 2008 by SamI have strong opinions when it comes to checked vs unchecked exceptions, as do many other people all over the interwebz. For me, a checked exception should be thrown when the caller can reasonably recover, otherwise if you can recover yourself (or the problem is not that significant) you should just log it and get on with life (e.g. if Closeable.close failed, it’s really not the end of the world… just log it). If it is a serious problem that cannot be reasonably programmatically recovered, then go for a runtime exception.
In this post, I’m going to discuss the RuntimeExceptions that I use and introduce a new exception, the GuruMeditationFailure.
There are lots of different kinds of problems that cannot be programmatically recovered. I find that the following cases are the most common:-
NullPointerException- I usually start all public methods with a call to Google Collections’ convenience methodPreconditions.checkNotNullto make sure that none of my parameters arenull. If a parameter is allowed to be null, then that parameter is annotated as@Nullable.IllegalArgumentException- If a parameter has a specific requirement, my next step aftercheckNotNullis to callPreconditions.checkArgumentwith the condition, e.g.checkArgument(a > 0)to ensure thatais positive.UnsupportedOperationException- I use this in two ways; for methods that I am not going to implement, and during development to indicate that I have not yet implemented the code. For the latter, I tend to throw it withthrow new UnsupportedOperationException("not implemented yet"). In the past, I’ve used aNotImplementedYetException, but the documentation ofUnsupportedOperationExceptionactually makes it sensible to use it for these cases and it would be overkill to create a new runtime class just for extra development support.ExceptionInInitializerError- as previously discussed by greefi, you cannot be guaranteed that your JVM will catch exceptions thrown from static initialisers. To be covered, always wrap static initialisers in a try-catch, then throwExceptionInInitializerErrorfr the catch.AssertionError- I never use this directly, but by calling the variousAssert.*methods from Junit or using theassertkeyword, this is what will be thrown on failure.IllegalStateException- For cases where a class needs to be used in a specific way, such as calling config methods before calling the calculate method or if somebody attempts to access a class after it has been explicitly closed. Google Collections’Preconditions.checkStatealso makes this trivial to check.
There is one scenario that is not covered by any of the above, which is when some code should never have been reached… but it did! In those scenarios it is clearly the programmers logic that is broken, and none of the Javadocs for the above exceptions cover those cases. That’s why I came up with the GuruMeditationFailure exception, as a tip of the hat to the Commodore Amiga error message of similar name.
The term “Guru Meditation Error” was an in-house joke from Amiga’s early days. One of the company’s products was the joyboard, a game controller much like a joystick but operated by one’s feet. Early in the development of the Amiga computer operating system, the company’s developers became so frustrated with the system’s frequent crashes that, as a relaxation technique, a game was developed where a person would sit cross-legged on the joyboard, resembling an Indian guru. The player was supposed to remain perfectly still with the goal of the game being to stay still the longest. If the player moved, a “guru meditation error” resulted.
As an example of its use, say you want to write a method that turns a String into an InputStream. A common approach to this problem is to create a ByteArrayInputStream from the raw bytes of the String. But String.getBytes throws (the sadly checked exception) UnsupportedEncodingException. But if we’re using the UTF-8 encoding, it should be guaranteed to always exist! That’s why it might be best to wrap it in a try-catch and throw GuruMeditationFailure, but letting the client remain ignorant about the details:-
/**
* Convert a String to an InputStream, for help with some APIs that only work with streams.
*
* @param string
* @return
*/
public static InputStream stringToStream(String string) {
Preconditions.checkNotNull(string);
try {
return new ByteArrayInputStream(string.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
// should never happen
throw new GuruMeditationFailure(ex);
}
}
In this case, I’m fairly confident that the exception will never be thrown… but I’m sure you can imagine situations where your logic might be more open to interpretation. You certainly don’t want to swallow the exceptions in these cases, because it’ll be an absolute nightmare to debug if you were wrong!
The source code for GuruMeditationFailure is trivial:-
/*
* Created 27-Sep-2008
*
* Copyright ThinkTank Maths Limited 2008
*
* This file is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this file. If not, see <http://www.gnu.org/licenses/>.
*/
package com.thinktankmaths.utils;
/**
* Indicates a failure in logic in the application. The name of this exception
* is a tip of the hat to Amiga OS messages of a similar nature.
*
* @author Samuel Halliday
* @see <a href="http://en.wikipedia.org/Guru_Meditation">Guru Meditation</a>
* @see <a href="http://javablog.co.uk/2008/10/18/runtimeexceptions-and-gurus-failing-to-meditate/">Javablog article</a>
*/
public class GuruMeditationFailure extends RuntimeException {
/** serial version 1 */
public static final long serialVersionUID = 1L;
/** */
public GuruMeditationFailure() {
super();
}
/**
* @param e
*/
public GuruMeditationFailure(Throwable e) {
super(e);
}
}
Omry wrote:
October 20th, 2008 at 6:00 pm