Static Typing and the Paranoid Style of Programming
I have kind of a long standing rant-thing about static languages vs. dynamic languages, defensive programming, exceptions, and the best use of a developer's time and effort. It's been dormant for a while, but Sharad's comments on static v. dynamic, which I largely agree with, and this short piece by David McLaughlin on defensive programming gave me an itchy ranting finger.
Way too many of these debates confuse "dynamic" and "weak" typing
Admittedly, this is less prevalent now that I see less Perl floating around. There are two separate distinctions in typing systems. Static vs. dynamic is one, a static type system requires that each variable be explicitly declared and associated with a type, while dynamic typing allows variables to be created willy-nilly and assigned to any old value that happens to be passing by.
The strong/weak distinction is a little different, a value in a strong typing system will always be of the same type, and will not act as a different type without being explicitly converted. In contrast, Perl, which is the standard bearer for weak typing, values will automatically convert --
"3" + 3 is valid Perl (getting 6), but would be an error in Ruby or Python. Weirdly, in Java you'd get
"33", because string addition is treated as a special case.
I admit that unfortunately, I haven't had much experience with languages like Haskell that have smarter static type systems.
Anyway, a lot of pro-static typing arguments are arguments against weak typing, not arguments against dynamic typing.
Compilers and static typing protects against a class of errors that are relatively rare and easy to fix
This is my experience, anyway, especially in Java versus Ruby or Python. The compiler prevents me from assigning a value of the wrong type to a variable. Which is great, except that I've been working in dynamic languages for over a dozen years, during which I've learned three things.
- This particular mistake is actually kind of rare
- When this kind of mistake happens in development, it tends to fail quickly and loudly.
- Even the most rudimentary test plan will catch mistyping errors before they get into production
I'm pretty sure that, in the last dozen years, I can count on one hand the number of bugs in my dynamic programming that made it to production and would have been caught by a compiler.
I make all kinds of mistakes -- don't get me wrong -- but I tend not to make the kinds of mistakes that compilers would catch.
You could argue that I don't make these mistakes because I've been working in dynamic languages for so long. That helps, but it's also true that I didn't make these mistakes even when I first started.
It's a little glib, but the language I've worked in recently where I've had the most problems with mistyping is the allegedly static typed Java -- with Lists and Hashes that treat everything as an Object and require casts to do anything useful.
That said, weak typing is genuinely harder to work in
No typing rant would be complete without a gratuitous swipe at Perl, which I find a genuine pain in the neck to work with. Type errors in Perl do not fail loudly and quickly. Instead, since values easily change types, type errors tend to be subtle and manifest themselves far from the original error. They are much harder to track down.
The cost of working in a static language is largely hidden
Back in the early days of the Mac, Bruce Tognazzini used to write about user studies that continually found that a) most power users felt that keyboard shortcuts were faster than mousing, and b) whenever they actually timed it, the mouse was faster. Tog theorized that the difference in time -- essentially, the time it took to remember the keyboard shortcut -- was largely forgotten by the user.
Similarly, satisfying a static compiler takes a lot of boilerplate up front time that I think developers largely don't account for because it's largely separate from the interesting part of solving problems.
Still, static typing requires more code to be written by the programmer. Even if your IDE does some or all of that work, there's still more code to wade through when reading code. It's a fair amount of time, and that doesn't even count the increased cost of abstracting common behavior while still keeping the compiler happy. At the same time, the nominal savings of catching compiler errors is much easier to see, and I think can give a programmer in a static language the illusion that their compiler is doing more for them than it actually is.
Before putting in code to protect against exceptional conditions, ask the following questions:
Static typing is something of a specific case of defensive programming, I think. Certainly, the Java programs I read tend to have much more defensive programming clauses and the like littering them.
I've always felt, without a whole ton of actual evidence, that many programmers spend more time guarding against rare errors than is strictly necessary, especially early in development.
Ask yourself this:
- Is it likely that this potential error will occur before the impending heat death of the universe?
- If this potential error occurs, is it a sign that something more serious is going on, and that the system will have ground to a halt long before it gets to this puny little method.
There's a distinction here between genuine special cases and off the wall stuff. Often, having
nil as an input is a special case that should be dealt with, because it's reasonable to expect a
nil value. Sometimes, though, if a
nil ever were to get to a method, it would indicate that the application was basically completely screwed up, and in that case, writing code to guard against that has always struck me as a low prioritiy. Similarly, testing for, say, three kinds of database failure on every database save is a low priority. If the database is down, the application is going to fail all over the place anyway. (In Rails, it won't even complete the request).
Your time is a precious resource, your attention when reading code is a precious resource. Make sure that error handling is there because there's a legitimate chance that the condition will occur.
Defense versus Trust
That's not exactly defensive programming, and it's not exactly offensive programming. It's more trust-that-you-and-the-other-guy-will-mostly-get-it right programming.
I'm not saying that this method will catch all the bugs and errors that might happen -- it won't. But in the time that you otherwise would be writing bundles of error catching code, you'll be writing real code. The amount of extra code you'll write will more than offset the time you'll spend fixing unexpected bugs. In most cases, for most projects, that's a good trade.