Exceptions Are Your Friend (but so is garbage collection)

October 18th, 2004

Benjamine Carlyle and I have been discussing exceptions. Put simply, he hates them, I love them. I think I know why now.

Adrian Sutton argues that exceptions are not in fact harmful but helpful. I don’t know about you, but I’m a stubborn bastard who needs to be right all the time. I’ve picked a fight, and I plan to win it ;)

I’m also a stubborn bastard (just ask Byron about ampersands), so let the death match begin! Actually, as I mentioned in this case I think we’re arguing different things so we might be able to resolve this amicably.

Benjamin’s biggest problem with exceptions is that code in between the throwing of the exception and the handling of the exception “gets hurt”. The answer here is called abstraction. Abstraction in this context has a very simple tenant: when I call a method, it does stuff and I don’t care how it does it. In other words, if I call the method doStuff(), I don’t care if an exception is thrown in a deeply nested function call or directly by doStuff, to me doStuff simply through an exception. This is then true for doStuff, it only cares about each method it calls and not any methods under that and so on. So for any given piece of code we only think about one level. Methods and classes provide abstraction.

Now that we’ve abstracted things, there simply is no code left in the middle to get hurt. Each method should handle whatever exceptions it may cause in whatever way it needs to.

It’s the whatever way it needs to that differentiates Benjamin’s viewpoint and mine. Benjamin has been talking about exceptions in C++ whereas I have been talking about exceptions in Java. In C++ when an exception is thrown you have to carefully sort out what memory you had already allocated and not freed then make sure you free it etc etc etc. In other words, an unexpected failure is catastrophic and pretty much unrecoverable. In Java however, it’s no big deal at all. Objects will happily be garbage collected when no longer required, it’s simple to tell if objects have been created and it’s also really simple to clean up any allocated resources.

So in Java if we were working with a database, we’d do something like:

public void doStuff() {
    try {
        _db.openConnection();
        // Work with connection, possibly throwing exceptions.
    } finally {
        _db.releaseConnection();
    }
}

Note here that we didn’t catch any exceptions, they will still propagate up to somewhere that can handle them intelligently (we probably should have caught them and used a wrapper exception for neatness but it’s all the same concept). Intelligently might mean killing the application or notifying the user or perhaps even trying again. The finally block however is guaranteed to run in all cases (other than the JVM exiting - remember, System.exit() is evil). With that finally block in place, our method is now guaranteed to clean up after itself correctly no matter what happens.

Situations that you need a finally block in java are pretty rare. It pretty much only happens when you’re working with databases or object pools. In C++ however, anytime you allocate memory, you need a finally block and not only that, you need a pretty complex one to make sure that you don’t miss freeing any memory and don’t free memory that wasn’t allocated. I don’t know enough C++ to know if this is even possible but it definitely sounds complex.

The article that Benjamin points to discusses these problems but does so in the context of C++ and points out some incredibly shoddy points in the example code which would be flaws even if return codes were used. I fail to see anything in that article that is actually specific to exceptions. It’s all a lacking of programmer ability in the first place (which would have existed regardless of how errors were reported and handled) or limitations of the language in use (anything related to memory management).

Regarding the quote:

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Using exceptions is not writing something as cleverly as possible, it’s actually writing something as clearly as possible. In fact, exceptions are really no different to return codes except for the fact that they are more obvious. It is trivial to replace all instances where an exception is used with a form of return code instead but it will make the code significantly less readable. Besides, the “cleverly as possible” is particularly referring to using “cute” or “tricky” ways of doing things instead of using a clearer approach. Exceptions do not fall into this category as they were specifically created to make things clearer.

Adrian also picks up my final points about how to program with the most straightforward error checking, and perhaps I should have worded my final paragraphs more clearly to avoid his confusion. I don’t like threads (that share data). They do cause similar code path blowouts and comprehensibility problems as do exceptions unless approached with straightforward discipline (such as a discipline of message-passing-only between threads).

Threads are difficult, threads do increase complexity, threads however are extremely important. Threads that don’t share data shouldn’t be different threads, they should be different programs (or at least processes). Threads must be used wisely. I’ll leave it at that for now, but expect a future entry on the subject.

Dealing with the operating system can’t be written off so easily, and my note towards the end of my original post was meant to convey my sentiment of “exceptions are no good elsewhere, and if the only place you can still argue their valid use is when dealing with the operating system… well… get a life” :)

