This story is a reflection about how I used to follow certain patterns without giving them any thought. It starts with a justification, followed by a step-by-step introduction to what the new approach looks like and ends with a conclusion.
During my apprenticeship and studies, the Flow Control concept has been hammered into my head by my peers and superiors. I remember them saying things like goto statements are evil, use guard clauses, don’t use exceptions, use md5 + salt to hash your password, you name it. The answer to my questions concerning the previously mentioned statements was always because it is best practice. At that time, I was using most of my mental capacity to solve the problems my code was causing. They consisted mainly of Stackoverflow Exceptions, Out of memory Exceptions, code not compiling, and don’t get me started on the Cookies and Cross-Origin problems I faced. Not using goto statements and introducing guard clauses was the least of my concerns.
Different types of validity
In the course of this adventure, I use the word formal validity a lot. What I mean is that a provided argument in a function conforms with certain rules. For instance, data can be checked if a property is missing or if a value is within certain range boundaries.
I also use the word contextual validity. By which I mean that data is also valid in an uncertain context. For example, I can try and update a formally valid record in the database. If no record with the provided identifier exists, the write operation will fail. The object is formally valid but is contextually invalid.
Changing my approach
Now, after years of writing code, I gained further understanding. I have more time to focus on code readability and view code on a higher abstraction level. One of the things I notice is that I am so used to writing guard clauses my whole codebase is littered with them.
Other than the code enforcement rules and the haunting whispers of my former peers, I have no idea why I should write code in this manner. I have become a brainless coding monkey in this aspect.
Of course, I understand what the guard clauses are doing. They prevent a possible null exception when using user or address and make sure that address is formally valid.
But is this the right place to do this kind of validation?
I am also repeating the user check in the update method, which violates the DRY rule.
Let’s put the previous code into context.
For example, the HTTP API, where the received JSON object is parsed and assigned to an object. But only if the data is formally valid, or else it throws an exception. Every function expecting an object of that type can assume that it is formally valid and doesn’t need to validate it.
You might think this is dangerous. What if another developer uses one of your methods and receives a null exception? Well, then I hope they will do the same things as they would when receiving an ArgumentNotNull exception → Change the code and make it work.
In the system’s entry point, I parse and validate the incoming data once and throw an exception if it is false. Does this mean I have to litter my code base with try-catch blocks? No, I can introduce an exception middleware with a defined exception hierarchy. Then based on what Exception you catch, you can create the appropriate ActionResult.
What about failed context validity
It is to be expected that API consumers provide you with data that is contextually invalid.
How do I handle an update request with an invalid identifier?
Well, I need to make my intention clear in my method signature. One widely accepted way of doing so is to use a result pattern. Introducing this concept greatly increases readability.
The latter communicates that the method can succeed or fail. In comparison, the first method looks like it will always return a user.
Following so called best practises brainlessly will most likely litter your code base. Take the time to think about why you should do something and don’t do it just because your peers and superiors say it is best practice. In my case I noticed that guard clauses don’t need to be used everywhere.