What are Guards?
Guard, is a check of integrity preconditions used to avoid errors during execution. The main purpose and use of this is to avoid unnecessary nested branching conditions and give meaningful errors and hence to simplify the code.
In simple language we can say that the code that validates your method's input is called a Guard clause. It makes your code more understandable and it protects you from bugs and unexpected behaviors.
The if block act as a guard clause by protecting the GetStudent method against any null _student arguments. But the main drawback is even though we have null check over here but to reuse this across the code we need to have multiple null checks in order to escape the crash or unexpected behavior.
public Students GetStudents(StudentModel _students) {
if (_strudents != null) {
// Write the code.
} else {
Console.WriteLine("Student model do not contain any data");
}
}
How to handle guard clauses exceptions?
"Guard clauses exceptions should never be caught."
This means that most of the time, we should let the caller hit those exceptions as most of the time, guard clauses will guard against scenarios that should never happen like null arguments. We all must have encountered the time when the bug is caused because of the null reference exception. So should we catch a bug and take the chance of never discover it? The answer is No! Instead, we should let the application fail immediately so that we can discover the bug before deploying it to production during the development process.
But what if we have preconditions that don't rely on bugs? What if we have preconditions that could occur sometimes like business logic preconditions? The solution is to expose your guard clauses!
How to use Guard clause?
We can write our own defensive code to use the guards or one library which is simple to understand and solves this problem is Dawn.Guard library.
The below example is one step solution of getting rid of nested if conditions. So let's say if the condition is not fulfilled the code will throw exception so that the developer can get to know the error and can fix it. This library is not limited to null check. There are guards like Equality guards, Boolean guards, Comparison guards, etc. which makes our task easy when it comes to validation conditions.
public Students GetStudentsData(StudentsModel _students) {
_students = Guard.Argument(_students, nameof(_students)).NotNull().Value;
//This will let the user know if the _student is null before navigating to next step.
}
Let's take an example and explore more. Suppose we want to check if the Student's name is not null and length should not exceed more than 25 characters.
Code without using Dawn.Guards Library
public Student(string studentName) {
if (studentName == null) throw new ArgumentNullException(nameof(studentName), "studentName cannot be null.");
if (studentName.Length > 25) throw new ArgumentException("studentName cannot exceed more than 25 characters.", nameof(studentName));
}
That if expression is a Guard Clause. However, we can do better than that, since repeating this code over and over in all of our methods is a bad practice.
Code using Guards(Dawn.Guards library)
Guard needs to know the argument's value to test it against preconditions and its name to include in a potential exception. There are three ways to initialize a guarded argument,
// First, by specifying the argument value and name separately.
Guard.Argument(arg, nameof(arg));
// Second, omitting the optional argument name.
Guard.Argument(arg);
// Third, creating a MemberExpression via a lambda expression.
Guard.Argument(() => arg);
- The first sample initializes a guarded argument by specifying both the argument's value and name.
- The second sample does not specify the argument name. This is allowed but not recommended since the argument name proves a valuable piece of information when you try to identify the cause of an error from logs or crash dumps.
- The third sample initializes a
MemberExpression
that provides both the argument's value and name. Although compiling an expression tree is an expensive operation, it is a convenient alternative that can be used in applications that are not performance-critical.
With Guard, if you want to guard an argument against null or max length, you just write NotNull and MaxLength
and that's it.
public Student(string StudentName) {
Guard.Argument(StudentName, nameof(StudentName)).NotNull().MaxLength(25);
}
If the argument is passed null, you'll get an ArgumentNullException
thrown with the correct parameter name and a clear error message out of the box. Similarly, it will throw an exception if the criteria for max length is not met. By pulling out all of the validation checks from throughout the method we can remove a number of nested conditionals. This definitely improves the readability of the code and makes the code easier to maintain in the future.
The above example illustrates the use of the Dawn.Guard library. We can extend the guard class to handle our own custom exceptions as well.
Exception Messages
Guard creates a meaningful exception message that contains the argument name and a description specific to the validation when a precondition can't be satisfied. Additionally, every validation in Guard accepts an optional parameter letting the user specify a custom error message.
// Throws an ArgumentException if the arg is not null.
Guard.Argument(() => arg).Null(a => "The argument must be null but it is: " + a);
// Throws an ArgumentNullException if the arg is null.
Guard.Argument(() => arg).NotNull("The argument cannot be null.");
Summary
In this article, we have seen how guards clause can help us to discover the bugs before deploying in production and to make our code more readable. We have also learned about Dawn.Guard library and how to use it.
That's all from my side. Happy coding!!!