Sunday, July 15, 2007

Debug.Assert + Custom Exceptions = NICE!

Thanks to chapter 3 of Debugging Microsoft .NET 2.0 Applications by John Robbins I have been working to implement debug.assert statements throughout my code. If you are new to debug.assert (as I was) here's a short summary of the benefit they provide.

A debug.assert statement typically looks like this:

Public Sub LoadData(Byval employeeId as integer)
  debug.assert(employeeId > 0)
End Sub

First of all, a debug.assert statement will only be executed in debug mode. So when you compile your project in release mode these lines will be omitted by the compiler. You can verify this setting by going to Project Properties --> Compile tab --> Advanced Compiler Options button and look at the "Define DEBUG constant" setting. You should find that this checkbox is checked for a debug builds and unchecked for release builds. For an ASP.Net project this setting is maintained in web.config in the <compilation> section definition. Just search web.config for the word "debug" and you will find the setting.

Assertion pop up a message box that allows you to go into break mode at the point in code where the debug.assert statement failed. Using the example above, this can be very helpful because if the calling method passed in 0 or a negative number for the employeeId this method is not going to truly succeed and you may get some failure way down the line in your code as some screen or method tries to use the results of the LoadData method. Those routines may not have a direct call stack back to the routine that passed the 0 to the LoadData function and this can slow your debugging. With assertions properly placed and each argument tested for validity you will break into break mode in Visual Studio as soon as something unexpected occurs and not further down the line where you may end up with a blank screen (becaues no data was found) or an unhandled exception. In ASP.Net an unhandled exception can be a real time killer because you may get redirected to an error page, you may have to stop the project, start the project, retrace all of your steps (and you may not remember them!), etc. So, breaking before an error happens can be a huge time saver.

One important thing to note about assertions is that they will dump you into break mode only if they fail. So, when you are writing an assertion you need to write the assertion in such a way as to say, "The valid values for this variable are...". For example, debug.assert(employeeId > 0) is saying, "The valid values for the employeeId are only numbers greater than zero." If the value does not meet that criteria the assertion fails and you get the opportunity to go into break mode.

The last important point to reiterate is that although assertions help you in debug mode they do not replace exceptions. Because of this, I found myself writing code that looked like the following. I had to write 1 line for a debug statement and 1 line for an exception. Then I had to do it for every parameter for every method in every class of every assembly for the rest of my life. This was quite repetitious and not very adventurous!

Public Sub LoadData(Byval employeeId as integer)
  If employeeId <= 0 then

    dim errMsg as string = "employeeId <= 0" 'I used .fail here because I want it to fail everytime since I already know my argument is out of range.
    throw new ArgumentOutOfRangeException(errMsg)
  end if
End Sub

Having the debug statement was nice because in debug mode I would hit the debug statement before the exception was thrown so Visual Studio would give me a chance to look over my callstack and find the flaw without having to suffer through hitting the exception line. However, I found this to be very verbose. Especially for methods that received several arguments. Nevertheless, I continued to implement it because almost immediately after adding debug statements in a few methods I was already experiencing the benefits.

Here is what qualifies this as an adventure.
Despite having written a macro to generate this pattern for me, I decided I needed a more succinct method that would ease the maintenance on all of this code that I was auto generating.  Here is my final implementation:

Public Sub LoadData(Byval employeeId as integer)  
  If employeeId <= 0 then
    throw new ArgException("employeeId <= 0")
  end if

End Sub

Now, you must be wondering, "This guy just said he loved asserts but he just deleted the assertion! What is he thinking?" Here's how I did it.

1) I created a base exception class called ExceptionBase that inherits from System.ApplicationException.

Public Class ExceptionBase
Inherits System.ApplicationException

  Public Sub New(ByVal message As String), Nothing)
  End Sub

  Public Sub New(ByVal message As String, ByVal InnerException As System.Exception) & DebugUtils.Assert(message), InnerException)
  End Sub
End Class

2) I created other custom exception classes that inherit from ExceptionBase. For example, I created a ArgsException class and a DataEOFException class. Each class has different constructors based on their special needs. Here are abbreviated samples:

Public NotInheritable Class ArgException
Inherits ExceptionBase
Private Const ERROR_MSG As String = "Invalid argument value: "

  Public Sub New(ByVal message As String)
    MyBase.New(ERROR_MSG & message)
  End Sub
End Class

Public NotInheritable Class DataEOFException
Inherits ExceptionBase
Private Const ERROR_MSG As String = "Unexpected EOF: "

  Public Sub New(ByVal cmd As DbCommand)
    MyBase.New(ERROR_MSG & DBUtils.GetCommandInfo(cmd))
  End Sub

  Public Sub New(ByVal sql As String)
    MyBase.New(ERROR_MSG & sql)
  End Sub
End Class

3) Here's the tricky part in case you missed it. If you look back at the BaseException class in the Sub New constuctor that actually calls the System.ApplicationException base class you will see this line of code: & DebugUtils.Assert(message), InnerException)

Because a constructor calling its base constructor must do so on the first line of code I cannot write the code like this:

    DebugUtils.Assert(message), InnerException)

So I had to add the call to my custom Assert method right inside of the call to the base constructor. Now, all that was left to do was write the simple Assert method in my DebugUtils module and I was all set.

Friend Module DebugUtils
  Friend Function Assert(ByVal message As String) As String
    TracingUtils.TraceData(TraceEventType.Error, 0, message)
    Return String.Empty 'always nothing
  End Function
End Module

Note that I had to make the Assert method a function in order to call it inline with the call to the base constructor so I simply made Assert return an empty string.

Also note that I implemented a call to my TracingUtils class inside of my Assert method so that if an error happens in a release build and the debug.assert statement is ignored at least I will get something written to my trace logs.

Also note that I declared my DebugUtils class with a "Friend" scope. This was because I did not want to make this method public and accessible to code outside of this assembly. The reason for that is because if the 2 assemblies were compiled in different modes (1 in debug and 1 in release) these debug statements would not behave as expected. So, if a different assembly wants to use any of the functionality within the DebugUtils module that assembly should contain its own copy (using a "Share") of the DebugUtils module.

In summary, I have decreased the lines of code that I have to write for each argument that I test within each method but I have not lost my beloved debug assertions.

Another nice thing to note is that I was able to use the regular expressions feature within VS 2005's Find & Replace screen to do a multiline find & replace. This allowed me to strip out all of the "" from all my methods and take out the newline after them (using \n for newline) so that I was not left with a bunch of blank lines in my source code. So, if you haven't been working on your regular expressions skills there is just one more reason to improve.

Thanks for joining me on this adventure.

1 comment:

Wave Warior said...

Great article, I've been looking for a alternative way to use the Debug.Assert, since it does not work well in ASP.NET.

Also have a look at the concept used in the following article (stop the debugger before it hits the actual Debug call):