I was originally going to write my thoughts about exceptions vs. error codes in the context of PHP development, but I got completely sidetracked. So instead, I’m going to talk a little bit about Java. Please remember that I have written a grand total of about 30 lines of Java code in the last 5 years, and I could very well be talking out of my ass. But I like to think I’m not an idiot, even if I’m not fluent in the language.

There’s a particular API wart I’ve seen with Java file I/O. I can’t find any references right now (sorry!), but I’ve seen it mentioned in the context of Java, RAII (which Java doesn’t have, yet some people do argue to that effect), checked exceptions, and general error-recovery strategies.

This (pseudocode) is the core of what you have to do when you open a single file in Java:

void doFileIO() {
  File file;
  try {
    file = new File(path);
    // do stuff with file
  }
  finally {
    try {
      file.close();
    }
    catch (IOException e) {
      // couldn't close the file... but why? and do we care?
    }
  }
  return;
}

Why is that so ugly? Well, probably because Java doesn’t have real destructors (finalizers don’t count!), there’s no distinction between the following two phrases:

  • Please close this file, and tell me if something failed.
  • Close the damned file. Don’t fail.

With destructors, the distinction could have been taken care of in the file API like this:

class File {
  // many other functions, then these two:

  // please closes the file
  // and tell me if something failed
  void close() throws IOException {
    ...
  }

  // (destructor) close the damned file
  ~File() {
    try {
      this.close();
    }
    catch (IOException e) {
      // seriously, how do you fail to *release* a resource?  ignore it.
    }
  }
}

This would give the best of both worlds. If the programmer wants to see file-closing errors, they can expicitly close it. If not, the destructor will clean it up as it goes out of scope (note: this doesn’t really work in Java, but it’s quite dandy in PHP/Perl/Python/C++).

This is mostly a problem because file.close() is usually going to be called while in the midst of handling an exception, when there’s usually something more important already happening. The related problem in C++ happens when a destructor can raise exceptions. This is what C++ does:

  1. Something raises exception A
  2. Stack unwinds, calling destructors of all local objects
  3. Something on the stack raises exception B in its destructor
  4. Program aborts. Whoops. Looks like we can’t deal with nested exceptions.

Why didn’t this happen in C? Well, here’s one case where error codes are nice: error codes can be implicitly ignored. If you are bailing out in an error condition, it’s ok to clean up as much as you can, and ignore cleanup failures. This problem only occurs when exceptions get thrown into the mix, because they have to be ignored explicitly.

What’s the takeaway? Well…

  • You can’t infinitely handle errors. If every resource-releasing function could raise an error, your program could theoretically loop forever upon receiving an error. At some point, you have to stop and say “I don’t care about other errors right now, I am busy with one already.”
  • Java API developers maybe made a mistake in making File.close() throw. Everyone who uses files has those silly nested-try blocks, which IMHO signifies that the encapsulation is incomplete (I don’t have to release my memory, but I have to jump through hoops to release a file descriptor). I say maybe only because I know it’s easy to add a wrapper function that suppresses the error… if I had to write Java for a living, I would probably follow that route. If I leak file descriptors doing that, it’s easy to add logging in that single wrapper so I can fix that fast, too.
  • I generally like exceptions better than error codes. However, I do think it’s important for a developer to pull his head out of his proverbial ass and say, “I don’t care if this pthread_cancel function completes, because I’m dealing with a larger-scale failure, and one defunct thread won’t make it any worse.” This definitely takes more work with exceptions than with error codes (which I suppose is good, because it’s usually not the best way to handle errors).
Advertisements