How to Reduce Cyclomatic Complexity Part 5: Option<T> Functional Type

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.

Sometimes there is No Object

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.

Returning an Empty Collection to Indicate Missing Object

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.

Option<T> Functional Type

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);

    }
}

Calling the Method Which Returns Option<T>

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.

Conclusion

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

About

Zoran Horvat

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.

  1. Pluralsight
  2. Udemy
  3. Twitter
  4. YouTube
  5. LinkedIn
  6. GitHub