How to Reduce Cyclomatic Complexity Part 7: Extension Methods

by Zoran Horvat

Many statements do not deserve to appear in the domain logic. Take this piece of code as an example:

namespace Store.Domain.Implementation
{
    public class DomainServices: IDomainServices
    {
        ...
        public IRegisteredUser RegisterUser(string userName, string referrerName)
        {

            IRegisteredUser user = this.RegisterUser(userName);

            this.userRepository
                .TryFind(referrerName)
                .ToList()
                .ForEach(referrer => user.SetReferrer(referrer));

            return user;

        }
        ...
    }
}

This function registers a user with a referrer. Once the user is created, we search for the referrer. If successful, i.e. if the user with specified user name exists, we just assign that referrer object to the newly created user. This operation has business meaning. Namely, referrer will receive certain discount after new user has registered.

The way in which this operation is implemented is to use TryFind method on the user repository. This method returns Option<User>, which is a collection containing either zero or one user. For more details on Option<T> functional type, please refer to one of the previous articles in this series - How to Reduce Cyclomatic Complexity: Option<T> Functional Type .

Option object returned from the TryFind method is then used to invoke SetReferrer method on all referrer user objects. However, this statement contains one irritating line – call to the ToList method. This method was invoked only because I wanted to call the ForEach method.

Can we do this in some other way? Basically, call to the ToList method has nothing to do with what we wanted and that line is only reducing readability of the code.

The solution lies in the combination of ToList and ForEach methods. We wanted to invoke SetReferrer method on all referrer objects in the collection, but we didn’t have a method which does precisely that. So we reached out for the combination of ToList and ForEach methods. This combination is idiomatic and it is easy to imagine other cases where it is used.

For example, this same DomainServices class has another method which also uses ToList and ForEach methods combined:

namespace Store.Domain.Implementation
{
    public class DomainServices: IDomainServices
    {
        ...
        public void Deposit(string userName, decimal amount)
        {
            this.userRepository
                .TryFind(userName)
                .Select(user => this.accountRepository.FindByUser(user))
                .ToList()
                .ForEach(account => account.Deposit(amount));
        }

    }
}

Deposit method searches for the user, then picks up the user’s account and invokes Deposit method on the Account object. Once again, TryFind method of the user repository returns a collection with zero or one object in it, and once again we use this ToList-ForEach idiom to invoke a method on each of the elements in the collection.

Moving Idiomatic Expressions to Extension Methods

We can simplify our code by identifying expressions that merely complicate the code and appear more than once. Then we move those expressions to an extension method on the class which was used.

For example, ToList method call followed with the ForEach method call can be moved to an extension method on an IEnumerable<T> interface:

namespace Store.Helpers
{
    public static class IEnumerableExtensions
    {
        public static void Each<T>(this IEnumerable<T> collection, Action<T> action)
        {
            collection.ToList().ForEach(action);
        }
    }
}

With this change, we can simplify domain service methods a bit, and also make them more readable:

namespace Store.Domain.Implementation
{
    public class DomainServices: IDomainServices
    {
        ...
        public IRegisteredUser RegisterUser(string userName, string referrerName)
        {

            IRegisteredUser user = this.RegisterUser(userName);

            this.userRepository
                .TryFind(referrerName)
                .Each(referrer => user.SetReferrer(referrer));

            return user;

        }

        public void Deposit(string userName, decimal amount)
        {
            this.userRepository
                .TryFind(userName)
                .Select(user => this.accountRepository.FindByUser(user))
                .Each(account => account.Deposit(amount));
        }

    }
}

With this modification, methods read exactly as they would read in the client requests. For example, user registration request would read like: Create a user with specified user name and, if another user with referrer name exists, set that user as the referrer.

Deposit request would read like: Find the user and, if she exists, deposit money to her account.

Implementation of both methods is exact copy of the request and that is what we can accomplish by moving unrelated processing to utility methods, such as extension methods.

Moving Ugly Code to Extension Methods

Sometimes classes that we use cause us to write plain ugly code. Look at this function:

namespace Store.Infrastructure
{
    public class UserRepository: IUserRepository
    {
        ...
        public Option<User> TryFind(string userName)
        {
            User user = null;
            if (this.userNameToUser.TryGetValue(userName, out user))
                return Option<User>.Create(user);
            return Option<User>.CreateEmpty();
        }
    }
}

This function simulates a repository. It keeps User objects in the internal dictionary and then searches the dictionary by user name when a user object is requested. But the way in which we are querying the dictionary is hard to read and requires some thinking every time we lay our eyes on that line.

One of the problems in programming is when there is a mapping between what we think and what the code looks like. Many times, operations are performed in an obscured way and we have to spend a couple of seconds thinking what this line does.

We can address readability problems by providing extension methods and putting this obscure code there. For example, we could extend the IDictionary interface to support getting an Option object from the key value:

using System.Collections.Generic;

namespace Store.Helpers
{
    public static class IDictionaryExtensions
    {
        public static Option<TValue> TryGetValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
        {

            TValue value;
            if (dictionary.TryGetValue(key, out value))
                return Option<TValue>.Create(value);

            return Option<TValue>.CreateEmpty();

        }
    }
}

This is all it takes to enclose the irritating operation on the dictionary. Now we can use the dictionary in much more elegant way:

namespace Store.Infrastructure
{
    public class UserRepository: IUserRepository
    {
        ...
        public Option<User> TryFind(string userName)
        {
            return this.userNameToUser.TryGetValue(userName);
        }
    }
}

When written in this way, it is quite obvious what the TryFind method does. It delegates the call to the internal dictionary and returns the Option<User> object possibly containing the requested element.

Conclusion

It is sometimes difficult to see the forest for the trees. It almost looks like programming languages are forcing us to add cryptic symbols to the code, lines and expressions that we write only to map requirements to actual way in which source code is normally written.

But we can step aside and ask: Is that really the way in which source code is supposed to be written?

When functional extensions were added to the C# language, we could suddenly move query-like code to extension methods. Now we can use that powerful option to remove things that are not part of the domain logic, but take room in the domain-related methods. Such scaffolding or infrastructure code can easily be enclosed in extension methods defined on the class or type which provides input for the operation.

Net result is the domain logic code which is much easier to read and to understand. By providing convenient names to extension methods, we could even get away with never having to look at their actual implementation in order to understand the domain logic implementation.


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