Sunday, January 29, 2012

Debug.Assert() – So, what about Release Mode?

Sometimes, as a developer, you create some little nifty tool to make life easier and you forget that other people may not have come up with that idea/solution and are still struggling with a problem you solved ages ago. 
I was having a Skype conversation with Pete Brown and he said “Why don't you blog this stuff, or set up a GitHub project or something with these things?”.  I told him, “well, actually, I just started blogging!”  
That conversation gave me a figurative kick in the tuchus, so I decided to share a small nugget.

Defensive Development


If you know me you know that I am a big fan of code instrumentation and “defensive development”. I think it’s the best way to protect yourself.  To me, developing defensively means that you are doing the following in your code:
Generally, you’re trying to “bullet-proof” your code.
Given that I think like this, I tend to use debug assertions a lot.



Debug.Assert


If you’re not familiar with Debug.Assert, it basically takes a boolean condition representing an “assertion” or assumption you are making at that point in your code.  It also takes an optional message that will be displayed on a modal dialog if the assertion fails.  The “Debug” part tells you that it will only run in Debug mode.  Essentially, internally, the implementation is wrapped in "#if DEBUG” conditional compilation.
So, Debug.Assert is  useful, but, what about Release mode?

What about Release Mode?


Well, in the realm of good, solid development, where you test everything thoroughly and have a long, drawn-out testing period before you deploy, Debug.Assert does the job well while not slowing down the executing code in deployment.
I think this is Utopian in this day and age.
For the large majority of developers, extreme performance is not a problem.  A few exceptions would be developers who work on:
  1. BIG games (not words with friends, more like Call Of Duty)
  2. Operating Systems
  3. Low-level Device Drivers
I’m sure there are a few more, but most of us work on Business Applications.  These applications don’t usually have extreme performance needs where a few extra “if” statements will be a problem.

Release.Assert?


Well, Debug.Assert pops up a dialog in the middle of a debugging session.  This is obviously not practical in Release Mode, since the user is most likely not a developer and is most likely not running the app inside Visual Studio.
Of course, you wouldn’t run Debug code in Production either… Right? RIGHT?
So, our only option is Exceptions.

My Solution


Well, since I don’t want to lose the nice Debug.Assert functionality of stopping my execution as it runs in debug mode, I decided to implement my solution in terms of Debug.Assert.  So, we have these requirements:
  • continue to utilize Debug.Assert
  • In Release Mode, throw an exception instead.
  • Allow for custom exception usage (can’t just throw one kind of exception)
So, we have the following method:

[SuppressMessage("Microsoft.Design", "CA1004", Justification = 
  "This method instantiates and throws the exception, it can't be passed in")]
[DebuggerStepThrough()]
public static void AssertThrow<TException>(bool condition, 
   string failureMessage) where TException : Exception
{
    Debug.Assert(condition, failureMessage);
    if (condition == false)
    {
        // throw exception here...
    }
}
The [SuppressMessage] is there in case you’re running Code Analysis. The analysis tools don’t like that the method is using a generic type that’s not part of the rest of the function signature.

The [DebuggerStepThrough] just makes it so that it steps over this as if it’s one execution statement when debugging.

The Debug.Assert line is rather obvious, so I’ll skip that.

The "where TException : Exception" part forces the type you specify to be a derived class of System.Exception, so it can be thrown.
The next section is where we throw the exception.

ConstructorInfo ctor = typeof(TException).GetConstructor(
   new Type[] { typeof(string) });
TException ex = (TException)ctor.Invoke(new object[] { failureMessage });
throw ex;

I’m using Reflection to get the constructor of the TException object that takes one string. Then, I am invoking that constructor with the failureMessage and throwing the exception.

So, all you do is call it like this:

void Foo(Bar bar)
{
    Diag.AssertThrow<ArgumentNullException>(
        bar != null, "Duh, Foo needs a Bar!");
        
    // use bar here.
}

Teh Codez


Here is the complete listing without all the line wraps and with comments. Once I set up a paid Github account or something like that, I’ll start moving code elsewhere:

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace McKearney.Common.Diagnostics
{
    /// <summary>
    /// Utility class that contains Diagnostic- and Instrumentation-related methods
    /// </summary>
    public static class Diag
    {
        #region Static Operations

        /// <summary>
        /// Asserts and then throws, making sure that the error gets handled by client code
        /// </summary>
        /// <typeparam name="TException">The type of the exception to throw.</typeparam>
        /// <param name="condition">the asserted condition</param>
        /// <param name="failureMessage">The failure message.</param>
        [SuppressMessage("Microsoft.Design", "CA1004", Justification = "This method instantiates and throws the exception, it can't be passed in")]
        [DebuggerStepThrough()]
        public static void AssertThrow<texception>(bool condition, string failureMessage) where TException : Exception
        {
            Debug.Assert(condition, failureMessage);
            if (condition == false)
            {
                ConstructorInfo ctor = typeof(TException).GetConstructor(new Type[] { typeof(string) });
                TException ex = (TException)ctor.Invoke(new object[] { failureMessage });
                throw ex;
            }
        }
    }
}

Leave me a comment, I'm curious what your thoughts are.

No comments:

Post a Comment