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