| By Craig Dewalt, Max Tardiveau | Article Rating: |
|
| March 1, 2003 12:00 AM EST | Reads: |
17,116 |
Do you consider yourself a Java expert? Think you know everything about exception handling? Can you quickly spot the six exception handling problems below?
1: OutputStreamWriter out = ...
2: java.sql.Connection conn = ...
3: try { 5
4: Statement stat = conn.createStatement();
5: ResultSet rs = stat.executeQuery(
6: "select uid, name from user");
7: while (rs.next())
8: {
9: out.println("User ID : " + rs.getString("uid") + 6
10: ", name : " + rs.getString("name"));
11: }
12: conn.close(); 3
13: out.close();
14: }
15: catch(Exception ex) 2
16: {
17: ex.printStackTrace(); 1-4
18: }
Every Java developer should be able to spot at least two. If you can't spot all six, read on.
Rather than provide general guidelines (most of which are well known), we decided to reveal what we call anti-patterns: (unfortunately) common bad programming practices that we've seen time and again in Java code. Our purpose is to familiarize you with these counterexamples so you can quickly spot them and avoid them.
Now for our hall of shame the six most common misuses and abuses of exception handling.
15: catch(Exception ex)
16: {
17: ex.printStackTrace();
18: }
This is the bane of Java programming catching an exception and not doing anything about it. In terms of frequency and severity, this is probably comparable to the infamous unchecked buffer problem in C and C++. If you see this anti-pattern, you can be 99% sure that it's wrong (there are a few cases where it might make sense, but they should be carefully commented to make it clear to the maintenance developer).
This is wrong because an exception (almost) always means that something bad (or at least unexpected) has happened, yet we choose to ignore and silence that cry for help. Calling printStackTrace does not qualify as "handling an exception" it's okay for debugging but should not be present past that initial step.
This problem is frighteningly pervasive. If you look at the documentation for the JDK class ThreadDeath, you'll see the following comment: "The class ThreadDeath is specifically a subclass of Error rather than Exception, even though it's a Œnormal occurrence,' because many applications catch all occurrences of Exception and then discard the exception." This poor programming practice is so common that, in an unhealthy feedback loop, it has affected the design of Java itself. Grunt.
What should you do instead? You basically have four options:
1. Handle the exception: Do something about it. Correct the problem, notify someone, or do some different processing, whatever makes sense in this particular situation. Once again, calling printStackTrace does not
qualify as "handling the exception."
2. Rethrow the exception: This is not uncommon if your exception handling code examines the exception for additional information, and decides that it can't handle it after all.
3. Translate the exception into another exception: Most often this is used to translate a low-level exception into an application-level one.
4. Don't catch the exception: If you're not going to do anything about it, why bother catching it? Let it go (and, if necessary, declare it in the signature of your method).
Solution 1: If you catch an exception, do something about it. Don't bury it.
15: catch(Exception ex)
It's often tempting to have a general catch statement. The most common is undoubtedly catch(Exception ex). In almost all cases, that's a bad idea. Why?
To understand why, we need to step back and consider the purpose of a catch statement. You use a catch statement to indicate that you anticipate certain exceptions and that you're willing to handle them. The class of the exception(s) is how you tell the Java compiler which exceptions you want to handle. Since the vast majority of exceptions inherit directly or indirectly from java.lang.Exception, specifying that in your catch statement effectively says you want to handle almost any exception.
Let's look at the code sample again. What kind of exceptions are we likely to catch? The most obvious one is SQLException, quite likely to happen any time you deal with JDBC. IOException is another possibility we're dealing with a stream writer. You can already see the problem: it doesn't make sense to handle these two (very different) types of errors in the same catch block. It would make a lot more sense to have two catch blocks, one for SQLException and one for IOException.
In addition, a large number of other exceptions could occur. What if, for some obscure reason, executeQuery returns null? You'll get a NullPointerException in the next line, yet that (unexpected) exception will be ignored (unless you happen to be looking at the console for your program and even then, unless you wrote that code yourself, what good does that do you?).
The better approach is to be more specific. In this case, we should have two catch blocks, one for SQLException, the other for IOException. This makes it clear to the next reader of this code that you have thought about these scenarios. But what about all the other exceptions? Let them go. Don't handle them (unless you have a reason to). You cannot (and in fact should not) try to handle all possible exceptions let them bubble up; someone else will deal with them (the JVM if nothing else).
There are some instances where catching Exception is a good idea, especially when testing or debugging. Once you have isolated the cause of the problem, you need to remove the catch Exception and replace it with the appropriate kind of Exception(s).
Solution 2: Be as specific as possible in your catch statements and use multiple catch statements if necessary. Don't try to handle all possible exceptions.
3: try {
4: Statement stat = conn.createStatement();
5: ResultSet rs = stat.executeQuery(
6: "select uid, name from user");
7: while (rs.next())
8: {
9: out.println("User ID : " + rs.getString("uid") +
10: ", name : " + rs.getString("name"));
11: }
12: conn.close();
13: out.close();
14: }
One of us used to work in a small office with only one bathroom and you had to use a key. One programmer was notorious for neglecting to return the key to its rightful place, thereby inconveniencing the rest of the office. Often this was because he would get interrupted while coming back to his desk and would forget to return the valuable resource.
Similarly, when an exception occurs, it affects the execution path of your program. It's easy to overlook that simple fact. If you use resources such as files, sockets, JDBC connections, etc., you need to make sure they're properly closed and released even if an exception occurs. Java has a mechanism designed for such a thing: finally.
Finally is a wonderful thing; it allows you to guarantee that your cleanup code will be executed before the end of the try/catch/finally block, regardless of whether or not an exception is being thrown. Think of it as a string on your finger that reminds you to return the bathroom key, even if something distracts you. Yet finally is rarely used, even by intermediate programmers.
Of course, finally blocks should be written with great care. In particular, you should be careful about exceptions being thrown in a finally block this is your final chance to clean things up; don't let it slip. In some cases, it may be acceptable to bury an exception that's thrown in a finally block. Think of it as a best effort. Regardless, use your best judgment, or better yet, ask someone more experienced.
Solution 3: Make sure all resources are properly released. Use finally liberally.
3: try {
4: Statement stat = conn.createStatement();
5: ResultSet rs = stat.executeQuery(
6: "select uid, name from user");
7: while (rs.next())
8: {
9: out.println("User ID : " + rs.getString("uid") +
10: ", name : " + rs.getString("name"));
11: }
12: conn.close();
13: out.close();
14: }
15: catch(Exception ex)
16: {
17: ex.printStackTrace();
18: }
Anyone who has been married for a number of years can relate to this situation. Look at the code again: what happens if an exception occurs during the execution of the loop? Will we get information that can tell us what causes the loop to fail? Not really. All we'll get is an indicator that something is wrong in the area of the class that's doing the processing, but we may not get any input as to what caused the problem.
The basic "stack trace" that shows the path of execution to the class that caused the exception is cryptic and not easily parsed. It also causes any tester without a Java development background to report application problems with a very general description such as "encountered an ugly Java problem." If such an exception is wrapped to display a message in plain English, testers are more likely to repeat the specific message displayed to them. Not everyone who tests business applications is experienced in the delicate art of stack trace interpretation.
In addition to displaying a more user-friendly message, there's great benefit in providing a little extra data such as the Class, Method, and a text message. While it's true this information can be obtained from the stack trace, it's much easier to read when it's isolated. Also, taking the extra time to describe the behavior when you're creating the code will greatly expedite resolving the problem later.
One solution is to embed a snippet like the following one wherever exceptions get logged in the application. You can use the this.getClass().getName() method, and insert the method named and a text message every time the program reports a checked exception. This practice makes exceptions easy to read and to parse.
this.getClass().getName(), "mymethod", "some message"
One caveat to this approach is that with a static method call you need to manually insert the class name because the "this" object reference does not have a this to refer to. Simply replace the this.getClass().getName()with your class name "myClass".
Solution 4: Report a reasonable amount of data in a way that's easy to read.
3: try {
4: Statement stat = conn.createStatement();
5: ResultSet rs = stat.executeQuery(
6: "select uid, name from user");
7: while (rs.next())
8: {
9: out.println("User ID : " + rs.getString("uid") +
10: ", name : " + rs.getString("name"));
11: }
12: conn.close();
13: out.close();
14: }
Putting too much activity in a single try block is a bad programming practice, even though it's common. The reason it's common is that it takes extra time to isolate what could go wrong in a particular block and what exceptions could get created as a result of this processing. Wrapping too many statements in a giant catch is like preparing for a move by packing your household goods in refrigerator-sized boxes. Sure, all your goods are in one place, but imagine the sorting task you'll have in the near future. For the inexperienced developer, it's easier to put a bunch of statements in a try block and wrap it up in a generic catch Exception than to isolate the statements. This practice makes troubleshooting Exceptions generated during program execution that much more difficult because there are more variations to check to find out what caused the Exception.
The code example doesn't really illustrate this fully because there's not enough space to show a complete example, but this is a common problem.
Solution 5: Keep try blocks as small as possible, but no smaller.
7: while (rs.next())
8: {
9: out.println("User ID : " + rs.getString("uid") +
10: ", name : " + rs.getString("name"));
11: }
This is the silent killer of Java systems. Look at the code again and imagine what happens if an exception is thrown in the middle of the loop. The execution of the loop will be interrupted, the catch block will be executed, and that's about it. What about the data that we're writing out? It will be silently incomplete. Whoever or whatever is supposed to use this data will receive incomplete (and hence incorrect) data, but will probably have no idea that it is incomplete. For some systems, that's a problem far more serious than any crash.
A better approach here would be to attempt to write a message to the output, signaling that the data could not be completely written. Another possibility would be to buffer the output (if possible) and write it out once we have it ready to go.
Solution 6: Consider all likely exceptions and how they'll affect the flow of execution.
Example (Rewritten)
Given these facts, Listing 1 shows what the same code should look like.
(Listing 1 can be downloaded from www.sys-con.com/java/sourcec.cfm.) Note
that it's quite a bit more verbose. That's not a bad thing good code often
includes a lot of error handling.
Conclusion
All the anti-patterns in this article were inspired by actual code
written by professional programmers. Before you snicker, ask yourself
whether your code is really free from these poor practices. Even the most
experienced programmers sometimes fall back into them, simply because
they're easy (we certainly do). Think of them as bad habits they are
notoriously difficult to shake off, but just trying makes you a better
person.
Of course, nothing in this article should be taken as gospel. Use common sense and your experience. For each anti-pattern described here, you could certainly come up with counterexamples. As long as the rules are broken willingly and thoughtfully, you have our blessing, but we do beg for one favor: if what you do is not obvious, please write a nice comment. You'll thank yourself later. Also, share your wealth of knowledge with others on your team; extra time spent on training is a small price to pay in comparison to a massive refactoring effort at the end of the development cycle.
References
Published March 1, 2003 Reads 17,116
Copyright © 2003 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Craig Dewalt
Craig Dewalt is a surfer who likes to lead Java development teams.
More Stories By Max Tardiveau
Max Tardiveau is the founder of Integrity Logic.max@tardiveau.com
![]() |
Infernoz 08/17/06 03:29:20 AM EDT | |||
You forgot to have a try/catch/finally for the connection itself. Any resource must be allocated inside a try and have it's reference outside the try, so that it can be deallocated if something goes wrong. |
||||
![]() |
David Chen 03/12/03 03:02:00 PM EST | |||
Excellent article on the potential places where a developer would miss or make mistakes in exception handling. Like it is said at the end, "nothing in this article should be taken as gospel." I think attention should be paid to those anti-patterns, but how it is done in pratice should be driven by requirements. For example, if it is only required to do the same thing on matter what kinds of errors have happened, I would argue that it is ok to catch just Exception, instead of each of the specific ones. The same can be applied to how big the try/catch block should be. As matter of fact, I doubt that many experienced programmers can identify all of the 6 anti-patterns, due to lack of enough requirements for the example. |
||||
![]() |
Max Tardiveau 03/10/03 09:52:00 PM EST | |||
You are of course right. It is very difficult to get these minute details right in the world of publishing. Thanks for pointing it out. -- Max |
||||
![]() |
Rick 03/04/03 03:20:00 PM EST | |||
Nice article. I have one minor nit to pick. You are missing a closing brace in listing one. I believe it should go just before "if (out != null) {" to close the one from "if (conn != null) {". Heck, while I'm at it, here's a 2nd nit: |
||||
- Cloud CEOs, CTOs & SVPs to Speak at 4th International Cloud Computing Expo
- Kindle 2 vs Nook
- Why IBM’s Server Chief Got Busted
- The Difference Between Web Hosting and Cloud Computing
- Cloud Computing Journal Opens "Readers' Choice Awards" Nominations
- Cloud Computing Expo: Exclusive Q&A with Yahoo! SVP Cloud Computing
- Industry Experts Discuss the State of Cloud Computing
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- It's the Java vs. C++ Shootout Revisited!
- The End of IT 1.0 As We Know It Has Begun
- An Introduction to Abbot
- Java Kicks Ruby on Rails in the Butt
- Interviewing Java Developers With Tears in My Eyes
- Cloud CEOs, CTOs & SVPs to Speak at 4th International Cloud Computing Expo
- 1st Annual Government IT Expo: Call for Papers Deadline July 15
- How to Diagnose Java Resource Starvation
- REA Is Where RIA Becomes the Norm
- Kindle 2 vs Nook
- Anatomy of a Java Finalizer
- Why IBM’s Server Chief Got Busted
- A Cup of AJAX? Nay, Just Regular Java Please
- Java Developer's Journal Exclusive: 2006 "JDJ Editors' Choice" Awards
- The i-Technology Right Stuff
- JavaServer Faces (JSF) vs Struts
- Rich Internet Applications with Adobe Flex 2 and Java
- Java vs C++ "Shootout" Revisited
- Bean-Managed Persistence Using a Proxy List
- Reporting Made Easy with JasperReports and Hibernate
- Creating a Pet Store Application with JavaServer Faces, Spring, and Hibernate
- What's New in Eclipse?




