Anyone who puts forward the argument that exceptions are more important along operating system boundaries is an idiot. The clearest examples of exception usage is found at operating system boundaries definitely because that’s the area where unexpected errors are most likely to occur, however unexpected errors can happen in many more places than at the operating system boundary. Exceptions are appropriate anywhere that a return code is appropriate. Remember, exceptions are simply a different and clearer way of returning the error code.

Most software is far more internally-complex than it is complex along its operating system boundary. If you want to use exceptions there, feel free. Just don’t throw them through other classes. I personally think that exceptions offer no better alternative to return code checking in that limited environment.

They are clearer. That’s all. Checked exceptions have the added benefit of forcing people to deal with them (another major reason why C++ exceptions aren’t particularly useful). When an IOException is thrown in Java, every single method that it might impact on must have explicitly dealt with it. No code will be caught out because they didn’t expect that exception, ever.

I’m not familiar enough with C++ to be certain, but I am definitely leaning towards thinking that exceptions in C++ is evil, though mostly I’d just suggest that C++ itself is mostly the problem (it’s just too difficult to recover from unexpected errors). I don’t know what the solution to the problem is and I suspect that the same kind of problem would show up in C despite the fact that exceptions simply aren’t an option.

What I can say without a doubt however, is that exceptions in Java are well thought out, simple to use and provide a massive benefit to writing clear, robust code. Most of this I suspect is due to the fact that it is surprisingly simple to recover from errors in Java in many situations. There is however a never-ending debate in the Java community about whether or not checked exceptions are a good thing or not. Personally I like them, James Gosling puts forward some very strong arguments for them but they do have some problems particularly when dealing with interfaces and the list of possible exceptions can get out of hand at times.

Still, the concept of exceptions themselves is definitely beneficial, the particular implementation of them may not be though.

Exceptions Are Your Friend

October 16th, 2004

Benjamin claims that exceptions are evil, he couldn’t be more wrong. Exceptions are in fact one of the best ideas that has been added to languages, particularly checked exceptions which force people to deal with error situations.

Benjamin’s first problem with exceptions is that they’re impossible to test. This assertion is flat out wrong. Exceptions simply make you aware of a case that you’re not testing. For instance, say we have a function that writes to a file, as part of our testing we should test that it behaves correctly (ie: produces the expected behavior) even in situations when the file can’t be written either because of a lack of permissions, a full disk, missing directory, network error or hardware failure. It doesn’t have to work in those situations, but it must behave predictably because one or more of those situations will occur when the program is in production at some point or another. Without exceptions, the code might look something like (using some convenient but totally fictional libraries):

public void writeFile(String path, String output) {
    new FileWriter(path).write(output);
}

So when we’re writing test cases we pass in a path and some output and then check that the contents of the file matches the output we gave and are happy. Our code coverage now returns 100% for that method. The trouble is the actual test coverage is much, much lower than 100% because it hasn’t tested any of the error conditions. There’s not even any indication that things might go wrong there. C programmers will be the first to tell you that you must always check the return code from IO libraries so that you detect errors - exceptions make that process more obvious and checked exceptions makes you take action to handle the case (even if your handling of it is really bad).

So now lets take a look at that method again, with exceptions:

public void writeFile(String path, String output) {
    try {
        new FileWriter(path).write(output);
    } catch (Exception e) {
        System.err.println("The file "" + path + "" could not be written.  The application can not continue.");
        e.printStackTrace();
        System.exit(-1);
    }
}

Now I’ll be the first to admit that even this second example is awful code and there’s no chance you’d ever find something like that in production code I wrote but it at least provides an informative error message when things goes wrong, terminates the program cleanly and most importantly, makes it absolutely clear that things can go wrong. Our code coverage for the method is now down around 50% and we can clearly see that we should add test cases that simulate things going wrong.

So how do we test it? The same way we test any other situation. Manipulate stuff so that the code follows the code path we want it to. The simplest method to generate an error here would be to pass in a file that can’t possible be created:

public void testErrorCase() throws Exception {
    String path = "AA:/thisPathDoesNotExist/file.txt";
    writeFile(path, "Test Output");
}

