.NET Code Contracts – Role in Solo-Programmer Projects

ccode-contractsnet

I often find myself wondering what programming best practices apply to solo programming, since most of the time, I'm the only programmer on a project. I just started experimenting with C# 4.0 Code Contracts, and I thought I'd ask the community what utility they may have for a solo programmer.

Here's where I'm coming from with this. Most of the time, when I encounter an exception, it means there's a bug in my code. There are definite exceptions (no pun intended) to this, such as when I'm dealing with something external, such as a text file, database, or service, but for the most part, exceptions mean I did something wrong.

Take this simple method:

public void MapEntity(PersonModel model, PersonEntity entity)
{
    entity.Name = model.Name;
    entity.Age = model.Age;
    entity.Updated = DateTime.Now;
}

If either model or entity are null, this method will throw a NullReferenceException.

As the only consumer of my code, I know that I shouldn't pass in a null reference, but if I somehow do, I want an exception to be thrown, and I want the debugger to jump right to the source of the error so I can see what happened.

That said, I could write:

public void MapEntity(PersonModel model, PersonEntity entity)
{
    Contract.Requires(model != null);
    Contract.Requires(entity != null);
    entity.Name = model.Name;
    entity.Age = model.Age;
    entity.Updated = DateTime.Now;
}

This has the advantage of explicitly indicating (to myself) that the two arguments are not allowed to be null. However, it's very unlikely I would make this mistake (because I know what the method does). Further, if I understand properly what Requires() does, the behavior at runtime if a null argument is encountered would be pretty much the same. So, I can't help but wonder, what have I gained besides two lines of code?

As a counterpoint, here's a case where I see definite value in being explicit:

public User FindUser(string emailAddress)
{
    if (string.IsNullOrWhiteSpace(emailAddress)) return null;
    var users = DbContext.Users.Where(u => u.EmailAddress == emailAddress);
    Contract.Assert(users.Count() <= 1,
                    "The database appears to be corrupted:
                    More than one user was found for the provided email address.
                    Email addresses are supposed to be unique.");
    return users.SingleOrDefault();
}

But in this case, the reason I see value in using a coding contract is to indicate that something that has gone wrong that is not a bug per se (though it could have resulted from a bug elsewhere in the system).

I'd like to know if I'm on the right track with my thinking, or if there is more value in code contracts than I realize. Does it pay to use contracts when you can examine and debug the code yourself or only when the code is going to be used by a third party?

Best Answer

The advantage of assertions (or contracts, which are basically a different take on assertions) comes when your codebase grows large enough that you can no longer hold the entire thing in your short-term memory. Especially when you're still developing it and changing things every day.

When you first wrote MapEntity, you most likely wrote it because some other routine somewhere required its functionality. You were working on the other routine at the time, and you know it's not going to pass any nulls. But a few months from now, you might be writing something else that needs the same functionality, so you have it call MapEntity too. But maybe this code can have a null somehow and you didn't think to check for that.

With no assertions, you get a null reference exception at this point. With assertions in place, you get an assertion failure (or whatever violated contracts throw, in this case.) Either way, you have an error here and you have to dig into the code to figure out what's going on. You look at the code, and you see that the problem is in MapEntity because it's choking on one of the parameters. So is there a bug in MapEntity, or is routine that called it feeding it an invalid parameter?

One of the advantages of an assertion/contract is that it clearly documents the fact that you anticipated this problem and explicitly did not want it to happen. This makes figuring out how to fix it that much easier because it makes it easier to determine the source of the problem. In this case, it shows clearly that the problem is in the caller, and not in MapEntity.

On the other hand, sometimes I'll get an assertion failure in code that's receiving perfectly cromulent parameters, because the requirements have changed. This helps me see what has to be changed, and sometimes it's not as simple as "argument must not be null". Sometimes the argument has to have certain properties set certain ways or data would have been silently corrupted. Having an assertion in place makes it easier to remember what I was thinking when I wrote it, so I can fix the code more easily.