Most programs are not written by a single programmer, and so defensive programming also means defending your code against other people's misuse of it. In an ideal world it should be obvious to others how to use the code I've written and this reduces the need for other forms of defence. However even where other people do not find the code obvious to use we should try to make it easy for them to understand what they have done wrong and how to get it right. This ideal seems can be quite hard to achieve in practice, but it is worth reminding ourselves of it from time to time!
Once written our programs will, we hope, be used. So we need to be defensive against users, possibly untrained and often not terribly computer literate, whose understanding of the purpose and use of our programs might be rather different from our own. Additionally, the program will be running on another person's machine and this could be a machine with a very different hardware configuration, resources, software versions, etc.
Again we hope our programs will continue to be used, perhaps for some to come. So we have to prepare for change and therefore another part of defensive programming is trying to make our programs safe against possible future changes; but this is hard. "It's tough to make predictions, especially about the future." (Yogi Berra.) A simple example from the world of SQL is avoiding the use of 'SELECT * FROM ...' as this usually breaks when the database schema changes.
Finally one of the ever present needs in recent times is defence against malicious crackers, particularly but not exclusively on the Internet. It is part of being a computing professional in this day and age to consider the security requirements of every program that we write and then to make informed judgments about the correct level of security audit required.
In practice however not all problems can be prevented; in which case defensive programming means trying to make any problems that do occur localised and of low cost to the users of the program. A less defensive approach to the plug and socket in the previous paragraph is to protect the user (and the equipment) from any fatal consequences of inserting the plug incorrectly. As an example from the world of IT, I used a full screen editor in the 80s that never ever lost any work done in an edit session (except for the actual screenful being typed in) even when the mainframe we were using crashed. That was a defensively written program - and some modern word processors could do with emulating this behaviour!
A secondary aim is to try and make any problems that do occur as easy to resolve as possible. When a problem does occur defensive programming tries to ensure that enough information is collected and logged when unexpected situations are encountered so that the actual problem can quickly be identified and resolved. For example, this may mean writing code that stops the program quickly on an error rather than attempting to carry on with bad data.
If you are an active programmer consider the current programs you are working on. With the possible exception of Tex [4] all substantial programs contain bugs so the chances are that you are writing - and will ship - buggy code. The defensive programmer's approach is to try and ensure that the bugs do as little damage as possible and that they can be reported and resolved rapidly.
However, even when we recognise the need for it, there are several difficulties with writing defensive code. First of all, it is hard to write defensively since, almost by definition, you are trying to face the unexpected. One of the things that comes with improved levels of skill is better awareness of the sorts of things that can go wrong with programs and the techniques that can be used to obviate them. Every bug you find gives you an opportunity to improve your awareness of similar potential bugs in the future.
Secondly, many of the techniques that make programs more defensive add additional code (not all do - in particular defensive design decisions may have no impact on eventual code size). This extra code takes time to write and may have a detrimental effect on performance. However there are some factors standing against these downsides. Firstly, as I'm sure most readers of CVu already believe, fixing problems usually becomes more expensive the later in the development process that they are discovered and so the cost of writing this extra code may well be recouped in the reduction of time spent fixing bugs later on. Secondly it is almost always better to get the right answer slowly than the wrong answer quickly; as most commentators on optimisation point out it is generally easier to make a slow program faster than it is to make an incorrect program correct.
A final reason may be lack of good examples. Have you noticed how many times in articles about computer programming the code shown comes with the comment "Error handling omitted for clarity"? Although this may be a true excuse the consequence is that we rarely see good examples of defensive code. In fact, I suppose you could say we see more of the opposite of 'defensive' code - 'offensive' code!
COMPUTER SCIENTISTS hunt elephants by exercising Algorithm A:
This amusing example makes a serious point about writing code that copes with the unexpected. The experienced programmer in the story has already faced the possibility that his search finds no elephants before even starting the search! As we write code, we should be conscious of the assumptions that we are making (examples might be 'this file exists' or 'this security permission is held by this user') and make a decision about how to cope when our assumption is not valid before we even execute the program. So for example a parallel to placing an elephant in Cairo might be providing a default setting if a configuration file is unreadable.
Another facet of this preparing for the worst comes into play when writing code for other people to use. We should ask ourselves how an interface might be abused (either accidentally or maliciously) and how we might prevent such abuse. This might be as simple as changing the arguments to a method call. For example Herb Sutter recently pointed out that the C++ standard library call std::transform takes three iterators and so it is all too easy to invoke it wrongly like this:
But if we had made the user write:
transform( in.begin(), in2.begin(), in2.end(), out ); // 1: oops
then the possibility of making a mistake would be drastically reduced as the function arguments themselves make the use of call clearer.
transform( range(in.begin(), in.end()), in2.begin(), out ); // 2: right
A bigger problem with some interfaces is an implicit ordering of method calls that seemed obvious to the writer but is less obvious to the user. For example in objects with state - such as TCP sockets - it is often not documented which actions are valid in which state(s). To make things harder any restrictions are often not enforced and method calls may just fail silently if they are made when the object is in the incorrect state.
We should reflect on our own bug writing technique. What sort of bugs do we ourselves write most often? If we can identify something we get wrong habitually we can then look at improving our technique to try and prevent such cases in the future. For example, strcpy in 'C' often causes people problems with buffer overruns so using alternatives, whether strncpy or std::string in C++, can prevent problems before they occur.
When we try writing defensively part of the work is examining what should be checked and deciding how certain the check must be. For example, if we are writing code that passes input from the user into a database query we must be aware of the possibility of 'hijacking' the query by embedding escape characters in the input field. This might be either accidental or malicious and our judgement of the degree of checking required must take account the risks involved: a public Web site usually needs better checking than an intranet-based team resource.