How to Reduce Cyclomatic Complexity Part 1: Null Object Pattern

by Zoran Horvat

Cyclomatic complexity is one of the basic measures that can show us how complex our code is. Basically, cyclomatic complexity is a number which indicates number of independent ways in which a segment of code can be executed. It is one of the standard metrics applied to source code and it is used to indicate segments of code which are making too many decisions. You can read more about this metric from Wikipedia article Cyclomatic complexity .

Although the question of complexity may look broad and vaguely defined, there are in fact only a couple of language constructs that are causing its complexity to grow. If statement may cause one or the other branch to be executed, thus it attributes to cyclomatic complexity with value 2.

For and while loops also add complexity because there are distinct execution paths through or around them. Nested if-else statements, or if-else statements within loops add even more to the overall complexity of the code.

In this article we will examine one of the simplest techniques that can be applied to avoid if-then-else statements and in that way reduce cyclomatic complexity. This particular case is when we have to test whether some object reference is null before we decide on the next action.

An Example

Take a simple e-commerce application into account. Presentation layer triggers requests such as login user, or deposit money, or purchase an item. These requests are handled by application service. Let's examine just implementation of the purchase method in the application service class:

public class ApplicationServices: IApplicationServices
{

    private readonly IDomainServices domain;

    public ApplicationServices(IDomainServices domain)
    {
        this.domain = domain;
    }

    public ReceiptDto LoggedInUserPurchase(string itemName)
    {
        if (IsDownForMaintenance())
            return null;
        return this.domain.Purchase(Session.LoggedInUserName, itemName);
    }

    private bool IsDownForMaintenance()
    {
        return File.Exists("maintenance.lock");
    }
}

The LoggedInUserPurchase method is supposed to buy an item in the name of currently logged in user. It returns a DTO (data transfer object) which contains information about successful purchase.

That is quite simple, but implementation quickly branches because this application supports maintenance mode. If a specific file exists, named maintenance.lock, the application service will just quit the operation and return null.

Further on, purchase may also fail downstream in the domain layer. Here is the sample implementation of domain service class:

public class DomainServices: IDomainServices
{

    private readonly IUserRepository userRepository;
    private readonly IProductRepository productRepository;
    private readonly IAccountRepository accountRepository;

    public ReceiptDto Purchase(string userName, string itemName)
    {

        User user = this.userRepository.Find(userName);

        if (user == null)
            return null;

        Account account = this.accountRepository.FindByUser(user);

        return this.Purchase(user, account, itemName);

    }

    private ReceiptDto Purchase(User user, Account account, string itemName)
    {

        Product item = this.productRepository.Find(itemName);

        if (item == null)
            return null;

        ReceiptDto receipt = user.Purchase(item);
        MoneyTransaction transaction = account.Withdraw(receipt.Price);

        if (transaction == null)
            return null;

        return receipt;

    }
}

There is quite a lot of domain logic here. Domain service is first looking up the user by its registered name. If such user does not exist, null receipt is returned once again. Otherwise, the operation continues, but it may fail again if requested item does not exist. The third way to fail is monetary transaction could not be executed on the user's account, which may be the result of low balance. Finally, if all the obstacles were passed, the method will return a non-null receipt DTO.

Direct consequence of returning null from methods is that the caller must deal with null result separately from all other non-null results. Here is the possible controller implementation:

public class HomeController
{
    private readonly IApplicationServices application;

    public HomeController(IApplicationServices application)
    {
        this.application = application;
    }

    public ActionResult Purchase(string itemName)
    {
        ReceiptDto receipt = this.application.LoggedInUserPurchase(itemName);
        return SelectView(receipt);
    }

    private ActionResult SelectView(ReceiptDto receipt)
    {
        if (receipt == null)
            return new FailedPurchaseView();
        else
            return new SuccessfulPurchaseView(receipt);
    }
}

The controller is responsible to select appropriate view which will be rendered. And that is precisely what it does in its SelectView method. The problem with this method is that it treats null result separately from non-null results.

What's the Problem with Null Results?

There are two issues which are inherent to using nulls around the application.

The first problem is that null carries no information. What was the root cause of a failing purchase request? Was it insufficient funds, or maybe a problem with user authentication? Presentation layer cannot tell that. It can only tell that something went wrong and operation was given up before completion.

The second problem is in the code which consumes null references. All logic must be guarded from nulls. Every single statement which operates on a potentially null object becomes an if-else statement. This may significantly increase cyclomatic complexity of the code, especially in more complicated applications.

How to Get Rid of Null Results?

The most obvious approach to null results is to replace them with some dummy objects. Null Object design pattern helps us with that.

Basic idea is to construct dummy object which would stay in place of a regular object in cases when the regular object cannot be constructed. We could also say that Null Object is just a replacement for the null reference.

The difference between Null Object implementation and plain null reference is that Null Object is still an object, and it may carry some information. Most obvious piece of information that every object carries is its type. But there are other pieces of information provided by default implementations in its members.

An Example of Null Object Implementation

Suppose that we want to replace our PurchaseDTO with Null Object in case when purchase could not be performed. Such Null Object would tell a story like "there is no receipt, purchase was not performed".

