Functional vs. imperative error handling

In the previous posts of this series we have seen how functional error handling can be implemented in F# and C#.

Now let's look at an implementation of the command line parser using the Notification Pattern.

Parsing arguments with the Notification Pattern

All the dependencies, in this case the arguments and the argument infos, are provided as constructor parameters.

The class exposes a public read-only property Dictionary which has a private backing field. In the getter the dictionary will be created if the backing field is NULL. If the creation of the dictionary fails, an exception will be thrown.

The caller can check if the arguments are valid by calling the Validation method. This method will return a Notification instance which is a container for error messages. The Notification instance has a property IsValid that is true if the validation succeeded, otherwise it is false.

Here is a shortened version of the class:

public class NoteArguments
{
    private readonly IEnumerable<string> _commandLineArguments;
    private readonly IEnumerable<string> _requiredCommands;
    private readonly IEnumerable<string> _definedCommands;
    private Dictionary<string, string> _dictionary;

    public NoteArguments(IEnumerable<string> commandLineArguments, IEnumerable<ArgInfo> defs)
    {
        _commandLineArguments = commandLineArguments;
        _requiredCommands = defs.Where(x => x.required).Select(x => x.command);
        _definedCommands = defs.Select(x => x.command);
    }

    public Dictionary<string, string> Dictionary
    {
        get
        {
            return _dictionary ?? (_dictionary = CreateDictionary());
        }
    }

    private Dictionary<string, string> CreateDictionary()
    {
        Check();
        return _dictionary;
    }

    private void Check()
    {
        var note = Validation();
        if (note.HasErrors)
            throw new ArgumentException(note.ErrorMessage);
    }

    public Notification Validation()
    {
        var note = new Notification();

        var parsed = Parse(note);

        if (note.HasErrors)
            return note;

        CheckNoDuplicates(parsed, note);
        CheckAllRequired(parsed, note);
        CheckAllDefined(parsed, note);

        if (note.IsValid)
            _dictionary = parsed.ToDictionary(x => x.First(), x => x.Skip(1).First());

        return note;
    }
    //... 
}

There are numerous other possibilities of how to implement this class and its public interface depending on the context. Returning a Notification instance might not really be necessary because in this case there is no separated presentation layer. So the IsValid or Errors properties and the Validate method could be members of the NoteArguments class itself. Or we could also implement an interface IValidatable<T> which exposes those members. Also the validator could be injected. However, since all implementations are in essence very similar I tried to stick with a very basic approach for demonstration purposes.

It's easy to shoot yourself in the foot

So what is the drawback of this design?

This design makes it very easy to shoot yourself in the foot.

If we access the Dictionary property while the provided command line parameters are not valid, the application will crash.

Can we avoid this? - No, we can't avoid this because we don't know what to return in case of a failure. Off course we could return NULL or even a NULL object, but this only postpones the need to handle the failure case. The call side has to wrap the calling code into a try-catch statement or check for null.

And the real problem with this is that the caller is not forced to do that. Also it might not even be obvious for the caller that accessing a given property could crash the application. Furthermore, exceptions, try-catch statements, and NULL checks will decrease the readability and maintainability of the code and will ruin the possibility to reason about the code.

Exceptions and expected behavior

Martin Fowler states that exception are only for unexpected behaviors:

Exceptions signal something outside the expected bounds of behavior of the code in question. But if you're running some checks on outside input, this is because you expect some messages to fail - and if a failure is expected behavior, then you shouldn't be using exceptions. -- Martin Fowler

And still the notification pattern uses exceptions for an expected failure case. As we have seen, we can not really avoid an exception when accessing the Dictionary property while the provided command line parameters are not valid. But this is not an unexpected situation because the command line parameters are outside inputs, so it is not unlikely that something goes wrong.

A better way of doing error handling

Returning NULL or a NULL object goes into the right direction, even though that's still far away from a good and solid design.

If we continue to think this approach we will eventually end up with the idea of the Result type as implemented by the Chessie library. The details of this approach are described in the first three parts of this series.

By comparing the functional error handling approach to the imperative approach it will become even more clear what the benefits of the Result type are.

Because it is very hard and inconvenient to extract the inner value of a Result without using its provided methods, the caller is always forced to handle the success as well as the failure case. There is no need to throw exceptions or to return NULL. Therefore the code won't be cluttered with try catch statements or NULL checks. Instead the caller can use the composition mechanisms of functional error handling which is applicative and monadic composition. As a result the code will be more declarative. The composition mechanisms will always be the same and they scale through all the layers of the application.

The FsCQRSShop and Railway-Oriented-Programming-Example are only two examples that demonstrate functional error handling and railway oriented programming without any exceptions where the data flows on two tracks throughout the application.

Conclusion

In the first three posts of this series we have examined the details of functional error handling with the help of the library Chessie in F# and C#. In C#, though, functional error handling is not as comfortable as it is in F# because of missing language features. Additionally, the possibility to initialize a Result value with NULL in C# ruins the benefit of safety that we have in F#. But still if C# and F# projects have to inter-operate, it does work fine with the Chessie library.

In this post we briefly compared the functional to the imperative approach.

We have seen the benefits of functional error handling, which are:

  • A much safer and solid usage
  • More predictable code
  • The details of error handling are abstracted away
  • More declarative code
  • Functional error handling embraces composition, separation of concerns and scalability

Resources