by Zoran Horvat
Guard clauses are important part of design. All public methods of all publicly available classes must be protected from invalid input. Otherwise, method execution could be erratic and lead to inconsistent system state.
Take a look at the implementation of domain services in the e-commerce application:
namespace Store.Domain.Implementation
{
public class DomainServices: IDomainServices
{
private readonly IUserRepository userRepository;
private readonly IProductRepository productRepository;
private readonly IAccountRepository accountRepository;
public DomainServices(IUserRepository userRepository,
IProductRepository productRepository,
IAccountRepository accountRepository)
{
this.userRepository = userRepository;
this.productRepository = productRepository;
this.accountRepository = accountRepository;
}
public IRegisteredUser RegisterUser(string userName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("User name must be non-empty.", "userName");
User user = new User(userName);
this.userRepository.Add(user);
Account account = new Account(user);
this.accountRepository.Add(account);
return user;
}
public IRegisteredUser RegisterUser(string userName, string referrerName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("User name must be non-empty.", "userName");
if (string.IsNullOrEmpty(referrerName))
throw new ArgumentException("Referrer name must be non-empty.", "referrerName");
IRegisteredUser user = this.RegisterUser(userName);
this.userRepository
.TryFind(referrerName)
.Each(referrer => user.SetReferrer(referrer));
return user;
}
public bool VerifyCredentials(string userName)
{
if (userName == null)
throw new ArgumentNullException("usernName");
return this.userRepository.TryFind(userName).Any();
}
public IReceiptViewModel Purchase(string userName, string itemName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("User name must be non-empty.", "userName");
if (string.IsNullOrEmpty(itemName))
throw new ArgumentException("Item name must be non-empty.", "itemName");
return this.userRepository
.TryFind(userName)
.Select(user => this.Purchase(user, itemName))
.DefaultIfEmpty(new InvalidUser(userName))
.Single();
}
public IReceiptViewModel AnonymousPurchase(string itemName)
{
if (string.IsNullOrEmpty(itemName))
throw new ArgumentException("Item name must be non-empty.", "itemName");
return this.Purchase(new AnonymousBuyer(), new InfiniteCash(), itemName);
}
public void Deposit(string userName, decimal amount)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("User name must be non-empty.");
if (amount <= 0)
throw new ArgumentException("Deposit must be positive.", "deposit");
this.userRepository
.TryFind(userName)
.Select(user => this.accountRepository.FindByUser(user))
.Each(account => account.Deposit(amount));
}
...
}
}
There are six public methods in this class. And if we slowly walk down the code we can see that nearly half of the code is devoted to defending from invalid input.
In all cases, user name must be non-null and non-empty string. Should the caller pass null string or empty string, ArgumentException would be thrown.
The same constraint is on the item name - whenever a user wants to purchase an item, but supplies null or empty string, ArgumentException is thrown again.
Deposit method is interesting as well. If user attempts to deposit non-positive amount, there comes the ArgumentException again.
These guard clauses are there on purpose. No doubt the application would be vulnerable without them, because faulty data could easily get to the lower layers of the application. Every step of the way, such data could cause harm. That is why we must always place guard clauses to stop them as early as possible.
But is there any other way to implement them? Any other implementation which is less invasive than this series of if statements is certainly welcome. That is the question we will address in this article.
One convenient way of dealing with guard clauses is to enclose them in extension methods. These methods would actually extend the type on which the test is performed.
Take string as an example. We want to test whether the string variable is non-null and non-empty. We do that using static method defined on the System.String type:
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("User name must be non-empty.", "userName");
The goal of this exercise is to move this entire if statement to the extension method defined on the string type. Something like this:
namespace Store.Helpers
{
public static class StringExtensions
{
public static void AssertNonEmpty(this string value, string paramName)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Value must be a non-empty string.", paramName);
}
}
}
This extension method does everything the if statement did before. But it has one advantage - extension method needs not be called directly. It behaves like a method defined on a type.
Now that we have the extension method defined on the System.String type, we can put it in motion in our custom code:
namespace Store.Domain.Implementation
{
public class DomainServices: IDomainServices
{
...
public IRegisteredUser RegisterUser(string userName, string referrerName)
{
userName.AssertNonEmpty("userName");
referrerName.AssertNonEmpty("referrerName");
IRegisteredUser user = this.RegisterUser(userName);
this.userRepository
.TryFind(referrerName)
.Each(referrer => user.SetReferrer(referrer));
return user;
}
...
}
}
See how this method has suddenly become easier to read? That is because long and cumbersome if-then-throw constructs have been removed and replaced with simple calls to the Assert method on string variables. This method clearly depicts benefits from using extension methods compared to explicit if-then-throw implementation.
In the previous implementation of the AssertNonEmpty extension method, we have implicitly allowed one slightly unusual operation. Having that extension method in place, try to tell what will happen when statement like this is executed:
((string)null).AssertNonEmpty("null");
Will the AssertNonEmpty method throw its ArgumentException, or will the whole statement fail with NullReferenceException even before execution gets into the method?
The answer lies in the extension method itself:
public static void AssertNonEmpty(this string value, string paramName)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Value must be a non-empty string.", paramName);
}
The object on which the method is invoked is actually an argument to the method. This means that we are safe to call the extension method directly on a null reference! There will be no NullReferenceException if we try to do that.
This detail has profound effects on the guard clause design. It ensures that we are perfectly safe to move all guard clauses to the extension methods. They will always be executed, even when reference to the object which is tested is null.
Strings are not the only type which can be tested using extension methods. Any other type can also be subdued to the same technique. Observe the Deposit method of the domain services class:
namespace Store.Domain.Implementation
{
public class DomainServices: IDomainServices
{
...
public void Deposit(string userName, decimal amount)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("User name must be non-empty.");
if (amount <= 0)
throw new ArgumentException("Deposit must be positive.", "deposit");
this.userRepository
.TryFind(userName)
.Select(user => this.accountRepository.FindByUser(user))
.Each(account => account.Deposit(amount));
}
}
}
This method is used to deposit specified amount of money to the specified user's account. The method has two guard clauses - one ensuring that user name is non-empty string, and the other one ensuring that the amount to deposit is positive.
We can fix the user name test easily by using the AssertNonEmpty extension method on the string variable. To do the same with the second test, we would have to extend the System.Decimal type:
namespace Store.Helpers
{
public static class DecimalExtensions
{
public static void AssertPositive(this decimal value, string paramName)
{
if (value <= 0)
throw new ArgumentException("Value must be positive.", paramName);
}
}
}
With this extension method in place, we can simplify the Deposit method:
namespace Store.Domain.Implementation
{
public class DomainServices: IDomainServices
{
...
public void Deposit(string userName, decimal amount)
{
userName.AssertNonEmpty("userName");
amount.AssertPositive("amount");
this.userRepository
.TryFind(userName)
.Select(user => this.accountRepository.FindByUser(user))
.Each(account => account.Deposit(amount));
}
}
}
With this modification in place, Deposit method can finally focus on its primary responsibility - depositing money to the user.
If-then-throw is a well known design principle used to protect operations from receiving invalid input. The problem with if-then-throw is that it quickly clogs the code with many cryptic and complicated throw statements, preceded by if statements which nearly all look the same.
In this article we have seen one powerful technique which helps us keep the code clean and tidy, while still protecting it from invalid input. This repetitive if-then-throw code can all be moved into a small number of extension methods. Each extension method is defined on the type which is actually tested by the if statement.
Net result of this approach is simplified and less complex custom code.
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.