The Importance of Failing Fast
Writing software is hard. If the guy next to you hasn’t created ten new bugs, management hasn’t added 10 new features, the customer hasn’t tightened the due date by a week, and your technical lead hasn’t passed a new programming commandment (“Thou shalt not use identifiers with fewer than 4 characters!”) – all by noon – then it’s a good day in Developerstan.
Writing software is hard enough just dealing with the practical, every-day, mundane bits without even having to think about the difficulty added by the actual technical work itself. To write really good, clean, correct code, the programmer has to have a million tiny things in his head – “What does
Integer.parseInt() throw if it fails?” “Which directory is
ServletContext.getResourceAsStream() relative to?” “Which language is this product in again?” – before he can even write a single line of code. And the really good programmer has to check all his assumptions and return codes before he can even get down to business.
Wouldn’t it be nice if your tools yelled at you when you miss something?
This is why things like exceptions are so good for productivity. Whereas C functions only tell you they failed by returning a value, which you probably ignore a lot anyway (when was the last time you checked the return code for
printf()?), languages like Java and Python and C++ explode in a massive fireball of fail when something goes wrong. This means the really good programmer doesn’t have to check every. single. return. code. Instead, he can just say “if anything in here fails, just do this.” Much easier.
So, anything that makes detecting and managing failure faster and easier is A Good Thing. And, as a corollary, anything that makes detecting and managing failure harder is A Bad Thing. But come on, man, this is 2010. Surely the modern languages we use to power the products around us are marvels of engineering and convenience for programmers? Right? And certainly nothing’s running around hiding errors from programmers.
The Importance of Failing Fast
Because finding errors is hard enough on programmers as it is, a good toolset fails fast. That is, the best tools kick and scream and yell at the programmer when he’s made a mistake, preferably as close to the actual programming error as possible so that the error is easy to track down.
A huge factor in how fast the code of a program fails is the programming language it uses. Language features like assert, exceptions, baked-in synchronization, and so on make failing fast easy and automatic, so it happens more.
Here’s a spectrum of how fast a few popular languages fail:
NOTE:Some great issues about complexity and disabling exceptions in C++ were brought up in the comments, and how these “features” mean that C++ probably (at best) ties with C. these are great points, and i agree.
On the two ends of the spectrum we have Java and Assembler.
Java yells and screams at the drop of a hat. Java programmers are so used to seeing things like
SocketTimeOutexceptions that people have made up drinking games around them. Java fails so fast that a lot of programs fail before they even run because of Java’s bondage type system. (There’s probably a joke in there somewhere.) Java programmers can generally feel pretty confident that their language will tell them when
something anything has gone wrong.
Assembler, on the other hand, fails very slowly. Assembler has no type system to speak of, no logging, no exceptions, and generally nothing to help a programmer find bugs. When a user hits a bug in an assembly program, there’s nothing in the language itself that helps a programmer understand it and track it down. He’s totally on his own.
C and his kooky younger brother C++ are kind of in the middle. With C, you have to check status codes, and you can easily have runaway memory bugs that are extremely difficult to track down, but C at least has the decency to crash when things go south.
Dynamic languages like Ruby and Python end up being kind of a mixed bag. Scripting languages are fail-fast at runtime, but have no safeguards at all before then – frequently not even a simple parsing test – so bugs can pop up seemingly out of nowhere. Ultimately, dynamic languages rely on their flexibility for robustness, which kind of works but kind of doesn’t.
Dammit, Jones, What the Hell Are Knoll Pointers?!
If you’re paying really close attention, you’ve noticed that we haven’t talked about Objective C yet. Don’t worry, I haven’t forgotten. I’ve just been coming up with new four-letter words to use.
The worst kind of failure by far is the kind that doesn’t look like a failure. The languages we’ve talked about so far have had varying ways of reporting errors, with some being faster than others, but at least all of them reported errors when they happened. All languages do that, right?
It turns out: No.
Consider the following snippet of Java:
In Java, what happens if
null? The program throws a
NullPointerException with a stack trace, telling you exactly where the
null pointer was encountered. This is The Right Thing. Java gets a gold star.
Now, what happens if we do the same thing in Objective C:
Bar *bar=[[self foo] bar];
In Objective C, if
nil (Objective-C’s version of
null), then surely the program throws an exception, right? Wrong. If foo returns nil, the variable bar contains
nil after the statement executes. That’s right, ladies and gentlemen: Objective C’s
null value can be dereferenced, and happily returns itself when that happens.
This was obviously an explicit design choice. You just know some manager somewhere said to an employee “Gosh, Jones, programs sure do crash a lot. It’s because of “knoll” pointers? What the hell are those? Dammit, Jones, make sure our programs don’t have any knoll pointers!” So Jones decided that he’d make dereferencing
null pointers fail silently instead of crash, call
nil — just in case, and declare victory. (I’m sure Jones got a pat on the back and a promotion to management.)
So, purely hypothetically – it’s not like this actually happened to me when I was working on an iPhone app or anything – let’s say you’ve got a method that takes one object, does a bunch of stuff to it, and then returns another object. It works like a charm. Then you add another call somewhere else in the program, and BAM, this call returns
nil. No problem, we’ll just check the stack trace from our exception… Oh wait, dereferencing
nil fails silently. Well, the bug must have been in the last assignment, because otherwise our program would have crashed… Oh wait, dereferencing
nil fails silently. Well, we’ll just check the method, there can’t be that many ways we could have gotten
nil… Oh wait, since
nil propagates, it could from any one of 50 of these calls.
So, What Have We Learned?
Programming is hard. Programmers have enough work to do without having to chase failures when the failures aren’t hiding from them. Failing silently is a great example of a hiding failure. So, failing silently is bad. Really bad. Take a minute to stop and think about how bad failing silently is. Mayo on a burger you ordered dry? Nope, much worse than that. Box of kittens hit by a school bus? Now we’re getting there. Palin elected president in 2012? Whoa. Not quite that bad.
EDIT: Some interesting issues about
nil and the null object pattern were brought up on reddit and here in the comments. Personally, I don’t buy this pattern as A Good Thing, and the ultimately small gain you get from applying this pattern doesn’t justify the cost. I explain in more depth here and here.
EDIT: It has also been pointed out that I’m a little snarky in this post. Heh. Yes, i definitely am. But the post is tagged rant for a reason, for what that’s worth. :)
EDIT: A comment below tells me that yes, this feature is annoying at first, but the annoyance is likely to pass, and be replaced by better, more concise code. I sure hope so. Perhaps I’m being heavy-handed here, but in my mind this feature is still very tricky, especially for new Objective-C developers.