ArgumentHelper and Consistent Exception Messages

The proper handling of exceptions in software is critical to the success of an application. No matter how elegant and bulletproof you *think* your code is, exceptions are a fact of life and development teams must decide how to deal with them for the application they are building.

How and when to raise exceptions must also be considered and should not be taken lightly. One common practice is to perform parameter checking at the start of each method that requires it and throw exceptions of the appropriate type if the check fails. For example, the code below shows me checking if a User object is null before trying to add it:

public static int AddUser(User user)
{
    if (user == null)
    {
        throw new ArgumentNullException("user", "Cannot be null.");
    }
 
    // Continue... 
}

There's nothing wrong with this code of course, but now imagine that not only do I need to perform a null check, but I also need to check if the User object is valid (using a validation framework such as EViL) and I need to check if the User already exists. Knowing that, my code ends up looking like this (where the GetUser method is defined elsewhere):

public static int AddUser(User user)
{
    if (user == null)
    {
        throw new ArgumentNullException("user", "Cannot be null.");
    }
 
    if (!user.IsValid())
    {
        throw new ArgumentException("Invalid entity.", "user");
    }
 
    if (GetUser(user.Username) != null)
    {
        throw new ArgumentException("Already exists.", "user.Username");
    }
 
    // Continue... 
}

Again, there's nothing wrong with this code, but it seems to take up quite a bit of space to do parameter checking, all before I do any real work. A better approach would be to refactor parameter checking into another class, such as ArgumentHelper.

ArgumentHelper

This is something Will found awhile back (from this CodeProject article) that we started using on one of our projects together and now I make sure to take it with me everywhere. Basically, it allows you to collapse all your parameter checks into a single line (which technically you could do anyway but now it shoves that responsibility into another class). So using ArgumentHelper I can refactor the above code to look like this instead:

public static int AddUser(User user)
{
    ArgumentHelper.AssertNotNull<User>(user, "user", "Cannot be null.");
    ArgumentHelper.AssertIsValid<User>(user, "user", "Invalid entity.");
    ArgumentHelper.AssertNull<User>(GetUser(user.Username), "user.Username", "Already exists.");
 
    // Continue... 
}

That's much better. It's very clear, concise, and don't you just love how the methods in ArgumentHelper start with the word Assert (mmm... TDD). Since this post is already going long and I have more to say, I'll save showing the ArgumentHelper code here and instead direct you to its spot in CodeKeep.

Consistent Exception Messages

One of the things that drives me nuts is inconsistent exception messages, especially for the same exception types. For instance, when throwing ArgumentNullExceptions, I've seen developers, on the same team, use the following messages:

"Cannot be null"
"Must not be null"
"Required"
"Is required"
"Must be populated"
"Cannot pass null"

And the list goes on. So which one is it? I mean, honestly, is it really that hard to actually *talk* to the other developers and come to some sort of consensus? Apparently it is, so I've come up with a more strongly-typed solution to the problem (which is a bit different than the solution posed in the above CodeProject article). What I've done is created an enum where each member is decorated with a description attribute that gives the actual text of the exception message, as shown below:

public enum ExceptionMessage
{
    [Description("Cannot be null.")]
    CannotBeNull,
 
    [Description("Cannot be null or empty.")]
    CannotBeNullOrEmpty,
 
    [Description("Must be greater than zero.")]
    MustBeGreaterThanZero,
 
    [Description("Already exists.")]
    AlreadyExists,
 
    [Description("Does not exist.")]
    DoesNotExist,
 
    [Description("Invalid entity.")]
    InvalidEntity
}

And in case you're interested, the DescriptionAttribute looks like this:

[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
public sealed class DescriptionAttribute : Attribute
{
    private string _text;
 
    public string Text
    {
        get { return _text; }
    }
 
    public DescriptionAttribute(string text)
    {
        _text = text;
    }
}

What this allows me to do is refactor the ArgumentHelper class to include overloaded methods that accept the ExceptionMessage enum for the exception messages instead of plain old strings. Doing so allows my AddUser method from above to now look like this:

public static int AddUser(User user)
{
    ArgumentHelper.AssertNotNull<User>(user, "user", ExceptionMessage.CannotBeNull);
    ArgumentHelper.AssertIsValid<User>(user, "user", ExceptionMessage.InvalidEntity);
    ArgumentHelper.AssertNull<User>(GetUser(user.Username), "user.Username", ExceptionMessage.AlreadyExists);
 
    // Continue... 
}

And with this I now have a strongly-typed way of ensuring that the development team is throwing the proper exception types and that they contain consistent messages. Nothing but goodness.

So, having gone through all this, how do you and your team handle this type of problem? Have you put similar things in place or are inconsistent exception messages the norm? Feel free to comment and let me know.
Print | posted on Monday, May 21, 2007 11:21 AM

Feedback

# Link Dump for Monday, May 21, 2007

left by michael eaton at 5/21/2007 4:28 PM
Link Dump for Monday, May 21, 2007

# re: ArgumentHelper and Consistent Exception Messages

Gravatar left by moke at 5/21/2007 9:05 PM
I am also using ArgumentHelper in my projects to do parameter validation.

# re: ArgumentHelper and Consistent Exception Messages

Gravatar left by Rob at 5/22/2007 9:28 AM
Thanks for this Dave, but I have one question. Where will the description for the enum ultimately be used? For example is there a function that displays a message on the screen based on an emum type, and is the message obtained from the enum type description. If not why not just use a meaningful enum without the description and provide a function to lookup the message from say a resource file based on the enum int value?

Regards

Rob.

# re: ArgumentHelper and Consistent Exception Messages

left by KevinUp at 5/22/2007 10:19 AM
Hmm, seems familier to an email I sent you in January, I guess you've cleaned it up a little but I still think it was my idea :)
-Kevin

# re: ArgumentHelper and Consistent Exception Messages

Gravatar left by Dave at 5/22/2007 10:26 AM
Kevin - Actually, I completely forgot about that email, but that's not where ArgumentHelper came from (honestly). And besides, the CodeProject article was written *before* you sent me the email, so it's not your idea anyway :-D

# re: ArgumentHelper and Consistent Exception Messages

left by KevinUp at 5/22/2007 11:29 AM
True, I guess I would have know that if I would have RTFA :), I think the other thing to point is your code coverage benefits from this approach. Especially if you have some automated null checking (http://kevinup.wordpress.com/2007/05/15/tdd/) or some variation to it, and you can make sure you're covering all your bases, while writing less code.

# re: ArgumentHelper and Consistent Exception Messages

Gravatar left by Dejan Vesić at 5/23/2007 9:18 AM
Nice and clean solution.

But what about I18N issues? How to store Description in satellite assembly and how to display proper message based on CurrentCulture / CurrentUICulture?

Regards,
Dejan

# re: ArgumentHelper and Consistent Exception Messages

Gravatar left by Dave at 5/23/2007 12:17 PM
Dejan,

You could certainly modify this solution in such a way that instead of passing the message to the Description attribute, you could pass some sort of key, which the attribute would then use with the culture to lookup the actual message in a resource file/assembly.

# re: ArgumentHelper and Consistent Exception Messages

Gravatar left by Golf Scrambles at 6/14/2007 10:18 PM
No Null Checks? Life just got a little bit sweeter ;)
Comments have been closed on this topic.