We’ve all heard of defensive driving (and if you haven’t, you’re probably the one we are all defending against). However, as developers, we should also be familiar with defensive coding. Just as defensive driving can save you lots of money and keep you safe on the road, defensive coding can keep the code you write from crashing and burning as well.
Well the first thing you might ask yourself is “What exactly is defensive coding?”. Wikipedia defines it as:
A form of defensive design intended to ensure the continuing function of a piece of software in spite of unforeseeable usage of said software. The idea can be viewed as reducing or eliminating the prospect of Murphy’s Law having effect. Defensive programming techniques are used especially when a piece of software could be misused mischievously or inadvertently to catastrophic effect.
This is a good definition, but a better or maybe simpler definition might just be “developing a system that behaves in a predictable manner despite unexpected conditions or inputs.”
Defensive coding can generally be broken down into three main areas. These areas are:
- Clean Code
- Testable Code
Let’s start off by talking about number 1.
Clean code is very important for a few reasons. Mainly because it will improve comprehension, simplify maintenance, and reduce bugs.
Comprehension is a very important part of defensive coding. If it is clear what your code is doing or what its responsibilities are, then there is a much lower chance of it being used in the incorrect way by client code or applications. One of the best ways to make you code comprehensible is coding in a way that is very readable. This starts off by naming methods and arguments appropriately. Of course, this is easier said than done. The famous quote by Phil Karlton points out that:
There are only two hard things in Computer Science: cache invalidation and naming things.
Keeping your methods focused and specific will help you in naming them. Spend time in developing a good name for you methods and arguments that clearly defines its purpose so that the client of the API will understand how to call the code in question rather than abusing it.
Keeping your code clean and readable also helps code maintenance. I know there have been many times when I have developed a complex method and would come back to it week later and have to re-learn the whole process of what I was actually doing.
Some people might say “Comment your code better!”, but I typically view comments that tell what the code is doing as a “code smell” and see it as an opportunity to refactor the code into a more readable solution instead (don’t get me wrong, comments aren’t always a code smell. If a comment explains why a particular action was done, especially an unexpected one, I believe it to be a good use for code comments). Having a clear understanding of your code will simplify maintenance and prevent unexpected bugs from popping up from someone that didn’t fully comprehend what the code was actually doing.
Well I don’t think it would be possible to talk about defensive coding and not mention unit testing. Unit testing is a automated way to test small “units” of your code that will provide your code with improved overall quality and confirm that code maintenance and changes did not break previous functionality.
The primary goal of unit testing is to take the smallest piece of testable software in the application, isolate it from the remainder of the code, and determine whether it behaves exactly as you expect. Each unit is tested separately before integrating them into modules to test the interfaces between modules. Unit testing has proven its value in that a large percentage of defects are identified during its use.
One great way to make sure you are developing testable code is to follow the SOLID principles of software development. Now the SOLID principles are a topic of their own and if you aren’t familiar with them I highly suggest your read this article by Shivprasad Koirala. However, I do want to cover what I perceive as the two most important SOLID principles in regards to testability. These are the ‘S’ and the ‘D’ in SOLID. The ‘S’ stands for Single Responsibility and ‘D’ stands for Dependency Inversion.
Single Responsibility: Your classes should really only be doing one thing, and one thing only. A class that creates a file, parses some input, and writes it to the file is already doing three things. If your class only does one thing, you know exactly what to expect of it, and designing the test cases for that should be fairly easy.
Dependency Inversion/Injection (DI): This gives you control of the testing environment. Instead of creating foreign objects inside your code, you inject it through the class constructor or the method call. When unit testing, you simply replace real classes by stubs or mocks, that you control entirely.
Validating (or sanitizing) user input is to ensure that input is safe prior to use.
The most secure way to do this is to Terminate on incorrect input and use a Whitelist/Blacklist strategy to determine if execution should continue or not.
In terms of method arguments, there are often known good inputs — input the developer is completely certain is safe. There are also known bad characters; input the developer is certain is unsafe (can cause Code injection etc.). Based on this, two different approaches to how input should be managed exists:
- Whitelist (known goods). A Whitelist is a list of “known good inputs”. A Whitelist is basically a list which says “A, B and C is good (and everything else is bad)”.
- Blacklist (known bads). A Blacklist is a list of “known bad inputs”. A Blacklist is basically a list which says “A, B and C is bad (and everything else is good)”.
Security professionals tend to prefer Whitelists, because Blacklists may accidentally treat bad input as safe. However, in some cases a whitelist solution may not be easily implemented.
Handling bad input in a predictable manner can better allow client applications to understand what exactly is bad input and what is good input and how to satisfy the conditions to achieve the desired result. This is typically done through an exception handling and propagation strategy, of which I will not go into detail here.
Following these three techniques can greatly reduce the amount of bugs in your code and will especially help with the overall quality of your code. Remember, just as defensive driving can’t prevent you from all accidents, defensive coding won’t protect you from all bugs. It will, however, certainly help you avoid being the cause or reason for many of them and I know your fellow developers will most definitely appreciate that :).