Single Responsibility Principle applied to exception logging

In the previous post How to log exceptions I said that you should let your logging framework handle exception logging. In this post I will explain why it is a bad idea to generate log messages in the catch block.

Lets first look at an example:

try
{
    UpdateAccount(userAccount);
}
catch (SqlException e)
{
    var sb = new StringBuilder();
    sb.AppendLine(e.ToString());
    sb.AppendFormat("Sql number: {0}", e.Number);
    sb.AppendFormat("Sql procedure: {0}", e.Procedure);
    sb.AppendFormat("Sql server: {0}", e.Server);

    logger.Error(sb.ToString());
}

As you can see, the main code invokes method UpdateAccount, which we can assume will perform update in the database. In the catch block error message is constructed with exception details and logged.

The first thing which you can easily notice is that exception handling block is much bigger than the main code. When analysing this code the user will have to filter out big chunk of exception formatting.

Next, because message formatting is embedded in the method, it will have to be duplicated across code base wherever SqlException is handled. The more places it is duplicated the harder it is to update.

And finally – it breaks Single Responsibility Principle. As defined by Uncle Bob:

A class should have one, and only one, reason to change.

In the above example, the class will have at least three reasons to change:

  1. the change in handling account update (this is the primary reason for this class)
  2. change in error message formatting, including adding/removing details
  3. change in error types handled, such as adding new error type to be logged

This is problematic because adding new exception property to be logged requires re-running all tests related to this call, including regression test. Suddenly a simple change is no longer simple.

The error message formatting logic belongs to the logging framework. Use type renderer for formatting message according to your needs. To handle logging new exception types you only need to create new type renderer and register it with logging framework. You can even reuse type renderers between different applications.
And the catch block will shrinks to one line of code:

logger.Error("Updating user account failed.", e);

Using policies to handle exceptions while calling external services

Exception handling very easily gets ugly. Typical try...catch block clutters method and grows with any new exception discovered. Then, bits of code are copied between methods which require same error handling. Adding any new logic into error handling is a nightmare and with each new release it seems like the same errors are coming back.

Policies for handling exceptions

To overcome those problems we can extract logic related to exception handling into separate objects – policies. This will keep main business logic clear, allow reusing and make testing easy.

Here’s the definition for recoverable policy:

public interface IRecoverablePolicy<TResult>
{
   TResult Execute(Func<TResult> operation);
}

One example of recoverable policy is handling transient exceptions. Usually they require retrying method call after a small pause.

public class TransientExceptionRecoveryPolicy<TResult> : IRecoverablePolicy<TResult>
{
    public TResult Execute(Func<TResult> operation)
    {
        try
        {
            return operation.Invoke();
        }
        catch (TransientException)
        {
            Thread.Sleep(TimeSpan.FromSeconds(1));
            return Execute(operation);
        }
    }
}

Now, extending this code with a functionality to retry N times with and exponentially increasing backoff between attempts is fairly easy. Also, unit testing is straight forward. The policy can be reused to protect any method call which may throw a TransientException.

The policy for handling communication exceptions is very similar to the above.

To execute code with the policy we call it like this:

policy.Execute(()=> { // call a method on external service });

Handling functional cases

Above examples were covering non functional cases where external service may give transient error. But we can cover functional cases as well. Let’s imagine a scenario where we call a method to create an article and get back article ID. Once article is created we want to further use it’s ID, let’s say to store it in the system. The article title has to be unique, and when uniqueness is not satisfied, the DuplicateTitleException is thrown. Assuming we are not using any transactions, there potentially is a risk of operation failing between creating and article and storing it’s ID. To make the operation idempotent we need to detect DuplicateTitleException, fetch article’s ID and carry on. This can be easily achieved by wrapping call to create an article in the policy which on failure will get the ID and return it back. Below is an example implementation:

public class DuplicateTitleRecoveryPolicy : IRecoverablePolicy<long>
{
    private readonly IExternalService _service;

    public DuplicateTitleRecoveryPolicy(IExternalService service)
    {
        _service = service;
    }

    public long Execute(Func<long> operation)
    {
        try
        {
            return operation.Invoke();
        }
        catch (DuplicateTitleException e)
        {
            var article = _service.GetArticleByTitle(e.Title);
            return article.Id;
        }
    }
}

Composite policy

To cover the code with few policies, we can use a CompositePolicy which will execute set of policies one after another:

public class CompositeRecoverablePolicy<TResult> : IRecoverablePolicy<TResult>
{
    private readonly List<IRecoverablePolicy<TResult>> _policies;

    public CompositeRecoverablePolicy(IEnumerable<IRecoverablePolicy<TResult>> policies)
    {
        _policies = new List<IRecoverablePolicy<TResult>>(policies);
    }

    public TResult Execute(Func<TResult> operation)
    {
        var chainedPolicies = operation;

        foreach (var policy in _policies)
        {
            var localOperation = chainedPolicies;
            var currentPolicy = policy;
            chainedPolicies = () => currentPolicy.Execute(localOperation);
        }

        return chainedPolicies.Invoke();
    }
}

We pass all the policies required to cover the code in the constructor, and use composite policy to execute the code.

Extension methods

To further increase readability of the code we can use extension methods and create overloads which take policy as an extra parameter:

public interface IExternalService
{
    long CreateArticle(string title, string author, string body);
}

public static class ExternalServicePolicyExtensions
{
    public static long CreateArticle(this IExternalService service, string title, string author, string body, IRecoverablePolicy<long> policy)
    {
        return policy.Execute(() => service.CreateArticle(title, author, body));
    }
}

Now, the call to create an article will look more familiar:

var articleId = service.CreateArticle("post", "me", "empty", policy);

Sample code

The sample code can be found on github.

Summary

Policies are very powerful tool. They allow to decouple exception handling from the main code. This leads to smaller and simpler classes which adhere to Single Responsibility principle. By using a strategy pattern we also follow Open/Closed principle and make the application easy to extend. Policies are easy to reuse, hence it is easier to prevent code duplication (DRY principle). Each policy is treating single case and is completely independed from other business code which makes them extremely easy to unit test.

The best place to use policies is when making network calls, using external service or need to respond and recover from business errors.