The first obstacle we hit is that PurchaseDTO and Null Purchase Object should be replaceable. In other words, they must share a common base type.

So we start the implementation by first defining an abstract base type for the class which can be represented by a Null Object. It can be either a proper abstract class, or just an interface (which is, by the way, also an abstract class).

namespace Store.Presentation.Interfaces
{
    public interface IReceiptViewModel
    {
    }
}

This is the marker interface. It defines no members, but only indicates that an object implementing it is the receipt.

Now we can provide two implementations of this view model. The first implementation is already there - it is the receipt DTO. The only change is to let it implement the interface:

public class ReceiptDto: IReceiptViewModel
{
    public string UserName { get; private set; }
    public string ItemName { get; private set; }
    public decimal Price { get; private set; }

    public ReceiptDto(string userName, string itemName, decimal price)
    {
        this.UserName = userName ?? "anonymous buyer";
        this.ItemName = itemName;
        this.Price = price;
    }
}

The other implementation will be the Null Object:

public class ReceiptNullObject: IReceiptViewModel
{
    private static IReceiptViewModel instance;

    private ReceiptNullObject() { }

    public static IReceiptViewModel Instance
    {
        get
        {
            if (instance == null)
                instance = new ReceiptNullObject();
            return instance;
        }
    }
}

ReceiptNullObject is an empty class. It carries no information because there is no receipt. Also, it implements the Singleton design pattern . Constructor is marked private and the only way to get hold of an instance of this class is through its static property getter Instance. This property getter will, in turn, always return the same instance.

Using the Null Object

The first step in using Null Object implementation is to replace all null references with the Null Object instance. In our example application, application service used to return null receipt in case when application is down for maintenance. We will fix that:

public class ApplicationServices: IApplicationServices
{
    ...
    public IReceiptViewModel LoggedInUserPurchase(string itemName)
    {
        if (IsDownForMaintenance())
            return ReceiptNullObject.Instance;
        return this.domain.Purchase(Session.LoggedInUserName, itemName);
    }
    ...
}

The next step is to repeat the process in the domain services:

public class DomainServices: IDomainServices
{

    private readonly IUserRepository userRepository;
    private readonly IProductRepository productRepository;
    private readonly IAccountRepository accountRepository;

    public ReceiptDto Purchase(string userName, string itemName)
    {

        User user = this.userRepository.Find(userName);

        if (user == null)
            return ReceiptNullObject.Instance;

        Account account = this.accountRepository.FindByUser(user);

        return this.Purchase(user, account, itemName);

    }

    private ReceiptDto Purchase(User user, Account account, string itemName)
    {

        Product item = this.productRepository.Find(itemName);

        if (item == null)
            return ReceiptNullObject.Instance;

        ReceiptDto receipt = user.Purchase(item);
        MoneyTransaction transaction = account.Withdraw(receipt.Price);

        if (transaction == null)
            return ReceiptNullObject.Instance;

        return receipt;

    }

}

In this way, all the holes are plugged. There are no more paths that return null to the presentation layer. This opens the opportunity to refactor the controller so that it doesn't ask what kind of result it has received.

Controller can make a map which determines which view to use in which case:

public class HomeController
{
    private readonly IApplicationServices application;
    private Dictionary<Type, Func<IReceiptViewModel, ActionResult>> receiptTypeToActionResult;

    public HomeController(IApplicationServices application)
    {
        this.application = application;

        this.receiptTypeToActionResult =
            new Dictionary<Type, Func<IReceiptViewModel, ActionResult>>();
        this.receiptTypeToActionResult.Add(typeof(ReceiptDto),
                                           model => new SuccessfulPurchaseView(model as ReceiptDto));
        this.receiptTypeToActionResult.Add(typeof(ReceiptNullObject),
                                           model => new FailedPurchaseView());

    }
    ...
}

This would be an application of Service Locator design pattern . The whole problem of mapping receipts to views becomes a pure configuration task. If needed, this piece of code can be made even more generic and moved to a specialized class which transparently configures the controller.

With this modification, controller can easily select the view for any of the supported DTOs that can be rendered in the response:

public class HomeController
{
    ...
    public ActionResult Purchase(string itemName)
    {
        IReceiptViewModel receipt = this.application.LoggedInUserPurchase(itemName);
        return SelectView(receipt);
    }

    private ActionResult SelectView(IReceiptViewModel receipt)
    {
        Type modelType = receipt.GetType();
        Func<IReceiptViewModel, ActionResult> mapper = this.receiptTypeToActionResult[modelType];
        return mapper(receipt);
    }
}

As you can see, there is no more if-else in the SelectView method. This method can now serve the view based on particular DTO that bubbled up from the applicative layer. The cyclomatic complexity of this method is now 1 and will remain 1 even after new views and new DTOs are added to the application.

Conclusion

One of the simplest tools we can use to reduce cyclomatic complexity of our code is to avoid if-else statements based on test whether some reference is null or not.

Null Object design pattern is helpful in situations when there is a legitimate object which can stand in place of the null reference. Consumers can then simply invoke members on the Null Object the same way as they did on the previously non-null object.

An obvious benefit from applying the Null Object pattern is that null reference tests and corresponding if-else statements will disappear from your code, making it overall simpler, easier to understand and to maintain.


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