Others have summarized quite well why to throw early. Let me concentrate on the why to catch late part instead, for which I haven't seen a satisfying explanation for my taste.
SO WHY EXCEPTIONS?
There seems to be quite a confusion around why exceptions exist in the first place. Let me share the big secret here: the reason for exceptions, and exception handling is... ABSTRACTION.
Have you seen code like this:
static int divide(int dividend, int divisor) throws DivideByZeroException {
if (divisor == 0)
throw new DivideByZeroException(); // that's a checked exception indeed
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
try {
int res = divide(a, b);
System.out.println(res);
} catch (DivideByZeroException e) {
// checked exception... I'm forced to handle it!
System.out.println("Nah, can't divide by zero. Try again.");
}
}
That's not how exceptions should be used. Code like the above do exist in real life, but they are more of an aberration, and are really the exception (pun). The definition of division for example, even in pure mathematics, is conditional: it is always the "caller code" who has to handle the exceptional case of zero to restrict the input domain. It's ugly. It's always pain for the caller. Still, for such situations the check-then-do pattern is the natural way to go:
static int divide(int dividend, int divisor) {
// throws unchecked ArithmeticException for 0 divisor
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
if (b != 0) {
int res = divide(a, b);
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Alternatively, you can go full commando on OOP style like this:
static class Division {
final int dividend;
final int divisor;
private Division(int dividend, int divisor) {
this.dividend = dividend;
this.divisor = divisor;
}
public boolean check() {
return divisor != 0;
}
public int eval() {
return dividend / divisor;
}
public static Division with(int dividend, int divisor) {
return new Division(dividend, divisor);
}
}
static void doDivide() {
int a = readInt();
int b = readInt();
Division d = Division.with(a, b);
if (d.check()) {
int res = d.eval();
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
As you see, the caller code has the burden of the pre-check, but does not do any exception handling after. If an ArithmeticException
ever comes from calling divide
or eval
, then it is YOU who has to do the exception handling and fix your code, because you forgot the check()
. For the similar reasons catching a NullPointerException
is almost always the wrong thing to do.
Now there are some folks who say they want to see the exceptional cases in the method / function signature, i.e. to explicitly extend the output domain. They are the ones who favor checked exceptions. Of course, changing the output domain should force any direct caller code to adapt, and that would indeed be achieved with checked exceptions. But you don't need exceptions for that! That's why you have Nullable<T>
generic classes, case classes, algebraic data types, and union types. Some OO people might even prefer returning null
for simple error cases like this:
static Integer divide(int dividend, int divisor) {
if (divisor == 0) return null;
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
Integer res = divide(a, b);
if (res != null) {
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Technically exceptions can be used for the purpose like the above, but here is the point: exceptions do not exist for such usage. Exceptions are pro abstraction. Exception are about indirection. Exceptions allow for extending the "outcome" domain without breaking direct client contracts, and deferring error handling to "somewhere else". If your code throws exceptions which are handled in direct callers of the same code, without any layers of abstraction in between, then you are doing it W. R. O. N. G.
HOW TO CATCH LATE?
So here we are. I have argued my way through to show that using exceptions in the above scenarios is not how exceptions are meant to be used. There exists a genuine use case though, where the abstraction and indirection offered by exception handling is indispensable. Understanding such usage will help understanding the catch late recommendation too.
That use case is: Programming Against Resource Abstractions...
Yeah, business logic should be programmed against abstractions, not concrete implementations. Top level IOC "wiring" code will instantiate the concrete implementations of the resource abstractions, and pass them down to the business logic. Nothing new here. But the concrete implementations of those resource abstractions can potentially be throwing their own implementation specific exceptions, can't they?
So who can handle those implementation specific exceptions? Is it possible to handle any resource specific exceptions at all in the business logic then? Nope, it is not. The business logic is programmed against abstractions, which does exclude the knowledge of those implementation specific exception details.
"Aha!", you might say: "but that's why we can subclass exceptions and create exception hierarchies" (check out Mr. Spring!). Let me tell you, that's a fallacy. Firstly, every reasonable book on OOP says concrete inheritance is bad, yet somehow this core component of JVM, exception handling, is closely tied with concrete inheritance. Ironically, Joshua Bloch could not have written his Effective Java book before he could get the experience with a working JVM, could he? It is more of a "lessons learnt" book for the next generation. Secondly, and more importantly, if you catch a high-level exception then how are you going to HANDLE it? PatientNeedsImmediateAttentionException
: do we have to give her a pill or amputate her legs!? How about a switch statement over all the possible subclasses? There goes your polymorphism, there goes the abstraction. You got the point.
So who can handle the resource specific exceptions? It must be the one who knows the concretions! The one who instantiated the resource! The "wiring" code of course! Check this out:
Business logic coded against abstractions... NO CONCRETE RESOURCE ERROR HANDLING!
static interface InputResource {
String fetchData();
}
static interface OutputResource {
void writeData(String data);
}
static void doMyBusiness(InputResource in, OutputResource out, int times) {
for (int i = 0; i < times; i++) {
System.out.println("fetching data");
String data = in.fetchData();
System.out.println("outputting data");
out.writeData(data);
}
}
Meanwhile somewhere else the concrete implementations...
static class ConstantInputResource implements InputResource {
@Override
public String fetchData() {
return "Hello World!";
}
}
static class FailingInputResourceException extends RuntimeException {
public FailingInputResourceException(String message) {
super(message);
}
}
static class FailingInputResource implements InputResource {
@Override
public String fetchData() {
throw new FailingInputResourceException("I am a complete failure!");
}
}
static class StandardOutputResource implements OutputResource {
@Override
public void writeData(String data) {
System.out.println("DATA: " + data);
}
}
And finally the wiring code... Who handles concrete resource exceptions? The one who knows about them!
static void start() {
InputResource in1 = new FailingInputResource();
InputResource in2 = new ConstantInputResource();
OutputResource out = new StandardOutputResource();
try {
ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
}
catch (FailingInputResourceException e)
{
System.out.println(e.getMessage());
System.out.println("retrying...");
ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
}
}
Now bear with with me. The above code is simplistic. You might say you have an enterprise application / web container with multiple scopes of IOC container managed resources, and you need automatic retries and reinitialization of session or request scope resources, etc. The wiring logic on the lower level scopes might be given abstract factories to create resources, therefore not being aware of the exact implementations. Only higher level scopes would really know what exceptions those lower level resources can throw. Now hold on!
Unfortunately, exceptions only allow indirection over the call stack, and different scopes with their different cardinalities usually run on multiple different threads. No way to communicate through that with exceptions. We need something more powerful here. Answer: async message passing. Catch every exception at the root of the lower level scope. Don't ignore anything, don't let anything slip through. This will close and dispose all resources created on the call stack of the current scope. Then propagate error messages to scopes higher up by using message queues / channels in the exception handling routine, until you reach the level where the concretions are known. That's the guy who knows how to handle it.
SUMMA SUMMARUM
So according to my interpretation catch late means to catch exceptions at the most convenient place WHERE YOU ARE NOT BREAKING ABSTRACTION ANYMORE. Do not catch too early! Catch exceptions at the layer where you create the concrete exception throwing instances of the resource abstractions, the layer which knows the concretions of the abstractions. The "wiring" layer.
HTH. Happy coding!