I've been a zone leader with DZone since 2008, and I'm crazy about community. Every day I get to work with the best that JavaScript, HTML5, Android and iOS has to offer, creating apps that truly make at difference, as principal front-end architect at Avego. James is a DZone Zone Leader and has posted 639 posts at DZone. You can read more from them at their website. View Full User Profile

Improving Exception Handling: One Subtle, One Radical Approach

08.07.2008
| 7007 views |
  • submit to reddit

Exception handling via try...catch...finally has some pointless headaches that really don't have any good reasons for their existence.

The Problem

Consider a classic database/session hunk of code:

try {
DBConnection connection = Driver.createCollection("url");
connection.execupdate("UPDATE something");
finally {
Driver.releaseConnection(connection);
}

Oops, can't do that, I have to do this:


DBConnection connection = null;
try {
connection = Driver.createCollection("url");
connection.execupdate("UPDATE something");
finally {
Driver.releaseConnection(connection);
}


Note the extra line of code, in this simple example. You all have probably had much more complicated versions with five or ten local var declarations at the top

 

The Subtle Proposal

So, my subtle proposal: make the finally and catch blocks child scopes to the main try block. That way, all the variables declared in the try section would be accessible/visible from the catch clauses for reporting and logging and error message composition, and for cleanup in the finally clause.


You may not buy this from the simple example above. One line of code, what's the big deal? Consider a complicated set of multiple calls, which I'll build upon for the second proposal:

try {
int status = checkconnection();
String configURL = getConnectionUrlFromConfig();
Connection connection = getDBConnection(configURL);
User user = (User)connection.getObject("a username");
} catch (NullPointerException npe) {
Logger.log("what was null? configUrl = "+configURL+" status = "+status+" user = "+user);
}

It's pretty common in complicated programs for there to be code that has lots of dependencies. This connection gets this setting based on this config value which invokes this service url. Things can go wrong at each level, with common NPEs. Since we don't have convenient syntax for per-statement exception checking (preview of my other major complaint), we usually end up with large try..catch blocks in a sort of crude batch processing of errors. However, since the catch block can't see multitudes of local vars declared in the try, we either need to add four variable declarations above the try statement, or just log an generic "something went wrong in the previous five lines" message - which sucks.

My suggestion for that doesn't even change syntax. At all. It does change the lexical scoping rules around trys and catches and finallys, which has a slim chance of breaking existing code. Maybe. It should be 99% backwards compatible.

The Radical Proposal

I just hinted at this. Because try { } catch (Exception e) {} is a statement involving two blocks and two keywords, it seems pretty long and heavyweight. Error handling is an important part of mature code, and it should be possible to place it into code without disrupting the readability of the core functional statements. What happens with existing try..catch is alluded to above, people end up batching ten or twenty or thirty or whatever lines of code in try...catches and then handling the errors that arise. In reality in the previous four-line example, we would want to check the error at EACH and EVERY. Right now that would look like, at best:

  int status = -1;
try {status = checkconnection();} catch (ConnectionDownException cde) {Log("conn down",cde); throw new AppException(cde);}
String configURL = null;
try {configURL = getConnectionUrlFromConfig();} catch (NullPointerException npe) {Log("Config not setup right",npe); throw new AppException(npe);}
Connection connection = null;
try {connection = getDBConnection(configURL);} catch (ServerDownException sde) {Log("server down",cde); throw new AppException(sde);}
User user = null;
try{user = (User)connection.getObject("a username"); catch (NotFoundException nfe) {Log("user not found in db",nfe); throw new AppException(nfe);}

Ugh is that annoying. Basically caused by the imposition of a scope block by the try, plus that try { keyword in the begining of each line ruins the readability of what's happening.

What If?

int status = checkconnection() 
$(ConnectionDownException cde) {Log("conn down",cde); throw new AppException(cde);};
String configURL = getConnectionUrlFromConfig()
$(NullPointerException cde) {Log("Config not setup right",npe); throw new AppException(npe);}
$(FileNotFoundException fnfe) {Log("Config file not created",fnfe); throw new AppException(fnfe);};
Connection connection = getDBConnection(configURL);
$(ServerDownException sde) {Log("server down",cde); throw new AppException(sde);};
User user = (User)connection.getObject("a username");
$(NotFoundException nfe) {Log("user not found in db",nfe); throw new AppException(nfe);}
So we have a means for inline handling of exceptions. I can still read what's going on pretty well and filter out the $exception handling. Or we could user @ or # or !! or ^.

Combine the above with the lexical changes I want for catch/finally (I'll try @ instead of $ to see if it's an improvement). Let's assume I architected a ProcessException service that will handling logging and (potentially) rethrowing/repackaging:
try {   
int status = checkconnection()
@(ConnectionDownException cde) {ProcessException.process("conn down",cde);};
String configURL = getConnectionUrlFromConfig()
@(NullPointerException cde) {ProcessException.process"Config not setup right",npe);}
@(FileNotFoundException fnfe) {ProcessException.process("Config file not created",fnfe);};
Connection connection = getDBConnection(configURL);
@(ServerDownException sde) {ProcessException.process("server down",cde);};
User user = (User)connection.getObject("a username");
@(NotFoundException nfe) {ProcessException.process("user not found in db",nfe);}
} finally {
if (connection != null) releaseConnection(connection);
}
That encapsulates a logically related set of statements into error handling/reporting and cleanup, with a minimized number of lines and pretty good readability IMHO. Exceptions are dealt with/detected immediately, and recovery can be done on the spot. Syntax can be highlighted. Handling can be nested.

Even if it isn't done in the Java main chunk, why not Groovy or Scala?

Comments

J Szy replied on Thu, 2008/08/07 - 7:08am

Perl6 had (or has?) an idea that could be adapted into something like:

try {
DBConnection connection = Driver.createCollection("url");
connection.execupdate("UPDATE something");

catch (DBException exp) {
handle(exp);
};
finally {
Driver.releaseConnection(connection);
} ;
}

 That is, to write catch and finally blocks inside the try block so variables wouldn't have to be declared outside the try block to be accessible to the other blocks.

Ricky Clarkson replied on Thu, 2008/08/07 - 7:23am

This does not work at all with the definite assignment rules (see the Java Language Specification).

Specifically, in:

try { int x = y(); } catch (X e) { What value should x have if y() throws an exception? } finally { Same question for here }

In Java, local variables are unreadable until they have been definitely assigned, which allows an optimisation (not cleaning stack on method entry or on gotos) - not having that would be slower and would encourage null, 0 or false default values, something Java already does too much of - i.e., it would introduce new bugs.

I suggest throwing your weight behind the BGGA proposal for closures, so that you can write C#'s 'using' construct as a library feature, getting: using(DBConnection connection: Driver.createCollection("url")) { stuff using connection here }

And for those reactionaries who think Java shouldn't copy C#, you're wrong.  Apart from some silly things like structs and unsafe blocks, C# only improves on Java.  And this particular idea didn't come from C# really - Lisp has had (with-open-file) since Microsoft was a baby.

Brian Sayatovic replied on Thu, 2008/08/07 - 7:58am

Interesting idea.  However, I'm not annoyed by this nearly as much as you are.  Generally, code I invoke that throws exceptions is encapsulated behind APIs I've built at the abstraction level of the problem I'm working on.  This means I don't have the try/catch ugliness proliferated throughout my code.  Mostly, it's at the boundaries (e.g. calling the database, rendering the UI).

phil swenson replied on Thu, 2008/08/07 - 10:53am

  1. // *** create connection before the try ***
  2. DBConnection connection = Driver.createCollection("url");  
  3. try {  
  4.   connection.execupdate("UPDATE something");  
  5. finally {  
  6.   Driver.releaseConnection(connection);  
  7. }  
I don't think you need all the null checks.  If the Driver.createConnection() fails, it throws an exception and the connection is not created.  So no need to release it.

Casper Bang replied on Thu, 2008/08/07 - 12:12pm

Very nice idea. It's sad Bloch's ARM proposal (basically a copy of C#'s using) doesn't get more focus, especially since it seems closures won't even make it into Java7.

Another observations with try...catch which would be very handy. I think the compiler should allow catching checked exceptions, even if none is thrown. Current practice makes it tremendously annoying to comment out a call to  a method which declared exceptions, since you will also have to commend out all surrounding try...catch code. Instead of refusing to compile, it should simple come with an unreachable code warning as the verfier does in many other similar cases.

Mike P(Okidoky) replied on Thu, 2008/08/07 - 3:41pm in response to: phil swenson

Agreed wholeheartedly. I was going to state this same obvious way of handling resources. In all the projects I've seen in many years now, practically NOONE seems to get this. It's so simple: create - try something - finally release. A few people being clumsy is one thing, but just about every project...? wtf. The kind of gobbledegook I'm seeing in projects makes me want to run my head into the wall. This becomes especially a problem where you have multiple resources. The only clear way of doing it is like this:

[code]
InputStream is = new FileInputStream("fromfile");
try
{
  OutputStream os = new FileOutputStream("outfile");
  try
  {
    read-from-is, write-to-os until eof
  }
  finally
  {
    os.close();
  }
}
finally
{
  is.close();
}
[/code]

Any other construct will look much harder to follow, and be hugely error prone once the developers start barfing up their gobbledegook (excuse my french). If a company sets a policy where resources must be handled this way where possible, you'll see far *FAR* less bugs and time wasted - I'm talking from many years of experience.

Another thing, unrelated, is the way people handle braces. It's so much clearer to line the braces up, instead of opening them at the end of the line.  Once you start indenting multiple levels, things will start to look like crap unless you have the close brace nicely line up with the open brace. This was a futile argument I've tried once, because people were too set in their ways. One of the arguments against it was that you would need more source lines with just braces in them - which actually makes the code easier to read and faster to scroll through.

Casper Bang replied on Thu, 2008/08/07 - 4:20pm in response to: Mike P(Okidoky)

I'm afraid most won't see eye-to-eye with your brace preference. Basically I agree with you, also considering what happens when lines starts to wrap (Java generics is a good driver for that). However, a proposed poll I suggested on java.net would suggest this practice to be deeply rooted in Java and the community and not likely to change:

http://today.java.net/pub/pq/210

In fact, I recently had to give in to following K&R/Sun standards at my job in the spirit of consistancy. It goes to show once something is an established practice in Java, it will remain so. Just have a look at the debate regarding tabs vs. space. I can't think of anohter language where so much time is spent discussing so little. lol

phil swenson replied on Thu, 2008/08/07 - 4:50pm in response to: Mike P(Okidoky)

Mike P...

 ya I don't get why the java world is filled with if != null checks for all this resource stuff and no one seems to question it.   I learned exception/resource handling in Delphi 10+ years ago and everyone did it the way we are talking about.  It's much cleaner, prettier, simpler, and less error prone. It's never failed me.

I agree in theory with your braces approach, but I caved in to the standard approach long ago....pretty much no one in the java world codes with open braces on their own line.

Mike P(Okidoky) replied on Thu, 2008/08/07 - 5:06pm in response to: Casper Bang

Just looking at the wikipedia's example where a long line is split between long lines, I just can not understand for the life of me anyone's love for the cluttery K&R style.

J Szy replied on Fri, 2008/08/08 - 7:35am in response to: Ricky Clarkson

[quote=rickyclarkson]

This does not work at all with the definite assignment rules (see the Java Language Specification).

Specifically, in:

try { int x = y(); } catch (X e) { What value should x have if y() throws an exception? } finally { Same question for here }

In Java, local variables are unreadable until they have been definitely assigned, which allows an optimisation (not cleaning stack on method entry or on gotos) - not having that would be slower and would encourage null, 0 or false default values, something Java already does too much of - i.e., it would introduce new bugs.

[/quote]

Well, now you have to assign that default value manually. The trouble is, either way there is a default value. The only robust way is to use two try/catch structures as others suggested. (The "Perl6" way still requires a default, explicit or not, and a check, hidden or not :) .)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.