by Zoran Horvat
In the previous articles we have highlighted several methods that can help reduce cyclomatic complexity of our code.
Basic approach to addressing the issues is to always try to provide certain object which is suitable for the scenario at hand. In that respect, we have seen Null Objects and Special Case objects at work.
These two design patterns helped us return non-null results in many cases. Primary problem with null reference returned from a method is that it forces the caller to branch. If result is null, one sort of action is taken; otherwise another sort. This is basically a polymorphism which is not recognized and it is therefore implemented as an if-then-else statement.
There are situations in which Null Object and Special Case design patterns are not applicable.
For example, what Special Case object would we return when looking up a registered user in case when such user does not exist in the database? We could return something like InvalidUser object. But soon we would get in trouble when it turns out that InvalidUser should place an order, pay for it, receive an email, etc. All the domain logic would have to be implemented to avoid making any change to the system when InvalidUser object is used.
It would be much easier to just say that there is no User object to return when nonexistent username is passed to the method. But at the same time, we desperately want to avoid returning null reference when object does not exist. We still want to return an object which indicates that there is no domain object that fulfills the request.
This might sound insane, but in fact there is such class. We could return a collection with zero or one element, indicating that specific object exists or does not exist. Collection would be the non-null object that we return.
Take a look at the class which represents a bank account:
public class Account: IAccount
{
...
public IMoneyTransaction Withdraw(decimal amount)
{
if (this.Balance >= amount)
{
MoneyTransaction transaction = new MoneyTransaction(-amount);
this.transactions.Add(transaction);
this.Log(string.Format("{0} withdrew ${1:0.00} - balance ${2:0.00}",
this.UserName, amount, this.Balance));
return transaction;
}
return null;
}
}
Withdraw method checks the balance and, if there are sufficient funds, generates a transaction. Otherwise, it simply returns null to indicate that there is no withdraw transaction.
We want to refactor this method so that it clearly indicates whether the transaction was executed or not, but at the same time to return a proper non-null object reference.
One way to ensure that the result will always be non-null, even when the money transaction object does not exist, is to return a collection of money transactions:
public class Account: IAccount
{
...
public IEnumerable<IMoneyTransaction> TryWithdraw(decimal amount)
{
if (this.Balance < amount)
return new MoneyTransaction[0];
MoneyTransaction transaction = new MoneyTransaction(-amount);
this.transactions.Add(transaction);
this.Log(string.Format("{0} withdrew ${1:0.00} - balance ${2:0.00}",
this.UserName, amount, this.Balance));
return new MoneyTransaction[] { transaction };
}
}
This implementation clearly communicates the fact that withdraw attempt may fail. First, name of the method is changed and now it reads TryWithdraw. The name by itself indicates that the operation may not be successful. Second, return value of the method is a collection of money transactions, and collection may be empty. Every programmer is instinctively cautious when dealing with collections. We know that collection may be empty and therefore we do not access its members before checking for their presence.
However, this solution is far from perfect. Just returning a collection does not solve the problem, because now TryWithdraw method tells us that it could equally probably generate a dozen of transactions. That is not what the domain logic specifies – when trying to withdraw money from the account, there could be one or none transactions created in response, depending on whether there were enough funds or not.
To better solve the problem, we can devise a special collection which can either be empty or contain one element. Such collection already exists in functional languages, and it is typically called Option. F#, for example, defines the option keyword for such purpose. Option is a collection which is either empty or contains exactly one element.
To better communicate that one particular object may exist or not exist, we can provide a special implementation of the IEnumerable interface. This new class is typically called Option<T>, though sometimes it is also referred to as Maybe<T>.
public class Option<T> : IEnumerable<T>
{
private readonly T[] data;
private Option(T[] data)
{
this.data = data;
}
public static Option<T> Create(T value)
{
return new Option<T>(new T[] { value });
}
public static Option<T> CreateEmpty()
{
return new Option<T>(new T[0]);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)this.data).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.data.GetEnumerator();
}
}
This class is the enumeration. But it cannot be initialized and populated like any other collection. The only way to populate the Option is via its static Create methods. And that is where the constraint comes, because Create method only receives one element, while the CreateEmpty method receives no elements. In that way, we can only create the Option object with one or no elements in it.
When it comes to using the Option type, it gets even simpler than using the array. Here is the TryWithdraw method of the Account class with Option result:
public class Account: IAccount
{
...
public Option<IMoneyTransaction> TryWithdraw(decimal amount)
{
if (this.Balance < amount)
return Option<IMoneyTransaction>.CreateEmpty();
MoneyTransaction transaction = new MoneyTransaction(-amount);
this.transactions.Add(transaction);
this.Log(string.Format("{0} withdrew ${1:0.00} - balance ${2:0.00}",
this.UserName, amount, this.Balance));
return Option<IMoneyTransaction>.Create(transaction);
}
}
By this point we have implemented the server side of the operation. Now we have a method of a domain class which doesn’t return a reference to an object but instead returns Option object for that same result type. In that way, we have ensured that the caller will always receive a non-null result in response to calling the method.
Now the time has come to see how the caller acts when faced with the Option result. Here is the domain services class which uses the Account class to make withdraw money on purchase:
public class DomainServices: IDomainServices
{
...
private IReceiptViewModel Purchase(IBuyer buyer, IAccount account, string itemName)
{
Product item = this.productRepository.Find(itemName);
if (item == null)
return new OutOfStock(buyer, itemName);
IReceiptViewModel receipt = buyer.Purchase(item);
IMoneyTransaction transaction = account.Withdraw(item.Price);
if (transaction == null)
return new InsufficientFunds(buyer, item.Price, itemName);
return receipt;
}
}
This method used to invoke the Withdraw method and then to test whether the result was null or not. Once we have switched to the TryWithdraw method which returns Option, we can simplify this piece of code:
public class DomainServices: IDomainServices
{
...
private IReceiptViewModel Purchase(IBuyer buyer, IAccount account, string itemName)
{
Product item = this.productRepository.Find(itemName);
if (item == null)
return new OutOfStock(buyer, itemName);
IReceiptViewModel receipt = buyer.Purchase(item);
return
account.TryWithdraw(item.Price)
.Select(transaction => receipt)
.DefaultIfEmpty(new InsufficientFunds(buyer, item.Price, itemName))
.Single();
}
}
This time, we are relying on the LINQ to Objects library to transform the result of the withdraw operation on the account to the appropriate view model object. If TryWithdraw returns Option with an element in it, then the previously prepared receipt object is returned. Otherwise, DefaultIfEmpty extension method jumps in and places InsufficientFunds object instead. Final line is call to the Single extension method, which will reduce the collection to a single object which will then be returned from the method.
In this article we have demonstrated the basic idea behind using the Option<T> functional type. Instead of returning null to indicate that an object doesn’t exist, we can return an empty Option object. Once Option is at hand, we can use LINQ to Objects library to manipulate its content.
This approach significantly simplifies the caller. This time, the caller does not have to test whether the reference it has obtained is null or non-null. This can be completely offloaded to the LINQ extension methods. As the result, cyclomatic complexity of the client will be 1, because there will be no alternate branch in case that the object reference is null.
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.