Writing a good Debug.Assert
I find it interesting that even after working on .NET code for years, I still keep evolving my usage of the platform and style. Today's post is an example of such a thing.
Hopefully you already know about the Debug.Assert API. This allows you to make assertions about how your code behaves, and works only on debug builds (technically, on any build that defines DEBUG). On release builds, this call (and the calls used to build up its arguments) are compiled out of the binary.
This means, for example, that it's appropriate to use it to check locally what has already been checked elsewhere, but it shouldn't be used to check parameters on public method - you'd want a real check with an exception thrown for the latter case.
One of the overloads for the Assert method takes the condition to be asserted and a message to output. A good message is very useful, as it helps you to track down exactly what went wrong.
Typically I start my assertion messages with the condition, especially for something simple. Here's an example.
public void Foo(string text) {
if (text == null) throw new ArgumentNullException("text"); // remember this is public!
InternalFoo(text);
}
private void InternalFoo(string text) {
Debug.Assert(text != null, "text != null");
Console.WriteLine("Value: " + text);
}
This example shows that for simple parameter checks, the assertion text is probably enough. If the assertion fires, you'll get a stack trace leading to the method with the assertion, and the message identifying which parameter failed.
For non-trivial cases (pretty much anything that isn't a direct fault of the caller with a bad argument), I like to include a short explanation that gives an idea of (a) why I think the check should be an assertion rather than a runtime check, and (b) who is to blame.
A classis non-trivial example is when a value is captured in one method but used in another. For example, let's say we have a Foo class that can write its argument to the console, but takes the argument in the constructor. Here's how we might code it.
public class Foo {
private readonly string textField;
public Foo(string text) {
if (text == null) throw new ArgumentNullException("text");
this.textField = text;
}
public void WriteItOut() {
Debug.Assert(this.fieldText != null, "this.fieldText != null -- otherwise .ctor should have thrown an exception");
Console.WriteLine("Value: " + this.fieldText);
}
}
This is the typical format I'll use - first the assertion code, then a little separator and an "otherwise" explanation, pointing to who I think should have checked this before. Of course this isn't infallible - for example, if textField later changes to not being readonly and a different method changes the field to null after the constructor has run, then the assertion might be misleading. But in general (more so the more consistent you are in validation designs), the assertion message is a very good hint, one that may often save you from even having to fire a debugger to figure out where your bug is.
Enjoy!
Comments
Anonymous
October 24, 2008
Good post. Now I always ponder if the comment on the assertion should be for the true or false sides of the assertion. For example in your sample you could also write it as: Debug.Assert(this.fieldText != null, "this.fieldText is null and we need it to print it to the console -- someone may have changed it after the ctor"); I guess the thinking on this is that when you (or someone else that uses the code) sees the assertion poping, the message tells him what actually failed. Thoughts? JaimeAnonymous
October 24, 2008
Jaime, I always pondered the same thing, until I came up with these rules: (1) The text of the assertion is the actual code of the assertion, followed by ' -- ' and an explanation of who/what is to blame otherwise. (2) OR, the text of the assertion is completely free-form English and then it better be non-ambiguous about what failed. Your snippet is a great example of (2), although I typically reserve it for complicated/obscure expressions.Anonymous
October 24, 2008
Great post. Actually, the Debug.Assert implementation covers the case of Marcelo and the point brought up by Jaime. Just use the following overload: Debug.Assert(Boolean, String, String) The last string parameter is your detail message, so for instance you might have: // reactive assert Debug.Assert(segment != null, "segment == null", "Calling public method is expected to fail."); // proactive assert Debug.Assert(member != null, "member == null", "GetCustomAttribute will fail with ArgumentNullException.");Anonymous
October 25, 2008
You've been kicked (a good thing) - Trackback from DotNetKicks.com