Key Takeaways
- Defensive programming, as defined in this piece, involves programming with the intention to anticipate likely failure points and circumventing these problems before they occur. This should not be viewed as a way to avoid test-driven development or a way to merely compensate for failures.
- The ‘Fail Fast, and Loudly’ approach is an effective method for defensive programming. This means that when an error occurs, it should do so as early as possible and alert the relevant party. This is especially useful when dealing with user input or input arriving from outside the script, module, or your system via an API.
- Input validation, preventing accidental assignment in comparisons, dealing with try/catch and exceptions, and transactions are all critical aspects of defensive programming. Each of these methods has its uses and situations where they may not be used, but incorporating these concepts into daily development regimes can enhance efficiency and robustness of applications.
The general definition of defensive programming (as seen in Part 1, an overview piece on Defensive Programming in PHP) being used here is thus:
Defensive programming, simply put, is programming with the intent to anticipate likely failure points. The goal is to circumvent those likely problems before they occur.
This definition is a wide one. Many people argue against defensive programming, but this is often because of the types of methods they have seen espoused by some as defensive programming. Defensive programming should not be viewed as a way to avoid test driven development or as a way to simply compensate for failures and move on.
“Fail Fast” should not be seen as a counter to defensive programming. It all falls under the same umbrella. What are these methods, if not ways to anticipate that your program may fail, and either prevent those, or else ways in which to handle those failures appropriately?
Fail Fast, and Loudly
Simply put, failing fast and loudly means that when an error occurs, it will do so as early as possible, and alert whomever it should alert, as opposed to silently continuing on in an error state that may cause more issues. Here is an excellent article on fail-fast style development. The premise of fail-fast methodology is to fail when acquiring input that might later compromise the program (or, more generally, to enter a failure state as soon as any problems could possibly be detected, rather than allowing bad data to travel and a program to run un-checked, or worse, bad data to be stored somewhere). This is most useful when dealing with user input, or dealing with input that is arriving from outside the script, module, or even out of your system via an API. An example of where this could be employed would be a check for invalid values or data types passed into functions.
function thisTestFunction($testInt)
{
if(!is_int($testInt)){
// Do something here
}
}
One mistake that some programmers make with fail-fast methodology is to simply throw Exceptions and errors out to the user without making proper provisions for handling them. You don’t want normal users to be worried or confused by your error messaging. And more importantly, you don’t want users with malicious intent learning things from the information displayed to them. Display a helpful message to the user, log your errors, and perform any other tasks that need to be a result of that exception. You don’t want to just fail fast, you also want to be loud (know there’s a problem, right away) and secure (don’t let your poor exception handling or complete lack thereof cause even more security issues).
Input Validation
There are many methods by which to validate user input safely.
Typecasting is an interesting way to “validate” user input. Sometimes it goes a bit like this:
$member->property = (int) $_GET['property'];
Instead of using another method to avoid cross-site scripting attacks, the value is simply being captured, typecast, and assigned. This only works when you have an expected type, and any value of that type is safe (Otherwise, you’d need to also check for appropriate int values). The problem with this approach, that I see (in most situations) is that you aren’t really checking the input, you’re just forcing it to become what it should be. This could have unforeseen consequences. Instead, a better approach might be to check for appropriate values using filter_input()
. Here is a great article on that subject. Basically, that comes out like this:
$member->property = filter_input(INPUT_GET, 'property', FILTER_VALIDATE_INT);
if (false === $member->property) {
throw new Exception('Property was not an int');
}
There are many benefits to using the native filter_input
function in modern PHP, and you can read more about them in the aforementioned article or on php.net.
Preventing Accidental Assignment in Comparisons
This is an easy, and often noted, tenet of defensive programming. A simple change in the way that you do your comparisons can have great effects. Consider the following:
if($member->property == 12345){
// Do crazy cool stuff
} else {
// Don't do any fun stuff
}
This is a relatively normal comparison, yes? However, what happens if you accidentally use the “=” instead of “==” (or, in most cases, the even better “===”)? A simple slip of the finger on the keyboard? Absentmindedness, maybe? All of a sudden, your comparison now reads true, all of the time, in all cases. Unless you have a good IDE warning you about this, how long will it take you to find out? In some cases, this could be a silent error for some time. However, there’s an extremely simple way to prevent this:
if(12345 == $member->property){
// Do crazy cool stuff
} else {
// Don't do any fun stuff
}
Now, if you accidentally use one equals sign, the error will not be silent. Obviously, this may not occur often, it may be mitigated by your testing, and it’s not useful in all cases, especially when doing variable to variable comparisons. But it’s still not a bad idea if this tends to happen to you.
Dealing with Try/Catch and Exceptions
try/catch
statements are another hot topic among PHP developers. Let’s first get a quick look at what we’re talking about.
try {
if($member->property <= 0){
throw new Exception("Value must be 1 or greater");
}
// If no exception was thrown, this will be output.
echo "The value is acceptable";
} catch(Exception $e) {
echo 'Message: '.$e->getMessage();
}
A well known tool of defensive programming is the try/catch
statement and the Exception
class. These are excellent for catching and logging errors, when used correctly. A good programmer will employ try/catch
statements to anticipate possible errors or other situations that might disrupt the normal flow. When those Exceptions occur, they must be handled in an appropriate way. The user of the application, where required, should get a reasonable error message that is as useful as it can be without divulging sensitive information. The administrator(s) of the application should receive detailed alerts and/or logs. Exceptions that are mishandled, or ignored, ignore the “Fail Loudly” advice, and may allow the program to continue in essentially a silent error state for some time, which is not good for anyone involved.
For a bit more on exceptions in PHP, see here and here.
Transactions
Transactions are a feature of databases that allow queries to be grouped together, so that if one query fails, all of them fail. This is an implementation of ACID, about which you can read more here. The idea is that grouping multiple queries together into one process can sometimes be a safer and more stable solution, especially when the queries depend on each other. PHP developers often completely ignore transactions, or assume that they are unnecessary, but when interacting with databases, a little bit of defensive programming can go a long way. Transactions are discussed in more depth in this excellent post, but in a nutshell, transactions allow you to run your MySQL updates, and then check the results before actually committing the results. If you are using PDO (you should be), you may use PDO methods to begin a transaction, commit the results, as well as roll back. In addition to the aforementioned overview of transactions, delve into them further with this in-depth guide.
Here’s a quick example of what the use of transactions in PDO might look like:
try{
// Start the transaction
$db->beginTransaction();
// Perform one update
$statement1 = $db->prepare("QUERY LANGAUGE HERE");
$statement1->execute(array($a,$b));
// Perform another update
$statement2 = $db->prepare("MORE QUERY LANGUAGE HERE");
$statement2->execute(array($a,$b,$c));
// Commit the transaction
$db->commit();
} catch(PDOException $ex) {
// If an exception occurs, first, roll back updates
$db->rollBack();
// Then, in this block, further handle the exception
}
Conclusion
Again, these are just general tips. Obviously, each of them has their uses and each has notable situations where it would not be used. However, if you take concepts like these and work them into your daily development regimes, it can make you more efficient at what you do. And while this is generally a topic that is more applicable to developers who are currently learning PHP, it’s a good refresher on practices for everyone.
If only a single thing is taken away from this, especially by newer developers, it is that you should program defensively – plan for the possibility of mistakes and errors. Handle them appropriately. Don’t allow silent errors to progress. Fail fast. Test your code. By building robust applications that test for and resolve issues, and anticipate and handle future ones, you are making your applications more dependable, and hopefully assisting in the creation of a better user experience from behind the scenes.
Jeff works for a startup as a technical writer, does contract writing and web development, and loves tinkering with new projects and ideas. In addition to being glued to a computer for a good part of his day, Jeff is also a husband, father, tech nerd, book nerd, and gamer.