At this point we see why the code example above is so bad - code that calls System.exit() is a real pain in the neck to test (it’s possible, you just have to set up a security policy to disallow calling System.exit()). Even so, we have successfully made the code go into it’s error condition.

For the record, a couple of other changes to the exception handling would be to catch the specific exceptions that can be thrown and handle them as appropriate (ie: FileNotFoundException might prompt the user for a different file name, a permissions error might bring up an authorization dialog). Secondly, I’d move the exception logic up a little higher by throwing it from this method and catching it above somewhere - where depends on application design and what action will be taken in response to each error.

Unfortunately, that’s not always what happens. Some people like to put a “catch all exceptions” statement somewhere near the top of their program’s stack. Perhaps it is in the main. Perhaps it is in an event loop. Even if you die after catching that exception you’ve thrown away the stack trace, the most useful piece of information you had available to you to debug your software.

There’s nothing wrong with catching all exceptions at the top of the program stack, it just severely limits the potential for recovery since the recovery code has no idea where the problem occurred. Catching the exception at the top of the program stack most definitely doesn’t throw away the stack trace either - on the contrary, it allows the stack trace to be sent somewhere useful instead of just dumping it to the console (which may not even be visible to the user). There are a bunch of printStackTrace methods in Exception (at least in Java but something equivalent should be in any sane language) that you can use to print the stack trace where you need it to go. Most importantly though, catching exceptions at the top of the program stack allows you to “offer your sincerest apologies to your user” in a friendly dialog instead of just disappearing into the ether and taking their data with you.

The point I really can’t believe anyone would try and make though is:

The worst thing that catching an exception does, though, is add more paths through your code

Exceptions don’t add paths through your code, error conditions add paths through your code. Exceptions merely provide a way of handling errors. Burying your head in the sand and pretending that branch doesn’t exist won’t make the problem go away. That file still won’t exist, or that network connection still won’t open - it’s just that without error handling code your program crashes disgracefully - with error handling code at worst your program crashes gracefully and at best it fully recovers.

To minimise paths through your code, just follow a few simple guidelines:

1. Make everything you think should be a branch in your code a precondition instead
2. Assert all your preconditions
3. Proivde[sic] functions that client objects can use to determine ahead of time whether they meet your preconditions

This simply isn’t possible. It is impossible to know ahead of time if an IO error will occur because there may be more than one thing happening on the machine. For instance, if we wanted to try to avoid the situation of a file not being able to be written, we might be tempted to change our example method to:

public void writeFile(String path, String output) {
    if (!new File(path).canWrite()) {
        // Error handling code for lack of permissions.
    } else {
        new FileWriter(path).write(output);
    }
}

Looks like we can’t possible run into a file that can’t be written anymore right? Wrong. There is always the possibility that the permissions on the file will change right between when the check is performed and when the actual file is written - or perhaps even while the file is being written. While adding the check is often a good idea as it might avoid getting into an indeterminate state where part of the file might have been written, it is not a complete solution and you still need to handle the possible error condition when you actually write the file.

Benjamin’s third point about providing functions to check that preconditions are met is particularly susceptible to this problem - the preconditions may have been met when the check function was called, but they may not be met when the real function is performed.

Benjamin’s main complaint seems to stem from bad programmers not handling exceptions carefully. This can lead to situations where exceptions are just totally ignored (ie: try { perform(); } catch(Exception e) {}) or where the error recovery code still winds up leaving an inconsistent state behind. This however is not a problem with Exceptions, but with bad programmers. Bad programmers will create errors in any code, regardless of whether or not it has exceptions. Benjamin also asserts:

It isn’t possible to write a class that survives every possible excpetion thrown through its member functions while maintaining consistent internal state. It just isn’t. Whenever you catch an exception your data structures are (almost) by definition screwed up.

This is also wrong. For some classes, this is not only possible, it’s a requirement. The most obvious example is a wrapper class for a database. I’m sure you’ve all heard of commits and rollbacks right? They’re used to implemented the atomic requirement of the ACID tests for databases. Here’s how they can be used to implement the “impossible” case of a class that remains consistent in the face of errors:

public void addPerson(String name) throws DatabaseException {
    try {
      _database.execute("INSERT INTO people (name);");
      _database.commit();
    } catch (Exception e) {
        _database.rollback();
        throw new DatabaseException("Failed to add person to database.", e);
    }
}

