Ferrari versus Land Rover

UPDATED: Len points out in a comment to his article that we don't actually have any disagreement because he was talking about C-style asserts and I'm focusing on the .NET equivalent. He thinks that my description of how .NET handles assertions actually sounds like the way that C++ "assertions" should work. And of course, I had to write this update because I don't have comments enabled on my blog yet.  

Len Holgate recently wrote an article with the headline "Assert is evil". In fact the article itself is rather more moderate than its headline, but I still found stuff with which I could disagree. Of course, I need to be careful about too much disagreement as we're actually geographical neighbours - I think he lives just down the road, although we've never met. But to paraphrase the meat of his article, he seems to be claiming that assertions (implemented with Debug.Assert and Trace.Assert in the .NET Framework) are a bad thing because they're often misused by developers. This sounds wrong to me - assertions are neutral, and can be used in both good and bad ways.  

Everybody has a slightly different definition of what constitutes an assertion, and these differences give rise to some of the controversy in this area. My own definition is that an assertion is a safety check within your code, above and beyond defensive programming, designed to check for a condition that theoretically shouldn't happen. Used properly, assertions are invaluable as a tool for the automatic detection of bugs in your code.

Here's a list of checks, some of which could be handled with defensive programming and some with an assertion.

  1. Validation of a parameter (argument) passed by code external to your application or component.
  2. Validation of a parameter passed by code internal to your application.
  3. Validation of a value returned by a function external to your application.
  4. Validation of a value returned by a function internal to your application.
  5. Validation of the consistency of your component's internal state.
  6. Validation of an assumption about the environment external to your application, for example:
    • A network folder that you've used before still exists.
    • A printer that you're trying to print to still exists.
  7. Validation of an assumption about the environment internal to your application, for example:
    • Your application's license certificate exists and is not corrupted.
    • Your application has a strong name signature.

As a general rule, I use an assertion to handle any check that's internal to my application - in the above list, that's items 2, 4, 5, 7. I use defensive programming for any check that's external to my application - that's items 1, 3, 6. This isn't a fixed rule for me, but serves as a good heuristic.

Len's first point is that many developers mistakenly create assertions that run only during development and testing, but are conditionally compiled out of release builds. Here I agree with him completely. The reasons for an assertion don't disappear just because an app moves into production. Of course, during development and testing, an assertion is reported directly to developer or tester. You can't do this in production, as an assertion failure won't make any sense to the customer. So you need to redirect production assertion failures to a log file rather than reporting them directly to the end-user. Fortunately, .NET allows you to specify a setting in your application's configuration file that does this redirection automatically.

<configuration>
    <system.diagnostics>
        <assert assertuienabled = "false" logfilename = "C:\Logfile.txt" />
    </system.diagnostics>
</configuration>

Len's second argument is that the use of assertions tends to encourage poor design. I agree that any developer who simply adds assertions at random is going to run into problems. But that's surely a problem between keyboard and chair, not the fault of the tool. In other blog entries, Len is gung-ho about giving developers powerful language tools and letting them just get on with the job. I'm not clear on why he thinks this situation is an exception to his usual position. 

His third point is that assertions are often hard to test - specifically, "it's complicated because assert ends the program". Of course, there's absolutely no reason why this has to be so. There are basically two approaches to this, which can be summarised as the "Ferrari" viewpoint and the "Land Rover" viewpoint.

With a Ferrari, the slightest problem or damage results in an immediate (and usually outrageously expensive) garage stop. A Ferrari is a supercar, with a temperament to match. You just don't take any liberties with it.

A Land Rover, on the other hand, can take a tremendous amount of damage and just keep on rolling. It's basically designed to survive anything short of a direct nuclear hit.

So when you're designing your application, you need to decide whether it's going to behave like a Ferrari or a Land Rover. Is it going to fall over in the event of anything unexpected, or is it going to keep going on and on like the Energiser Bunny?

In reality, your application is probably going to fall somewhere between these two ends of the spectrum. It's interesting that the majority of C and C++ developers lean towards the Ferrari approach, whereas the majority of VB and Java programmers favour the Land Rover approach. C# developers seem to position themselves midway between the two camps.

My approach is that, except for life-critical applications such as medical machines, the Land Rover approach is normally best. And this is why I don't find assertions hard to test - in my apps, it's rare for an assertion to kill my program. Instead I throw an AssertionException (derived from ApplicationException) which is caught by my top-level unhandled exception handler. In effect, this means that the current task is killed, but not the application as a whole. Of course, this also means that I need to be very careful about cleaning up safely after every assertion, without causing any unwanted side-effects.

Len's espousal of the Ferrari approach is clear in the last paragraph of his article. He suggests that you can replace almost every assertion failure with an exception throw, and then concludes by saying "...that you, the programmer, don't usually need to worry about the stuff that can't fail". Well....that's true only if you want your application to crash in the presence of what would have been an assertion failure.

If you don't use assertions in your own code, take some time to re-visit that decision, and also give some thought as to whether you want your application to behave like a Ferrari or a Land Rover.