Analysis of Exception Handling Patterns in Java Projects: An Empirical Study

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
6,282
Interesting study of ineffective exception handling.
Java is one of the few programming languages to enforce an additional compilation check on certain sub- classes of the Exception class through Checked Exceptions. This enforcement has made the writing of exception handling code very common. Almost all the existing IDEs today give the suggestions and even generate the try-catch code automatically to assist the developers. This motivated us to study the habits of developers in general. As part of this study, empirical data was extracted from software projects developed in Java
image.jpeg

Result:
image.jpeg

Conclusion:
In this paper, we studied all Java projects in the GitHub and SourceForge repositories to spot common exception handling practices. We have particularly analyzed the checked exception handling in good detail. Next, these observations were compared against best practices presented by Bloch (book - “Eective Java”). To sum up the results, most developers ignore checked exceptions and leave them unattended. This is true for other types of exceptions as well. General exception handling shows developer inclination shifting from proactive recovery to debugging.
The ignore-for-now and mere logging approach followed for checked exceptions may help with debugging, but the absence of required recovery steps and continued smooth flow may lead to errors going unnoticed and potential in- correct product output. Thus, handling exceptions well is a skill that developers need to inculcate in themselves, else this language enforcement of checked exception handling will fall short of its intended value. Developers also tend to use generic handlers over specific ones. The reason for these approaches cannot be confirmed to be correct or incorrect without studying source codes. However overall, the results show major di↵erences between theoretical best practices and the use of exception handling in practice. We further summarized the best practices stated by Bloch and described one of the recommended approaches by Bruce to tackle exceptions.
I've never been able to appreciate the thinking behind languages that by design chose to enforce "try catch blocks" patterns.

In practice I've rarely found many valid uses of this pattern; and in practice it's a far worse code smell than partial functions; because it can be used to expressively mask problems, whereas at least the preconditions usually associated with partial functions, have a clearly defined set of failures (they're a better kind of bad :whistling:)

In studying language design, it's obvious that exception handling cannot be avoided, but it's also (at least for me), obvious that current implementations in languages like Java, C#, ... have made it too easy to simply ignore all exceptions, and even some IDEs actively help you to add the superfluous "try catch block". Consider for a moment how much bad code relies on exception avoidance to appear stable?

As to alternatives: many lower level exceptions should simply be rethrown and be dealt with higher up the call stack; but it should ultimately be dealt with and never ignored; because any API with exceptions that you can safely ignore shouldn't have had exceptions in the first place i.e. they were probably a partial function masquerading as an exception.

Link to study
http://plg.uwaterloo.ca/~migod/846/current/projects/09-NakshatriHegdeThandra-report.pdf
 
Last edited:

Gnome

Executive Member
Joined
Sep 19, 2005
Messages
7,208
Having both checked and unchecked exceptions makes perfect sense to me.

Checked when you have an expected edge case.
Unchecked when it is not expected (eg. NPE, IllegalArgument). Most unchecked exceptions should truly be a case where typically the developer will be unable to do anything useful to prevent it.

When I use reflection for example I find the TargetInvocationException tedious. Typically because I'm writing a reflective lambda function as a utility.
In that case the only option is:
Code:
public void someMethod() {
  try {
    ..
  } catch(final Exception ex) {
    if (ex instanceof RuntimeException) throw (RuntimeException) ex;
    throw new RuntimeException(ex);
  }
}

Or
Code:
public void someMethod() throws TargetInvocationException {
  try {
  ..
  } catch(final Exception ex) {
    if (ex instanceof RuntimeException) throw (RuntimeException) ex;
    throw new TargetInvocationException(ex);
  }
}

Or

Code:
@SneakyThrows //Lombok
public void someMethod() {
  throws new Exception("I'm going to the top unchecked baby!");
}

Now SneakyThrows is controversial because it allows a checked exception to be pushed up the stack as is. In which case a method high up could see this exception and be like ???
But how? It is checked and my code compiled without the need to catch it!

Which brings us back to checked exceptions that need the throws tag in order to not be caught. (Current world)
It would however be useful to have a tag that simply says that any checked exception should be pushed up but force checked at the next level.
Similar to generics, I don't want to say the following:
throws XXException, YYException, ...

simply:
rethrows

eg.
Code:
public <T,R> R lambdaExecutor(final T input, final Function<T,R> f) rethrows {
  return f.a(input); // any checked exceptions need to be handled by the caller of this method, as a checked exception, not by f
}

The problem with blind unchecked exceptions everywhere is that it becomes easy to ignore legitimate edge cases that should be catered for.

But certainly Java could tone down some of the checked exceptions. eg. What can I really do if I can't close my InputStream? (which is why everyone ignores it).
Or InputStream.read fails because of disk errors. Crash hard is probably my best option there.

My 2c on the topic
 
Last edited:

[)roi(]

Executive Member
Joined
Apr 15, 2005
Messages
6,282
Unchecked exceptions are like partial functions and hidden inputs: unpredictable. Personally not a fan of either checked or unchecked, mainly because it's not part of the formal type signature. I rather prefer Either types (Result, Optional, Future, Promise, etc.); primarily because "try catch throw" doesn't accommodate asynchronous processes and overall I just prefer an approach where the type signature in conjunction with higher order functions explicitly control the branching.

Java doesn't formally have an Either Type, so conformance across types isn't standard, however classes like CompletableFuture and Optional have separate / different implementations of this approach. For a more complete solution, you could look at Functional Java which includes a reference implementation for an Either type:

Rethrows
As for "rethrows", you may want to look at Swift, their implementation of "throws/rethrows" -- I think pretty much covers what you described, for example:
Code:
func someFunction(callback: () throws -> Void) rethrows {
    try callback()
}
As for not having to explictly specify exceptions, that's also happens to how they've implemented throws & rethrows (was a highly debated topic though). Another key difference is that exceptions cannot ever be unchecked i.e. no SneakyThrows -- throw always requires declaration and similarly must always be treated in a "try".
 
Top