Weekly Dev Tips
Guard Clauses
Your methods should fail fast, if doing so can short-circuit their execution. Guard clauses are a programming technique that enables this behavior, resulting in smaller, simpler functions.
Show Notes / Transcript
Complexity in our code makes it harder for us and others to understand what the code is doing. The smallest unit of our code tends to be the function or method. You should be able to look at a given function and quickly determine what it's doing. This tends to be much easier if the function is small, well-named, and focused. One factor that's constantly working against you as you try to keep your functions manageable is conditional complexity (a code smell) - if and switch statements. When not properly managed, these two constructs can quickly cause functions to shift from simple and easily understood to long, obtuse, and scary. One way you can cut down on some of the complexity is through the use of guard clauses.
A guard clause is simply a check that immediately exits the function, either with a return statement or an exception. If you're used to writing code such that you check to ensure that everything is valid for the function to run, then you write the main function code, and then you write else statements to deal with error cases, this involves inverting your current workflow. The benefit is that your code will tend to be shorter and simpler, and less deeply indented.
Imagine you have a method that performs a subscribe operation, and takes in three objects: a user, a subscription, and a term. Naturally, you want to ensure that these objects are not null before you start working with them. One way to structure the method would be to first check if the user is not null. Then, inside this if statement, check if the subscription is not null. And in this statement, check if the term is not null. Now in this statement, we are in what is sometimes referred to as the "happy path" for the function. Assuming the values of these objects are otherwise valid, the expected work of the function can take place in this block. Each of the different cases of invalid inputs should result in an appropriate exception being thrown, though, and so these require else blocks. There will be an else block for the term != null check, another for the subscription != null check, and finally an else block for the user != null check. This results in a fair amount of plumbing code and complexity that adds to the noise of the function and obscures its true purpose - to subscribe a user to a subscription.
Refactor to reduce nesting and else statements
The first way to address this is to eliminate the need for the else clauses. You can do this by inverting the if checks and putting the exception throwing statements at the start of the function instead of at the end. The first thing you check is if the user is null. If it is, throw an exception. No need for an else clause and no need for a nested if statement. Move on to the next argument. Check it, throw if it's null. Do the same for the third parameter. When you're done, you have 3 simple if statements, none of which are nested, and no else clauses. The function now fails fast, and after the input checks, the happy path for the function is whatever remains.
Refactor to use Guard helper methods
Checking for null arguments is a common enough task in strongly typed programs that you can probably encapsulate it in its own helper function. I use a common static class I call Guard which provides static helper methods for common scenarios. For instance, in the example I just described, I'm going to want to throw an ArgumentNullException with the name of whichever argument was null in each of the three possible cases. Thus, it's very easy to write a method I call AgainstNull that simply takes in the argument and the argument's name, and throws an ArgumentNullException if the argument is null. This exception will bubble up and out of the calling method. When you implement this in the scenario I described, you end up with code that reads like this:
Guard.AgainstNull(user, nameof(user));
Guard.AgainstNull(subscription, nameof(subscription));
Guard.AgainstNull(term, nameof(term));
You can add additional Guard helper methods for any other common cases you need to check for, such as empty strings, negative numbers, invalid enum values, etc.
If statements can take over your functions if you're not careful, making them much harder to understand and thus much harder to maintain. Cyclomatic complexity refers to the total number of paths through a given function, and should be kept in the low single digits whenever possible. Using guard clauses is one simple way to tame complexity in your functions and keep them smaller, simpler, and easier to maintain.