If you ignore the poor SQL and security issues not quoting things correctly raises, you’ll note that this method is absolutely guaranteed to maintain a consistent state in the face of errors. Also note how we throw an exception ourselves to tell the caller of the method that it didn’t work and particularly note that the original exception and it’s precious stack trace is preserved so it can be logged later if required.

That example is however a little contrived since all the rollback stuff is handled by the database and the class itself doesn’t really maintain any state that we can see from that method. Still, if a database is capable of committing or rolling back changes then the same process could be used within a class if needed.

There are also times when it might be desirable to attempt to continue even if you can’t guarantee that the state is consistent. For instance, if you’re writing a document in a word processor and something odd happens and the program encounters an error situation. The worst thing the program could do is crash at this point because the user would lose their data. No matter how messed up the internal state might be, the program must at least make an attempt to save the users data. Note here that the save should go to a new file so that even if it doesn’t work out at least any previously saved versions are still okay.

Another situation where continuing on is probably worthwhile is where a function fails but it’s unlikely to have messed things up enough that the user can’t continue. For instance, there are a number of error conditions that may occur when the user tries to add a hyperlink in a HTML editor. Most of the time these error conditions result in the operation failing and everything is left in a consistent state (though the user doesn’t get their hyperlink). In some rare cases however, things might go really wrong and the state might become inconsistent, however it’s not feasible to detect if the state is invalid or not. In this case, it’s better to attempt to continue because in most cases it will all be okay and as an added bonus, in this case there is an undo function which provides a partial but exceptionally effective state reset function. I would however suggest that after such an error occurs a backup should be made of the users data and they should be warned that strange behavior might occur.

And in the miscellaneous other things I disagree with category:

Of course multiple threads are evil too, and for many of the same reasons.

Multiple threads are an extremely important feature and most applications should be taking advantage of them, particularly with the growing popularity of multiple processor systems. Writing safely threaded code is hard, but noone said writing code was easy. Time for a good, solid design and some learning on how to handle threads safely.

When dealing with the operating system, just use the damn return codes

But return codes are just a primitive, non-obvious form of exceptions. The only real difference is that it’s extremely simply to ignore return codes whereas exceptions terminate the thread if you don’t do something about them. Checking return codes adds exactly the same amount of complexity as handling exceptions does - it’s one extra branch for every different response to the return code.

Anti-Anti-Anti-Smokers

October 15th, 2004

(I’m upping the anti)

The next person that whinges about restriction of smoking to private homes will receive a free gift.

Yes ladies and gentlemen I shall stand next to them, and share with them the gaseous byproduct of my digestive process. It’s my RIGHT isn’t it?

Isn’t it?

David Jericho

No it is not. Quite the opposite in fact. As part of your right to life, you have the responsibility to not interfere with the right to life of others. Since smoking reduces the expected life span of the people around you via passive smoking, you are interfering with their right to life. I’m sure you could also word that around having the right to not have people reduce your health but I’m not sure how to word it clearly and the right to life is a far better established right.

As for the threat to share the “gaseous byproduct of my digestive process”, were it enacted, I would be forced to remove the fire hazard with extreme prejudice (and the closest bucket of water).

UPDATE: er, clearly, I’m an idiot as I’m agree with David entirely. I guess I got confused with the anti-antiness of it all.

Burning Ubuntu Linux ISO On OS X

October 14th, 2004

If you ever try to download, burn and install the Ubunto warty ISO image on OS X you’ll find that Disk Utility crashes and hdiutil crashes. To get it to burn you need to install cdrecord using fink (fink install cdrecord) and then use the information on this page to work out how to burn it. On my PowerBook G4 with a DVD-R/CD-RW drive I used the command:

sudo cdrecord -v speed=24 dev=IODVDServices warty-rc-install-powerpc.iso

and it’s working fine. We’ll see how it goes from here on in.

More Sex All Round

October 13th, 2004

Iain points to this gem of an article. Some choice quotes:

Dr Greening and colleagues asked 42 men to ejaculate every day for seven days

Excuse me Sir. Yes, sorry to interrupt - would you mind doing me a favor? Yes, I just need you to jerk off every day for a week. … Say, how’d I wind up in hospital again?

I think it’s exciting

Don’t we all Dr Greening. Don’t we all.