Defensive programming, what is it? – It’s quite simple really, defensive programming is the practice of engineering your code in such a way where you handle all potential states. This includes unexpected states, this includes invalid states, valid states, totally unexpected states & everything in between.
However, if you were to quickly Google “defensive programming“, it’s likely that you’ll come across some code where there’s an array of if statements, if they hold true, they’ll throw a run time exception. This isn’t by any means a bad practice as such, at the very least you’re still handling all potential states. This is good, it’s certainly better than allowing your code base to become riddled with bugs, even if it is just trying to run some method on a string that’s null. Instantly, with a null check in place, you’ve prevented a null pointer exception from being thrown, good job! 👍
However, when you begin to think of the many disciplines that we engineers need to be aware of, you begin to realise that these more entry level practices are actually bad, in some respect. Now, before you jump down my throat, let me explain, as you begin to think more & more about your code base, it dawns upon you.
Throwing exceptions for invalid states defies the idea of encapsulation, the code consuming your solution, the solution that throws a set of exceptions needs to be aware of all the exceptions that can be thrown. Before I go far down some rabbit hole, let me provide an example:
In this example, you can make a reasonable assumption that this snippet is trying to run user authentication, authorisation & generating a JWT all in one unit of work, not bad! Or is it? 🤔
The business logic itself makes perfect sense to me, if you wanted to allow some admin staff to log in to some admin portal, why wouldn’t you want to conduct some process like the above?
The issue I have here is that you may want to use this exact logic or similar logic throughout the application several times, meaning that you’d need to introduce many branches in each use case. You may even want to use this kinda mechanism throughout several applications, in any case, this increases the complexity of testing in my eyes. Since you need to cover all the potential branches in each case, it makes your life harder when trying to keep up with the governance requirements.
Rather than throwing many exceptions, you could reconsider the implantation without having to implement a totally new solution. What I propose is that we begin to consider an implementation that’s more event oriented, such as this:
In this scenario, it ensures that the consuming code base is easy to debug, since there are domain specific methods such as
whenAuthenticationFails, the code base is also a lot more readable. Not only is this solution more readable, but you still have the benefit of executing some unit of work in the scenario of a specific exception occurring.
In my eyes, this increases the level of encapsulation, since the consuming code has specified how it would like to handle specific states or scenarios. But better again, you could make the consuming code base totally oblivious to these exceptions, provided you have the want or need for it to fail silently. An alternative solution is to return some state object(s) in each case, where each relevant object begins as a null object.
To progress with this idea, when you generate the JWT, you could then return some
InvalidJWTState object. This object could have a set of methods or properties where it implements the minimal functionality necessary for the consuming code base.
A significant benefit to such a solution is that the client code doesn’t even have to worry about null values. This further makes the consuming code base easier to manage & maintain. As Zoran Horat has said:
When you have to defend, you have already lost.
Personally, I couldn’t word it better myself. I love this phrase, the moment you have to introduce reactive defences, proactive defences, or any other kind of defence for that matter. I believe that you’ve essentially failed to implement secure, defensive code. Don’t get me wrong, I believe that it’s fine to throw exceptions, provided that they’re handled internally.
Provided you do this, your consuming code base will be as care free as it possibly can be. Even in cases where there’s a fundamental failure, such as a configuration value is missing or incorrect, this should be handled internally, the consuming code should only be aware upon request. With the ability to be aware of failures upon request such as my second coded example above, it ensures that the application isn’t silently failing, but it also ensures that you’re ensuring that the consuming code base won’t experience any unknown scenarios.
I guess, to an extent, this approach to writing code goes hand in hand with concepts such as chaos engineering, where you’re assuming that your implementation will explode unexpectedly. The fundamental ideas are the same, where you design your solution to be as fault tolerant as possible. In the real world, this is essential, as things go wrong all the times, things that are beyond the scope of our control, even if it’s something as unlikely as your database server suddenly exploding into flames. 🔥
Remember, you should always write your code for the person who’s going to be maintaining your code. You should also assume that the person maintaining your code is a violent psychopath who knows where you live. 🔪