I stumbled across a couple of old posts from Bruce Eckel, and especially appreciate how much experience he brings to the topics, and how reasonable his tone is, in contrast to some of the (un)checked exception debate, for example. The first is one of a handful of things I just read advocating unchecked exceptions, as is used in C#. For Eckel, someone who’s been looking at exceptions for an awful lot of years, the light went on after using a language with exceptions, but without checked exceptions:
When I started using Python, all the exceptions appeared, none were accidentally “disappeared.” If you want to catch an exception, you can, but you aren’t forced to write reams of code all the time just to be passing the exceptions around. They go up to where you want to catch them, or they go all the way out if you forget (and thus they remind you) but they don’t vanish, which is the worst of all possible cases [emphasis mine]. I now believe that checked exceptions encourage people to make them vanish. Plus they make much less readable code.
Here’s an entertaining illustration from Heinz Kabutz:
I enjoyed using Together/J, even though it was rather memory hungry. Instead of starting with 512MB as default maximum old generation memory size, I set it to only use 92MB. This made Together work faster and save resources. However, occasionally random threads would simply die, so you could perhaps not print anymore or some other functionality would vanish.
Kabutz’s article also has a good discussion of how java 1.5 helps you catch all unhandled exceptions. And it introduced me to the cool java api diff tool here.
My own experience with exceptions has been one of periodic enlightenments punctuated by sharp realizations that I don’t completely get it yet. I learned SmallTalk and CORBA at the same time, and after getting burned by uncaught exceptions killing worker threads, I got in the habit of wrapping all remote calls and remote call implementations in top-level catches. When I came to java, it became more complicated: UserExceptions were checked but SystemExceptions were not. I slowly grew to appreciate this, but my top-level catching was still swallowing a lot of exceptions, and/or I was spending a ton of time how far up the stack to carry my checked ones. Then I started thinking of RuntimeExceptions as “bugs” or things I didn’t want to catch. Finally a useful rule about what to (not)catch! Except for the SystemException special case, uncheckeds were bugs, not to be caught.
Then I had a subsystem very vulnerable to bad data: I needed a ton of positional string parsing (and therefore RTE handling code), and it was far simpler to take a best-effort approach and swallow all those RTEs at the top. Simple success or failure, and I couldn’t do anything about the badly formatted data anyway. Ah, RTE=bug but with some more intentional special cases, because sometimes it’s so damn much more productive.
Okay, now it’s not such a big step to using RTE (unchecked) any old time, for productivity reasons, because we will find all the problems at runtime anyway, and we’re much less likely to swallow. The tests are key: TDD makes deferral to runtime a feedback loop almost as tight as the compiler.
The second Eckel posting I’m blogging is very related: his emerging preference for strong testing rather than strong typing. Huge experience in C++/Java, but some personal experience with high-productivity, low-problem Python systems has caused him to reconsider a very basic, long-held assumption. Here he makes an analogy to the productivity boost the compiler brought us:
It’s very much like going from old C to C++. Suddenly, the compiler was performing many more tests for you and your code was getting right, faster. But those syntax tests can only go so far. The compiler cannot know how you expect the program to behave, so you must “extend” the compiler by adding unit tests (regardless of the language you’re using). If you do this, you can make sweeping changes (refactoring code or modifying design) in a rapid manner because you know that your suite of tests will back you up, and immediately fail if there’s a problem - just like a compilation fails when there’s a syntax problem.
What will the post-TDD programming shift bring? I want Eckel’s posting where he says “It’s very much like going from old strong-typed languages to weak-typed, test-driven development. Suddenly, your tests were catching all your bugs as fast as you wrote your code. But those tests can only go so far. Now, …”