Shaun Abram
Technology and Leadership Blog
Exceptions versus Return Values
In a previous post here, I blogged about checked exceptions and how I felt they are a useful tool in your Java toolbox. It is generally accepted that errors, or unrecoverable events, should be dealt with using unchecked exceptions and that recoverable problems, or contingencies, should be dealt with using checked exceptions.
However, there is another option for dealing with recoverable problems, i.e. using return values.
null/false/-1
At its most simple, using a return value to signal a problem would involve returning a null value instead of an actual object to signify that an object could not be returned (or retrieved, or created depending on the context) for some reason.
For example, this is the approach taken by the get(key) method of the Map class in the Collections API.
A slight variation on this when dealing with numeric return types, is to return -1 to signify a problem or some other condition. For example, this is the approach taken by the indexOf() method of the List class in the Collections API.
Another variation is having a boolean return type, with a return of true representing that the operation took place OK, and false representing that the operation could not be completed, for whatever reason. For example, this is the approach taken by the offer() method of the Queue class in the Collections API.
Integer return codes – no!
A more complicated (but not recommended!) approach (typically, only really an option if the return type is numeric), is to use specific values (e.g. negative) to represent a variety of problem. This approach however, is definitely not recommended as you are:
- severely limited by the existing return type of the method
- essentially hijacking the return type to use it for error handling
- getting into the realm of C/C++ error code checking, something Java exceptions were created to avoid
Special Result Class
Another better, but still fairly unorthodox, approach is to use a Special Result Class. A Special Result Class is a class whose sole purpose is to enable result and/or error data to be returned in a single return type.
For example, a getUser() method could have a GetUserResult as its return type, where GetUserResult has a boolean returning hasErrors() method that can be checked to see if all went well. If so, the calling code can call getResult() which returns the valid result (i.e. the return type you would normally expect the getUser method to return). If not, the calling code can call getErrors() and deal with the returned errors.
Advantages
The advantages of using a Special Result Class a a return type are:
1. They replace the need for checked exceptions
Avoiding checked exceptions, and their associated infrastructure overhead of try/catches, keeps your code much simpler and easier to use.
2. You are not forced to deal with errors.
You can ignore them if you simply don’t care about them, or if you are sure the call will succeed, avoiding redundant code such as
try {
…
} catch (Exception ex) {
//no action required…
}
3. Multiple errors = single return value
In the case of a large number of possible errors being generated, you still deal with a single return object, rather than having a large number of exceptions thrown and declared in the method signature
Disadvantages
The disadvantages of the Special Result Class approach are:
1. Explicit check required
The caller has to explicitly check the return value if they are concerned about errors happening (whereas Exceptions are ‘automatically’ thrown)
2. Checking for errors is not enforced by the compiler – it is up to the developer to do so, making it much more likely for errors to go undetected. In general, it is best to detect problems as early as possible.
3. Additional calls necessary
If the return value confirms errors occurred, additional calls are necessary to find the cause of the errors
As with the ‘specific numeric return values to represent errors’ approach above, we are getting back to the approach used for C/C++ error code checking, something Java exceptions were created to avoid
4. Any code that needs to deal with the errors has to have direct access to the special result object
With exceptions, the error/exception can be propagated up through the call stack to the code that is most suitable to deal with it (e.g. in a fault layer; a layer in an application dedicated to dealing with faults, as described here). A special result object however is more difficult to pass up through a call stack as it would mean changing the return type of all methods involved. Passing the special result object around to different methods as a parameter is also a little messy.
5. More unorthodox than exceptions
Exceptions are normal classes, can have their own methods, fields etc, which can contain info to describe the situation that caused the exception to be thrown. A Special Result Class may be setup with the same capabilities, but its use is likely to be less intuitive to most developers.
Either[Integer, Failure] result = getUserValue();
result.effect(new E[Integer] { public void e(Integer i) { whatever you want if getUserValue succeeded } },
new E[Failure] { public void e(Failure f) { and if it failed } });
I used []s instead of angle brackets to avoid problems with comment formatting.
The above can be prettier if you use a better language.
val result = getUserInput
result match { case Left(i) => println(“The user specified ” + i)
case Right(f) => println(“The user is an eejit because ” + f)
}
Good job!
Another problem on using exceptions is that on caller method you don’t know when an exception may be thrown. Using return values/class you know explicitly what may happen. 😉
One thing that frustrates me is when people throw exceptions for error checking and you only get one error back after say… filling out a form, and then you resubmit it to get another error back… and so on…
I mostly have a simple rule most of the time: Exceptions are for exceptional cases. That is, if you are likely to ignore an error condition and it shouldn’t be ignored, you should make sure to throw an exception rather than use a return value. This covers a great majority of error conditions, I think. Use a special class if you determine that specific identification is valuable, but any exceptions is better than none. Never use checked exceptions.
Hi Ricky,
Thanks for posting, but I’m not sure I follow it entirely. I think what you are proposing is a new, custom, parameterized type, called “Either” which has a single method called effect(). As well as a new class called Failure? Or am I being slow and it’s actually a standard class/library that I haven’t came across before?
Seems like an interesting idea. If you could point us to any complete code, or posting/docs on it, I’d like to understand it a little better…
And I assume by ‘a better language’ you mean Scala? 🙂
Thanks,
Shaun
Thanks Esteve…
I think I understand what you mean – that when using a return value or Special Result Class, you always get that result back, regardless. But you may well then need do do a series of if blocks for example,
if (result.hasErrors()) {…}
else {…}
which isn’t really too different from the catch blocks involved with the exceptions approach?
Shaun
Tom,
Thanks for commenting.
I agree that exceptions should only be used for exceptional conditions. The debate lies in what constitutes an exceptional condition. My own definition is a little looser than some other peoples.
Exceptions definitely shouldn’t be used or normal control flow, but I do think it is OK to use them for dealing with ‘contingency’ situations e.g. to signal a situation that stops a method from completing in its normal way, and that the method itself doesn’t know how to deal with – but that a calling method may know how to deal with and recover from. If that contingency is important enough that it shouldn’t be (accidentally) ignored, then checked exceptions are a good fit.
I think I did a better job of explaining this in a recent blog posting:
http://www.shaunabram.com/checked-exceptions-article/
Maybe it will change your mind 😉 If not, it would be great to hear your counter views…
Shaun