To Default or not to Default

Posted on 2016-08-17

Here’s a question for you: how quickly do you think it is best to fail in your code? And at what level do you think it should be handled? My answers to those two questions are immediately, and at no level. Let me explain.

In C# we have the wonderful compose-able LINQ extension methods, the sort of thing that has become almost a staple in every managed language now. As part of the framework it contains two methods, Single and First, which takes the only and the first item from a collection respectively. However, these two methods fail if there are no elements in the collection, and Single also fails if there are more than one. The framework also provides alternative extensions, namely SingleOrDefault and FirstOrDefault. These alternative extensions will return null (or the equivalent default value type) when the collections are empty, however SingleOrDefault will still fail if there are more than one item in the collection.

To get to the questions, let’s look at a use case:


public class User : UnitOfWork<User>
{
    public User Get(int id)
    {
        return base.Set().<extension method here>(x => x.Id == id);
    }
}

This example actually makes you decide what failure and error handling strategy you'll use by choosing the extension method that should plug into the provided space. For example, if you use FirstOrDefault you’ll never see an error from this code, but then you are faced with having to handle inconsistent state elsewhere in your domain logic and will probably have to do null checks. Similarly, if you use SingleOrDefault, null checks are in your future, and exceptional or failure program flow probably too for those times when there are more than one entry in the collection. But is that necessary?

Imagine how a user would get your code to call into this method: The UI is requesting a particular user entry, most likely to show a profile screen, or an account edit screen. Or, your controller is building an API data model that contains a User instance to send back as a RESTfull response. In any case, this is not a method that performs a search, or applies a filter, instead a particular instance is requested using a particular id. So what happens if the provided id parameter doesn’t match anything? Or the id somehow matches more than one? Is that actually your UnitOfWork’s problem? Well, consider if this is in a web application. Some URL /user/1234 has routed into a controller call which ended up in your UnitOfWork. A savvy user could easily have manipulated that URL to try out different user Ids (think Ekurhuleni municipality account hacks in 2013). Or your controller code made a wrong assignment (are you unit testing?) and your repository gets the wrong id to pass on. Or the database was changed by someone's script and now id is no longer the primary key. None of these cases is something that you want to suppress or ignore. You want to get that noob-hacker-user out of your IIS thread pool as quickly as humanly possible; an error page is all he should get. And you want to let your testers see an error page the moment something goes wrong. The bottom line: If your UnitOfWork code gets an id that doesn’t match anything, or matches multiple, you want to know about it immediately for a method like this.

So how do you do that? You fail fast and hard, and using the correct extension method is the quickest way, e.g Single. Your UnitOfWork will generate an immediate exception in the case of a bogus id being passed. Your service layer expecting an instance of User won’t bother wasting any more time doing validity checks since an exception is bubbling up, and ultimately this results in redirecting to your custom error page. Similarly, for WebApi or RESTfull services, a 400 or 404 is all I should expect when I pass a bogus id to your API. You might want to do some custom handling to decide on which HTTP status code to return, and maybe a custom header message, but the fail should be obvious to you, your testers and the end-user. Note that handling the exception for the purposes of logging doesn’t constitute an error-handling strategy in my book, and in such a case the exception should be thrown right back anyway.

You often see articles or quotes stating that you should not be afraid of failing. The thing is, the exact same thing applies to code. If you hide all your errors you’ll never find them, and you’ll never improve. Errors are meant to preserve data integrity and to communicate state, and ignoring them opens you up to all sorts of other uncontrollable scenarios with unknown states. You should never take it personally either, for example when an error is reported by QA. Similarly, managers and project leads should accept and encourage risks and thus failures from their developers. It’s a spark for good design discussions and self-improvement.

A final anecdote: A while ago I talked to a start-up which was looking to hire an API and back-office support person that should be available 24 hours during the extended launch period. That is absolutely insane. Apart from the fact that no person should be doing such a job, that start-up made fundamental flaws in their design if they don’t have the confidence to launch and know every error’s cause within moments of it happening. And we're not even considering the case of scaling up. A big part of “lean” is failing, and a huge part of succeeding is how you deal with errors. Similarly, how your code deals with errors is a fundamental component of its design.

Here are some more reading on failing:

Recently on helloserve

It's been a year and a bit since I took delivery of my CX-5. If you're wondering how it has been living with this car, read on. Spoiler alert: It's been pretty fantastic.

A while ago I wrote about interfaces and how everybody just uses it for IoC these days. This time I want to discuss something with regards to established and advertised IoC patterns and problems I have with it.

You would expect that a member in a sub-class would always take precedence over a member with the same name in the base class when being invoked? I did too.