Practical .NET

Keep Track of What Your Code Really Did

When it's important to know what path your application took when processing data, a log of that path can be helpful. And, when you need to make a decision in your code based on an earlier decision, that internal path can make your code both simpler and easier to understand.

Any reasonably complex application has multiple paths through it. When your application gives you results you don't expect, it can be very helpful to find out which path, exactly, your application followed when generating that surprising result. In these situations, a "state path" object that allows you to record the path taken can be helpful.

Fundamentally, with a state path object, whenever you make a decision in your application you add a state value to a list. At the end of processing, you can write that list of states to a log file, along with any other relevant information, that you need to support debugging (for writing out that log, I've been using Nlog in my most recent applications). Think of this "state path" as a poor man's IntelliTrace. If you go one step further (and if you're clever with hex values and bitmapping) you can even summarize that path into a single number.

In addition to being a valuable tool when tracking down some unexpected result, I've also found having a state path object helpful when later processing in an application is driven by earlier decisions made in the application. One solution is to create some random set of Boolean variables that you can use as flags to indicate earlier decisions and then test against in later code. However, if you're already maintaining a state path, that path also makes it easy to check to see what decisions were made earlier when processing the data.

Before creating your state path, you need to create a list of enumerated valuables representing your application's states. This example is from a recent application, which ended up having some two dozen different states:

Public Enum ProcessingStates
 Redundant = &H40000
 BadDataId = &H10000
 InProcessing = &H1000000
 ProcessingOff = &H4

You don't have to get this list right the first time -- you can add values to this list as you need them. In this example, I've also set the items in the enumeration to hex values to support extracting states using bit mapping. Assigning hex values is optional but can be useful (more on that later).

Keeping the State List
To manage my state path, I use a class I call StatePath. I have this class implement the IFormattable interface (which is also optional but can be useful -- I'll discuss that later, also).

Within the StatePath class, I declare a private list to hold my enumerated values. I declare this list as private to ensure that no code other than code in the StatePath class can alter the list. Because enumerated values are, under the hood, an integer value, I also declare an Integer property that holds the total value of the enumerated values in the state:

Public Class StatePath
  Implements IFormattable

  Private Property PathList As List(Of ProcessingStates)
  Public Property PathNum As Integer 

In the constructor for the class, I initialize the variable holding the list of states and my PathNum property:

Public Sub New()
  PathList = New List(Of ProcessingStates)
  PathNum = 0
End Sub

Because developers can't access the list directly, I provide a method called UpdatePath that accepts one of my enumerated values and then adds it to the end of the list. In this particular application, I want to ensure that no state is added twice so this code also checks to make sure that the state isn't already in the list, using another method in my class (IsStateInPath):

Public Sub UpdatePath(processState As ProcessingStates)
  If Not IsStateInPath(processState) Then
    PathList.Add(processState)
    PathNum += CType(processState, Integer)
  End If
End Sub

To use the class, a developer would first declare a variable to hold the object (probably at the class level so the StatePath can be used from multiple methods) and loads an instance of the class into the variable:

Private spth = New StatePath()

Now, to add a new state to the list, the developer just calls UpdatePath and passes one of the enumerated values:

If (prev.ReadDate - current.ReadDate).Duration.TotalSeconds < 4 Then
  spth.UpdatePath(ProcessingStates.Redundant)

To ensure that all the states that the application records are represented in the list I don't provide a RemoveStatus method.

Checking for States
There are two ways to write the IsStateInPath method that returns True when passed an enumerated value that's already in the list. One way is to check the list to see if the enumerated value is already present using some obvious code:

Public Function IsStateInPath(PathState As ProcessingStates) As Boolean
  Return PathList.Contains(PathState)
End Function

The other way is to use bitmapping against the number that summarizes the values in the list (this assumes you've been clever in assigning hex values in the list of enumerated values). The resulting code is probably more efficient but is less obvious:

Public Function IsStateInPath(PathState As ProcessingStates) As Boolean
  Return Integer.Parse(PathState) = (Me.PathNum And PathState)
End Function

Rather than write a comment explaining what the second version was doing (and because I wasn't worried about efficiency when searching the typical list of five to six states in the state path), I went with the obvious version. You might make a different decision.

Now, in my application when a developer wants to see if a particular decision was made earlier on, code like this will answer the question:

If spth.IsStateInPath(ProcessingStates.Redundant) Then

Displaying the List of States
The most complicated code is the code that handles outputting the list of states. To make the log as easy to read as possible, I want to output the enumerated values by name rather than by value. To handle that, I first create a StringBuilder to handle concatenating together the names of the enumerated values. I then loop through the path list, using the Enum object's GetName method to retrieve the name of each enumerated value and append that name to the end of my StringBuilder with a delimiter:

Dim stb As New StringBuilder()
For Each ps As ProcessingStates In Me.PathList
  stb.Append([Enum].GetName(GetType(ProcessingStates), ps) & ":")
Next

However, I also want my StatePath to support three other outputs, which I've found useful when debugging:

    • PathNum as a decimal value • PathNum as a hexadecimal value (this value was actually stored in the database with the record being processed) • A combination of the hex PathNumber and the path list

The benefit of outputting the PathNum is that, for any application, I soon come to recognize specific PathNum values. I'm not in the league of the embedded programmer on one team I worked with who could decode the PathNum's hex values in his head … but I can come to recognize that particular PathNums represent specific paths through the application.

This is where the IFormattable interface is useful because it supports ToString methods that accept formatting strings which allow me to specify which output I want. Listing 1 shows my StatePath's two ToString methods, supporting the four output formats I've defined (for more about the IFormattable interface, see my earlier article).

Listing 1: Formattable String Methods
Public Overrides Function ToString() As String
  Return Me.ToString1("G", Threading.Thread.CurrentThread.CurrentCulture)
End Function

Public Function ToString1(format As String, formatProvider As IFormatProvider) As String Implements IFormattable.ToString
  Dim stb As New StringBuilder()
  Select Case format.ToUpper
    Case "D"
      stb.Append(PathNum.ToString())
    Case "G", Nothing
      stb.Append(PathNum.ToString("X") & ",")
      For Each ps As ProcessingStates In Me.PathList
        stb.Append([Enum].GetName(GetType(ProcessingStates), ps) & ":")
      Next
    Case "X"
      stb.Append(PathNum.ToString("X"))
    Case "P"
      For Each ps As ReadingsSelectState In Me.PathList
        stb.Append([Enum].GetName(GetType(ProcessingStates), ps) & ":")
      Next
  End Select
  If stb.Length > 2 Then
    Return stb.ToString
  Else
    Return ""
  End If
End Function

My StatePath is a useful enough object that I've used it in multiple projects, just changing the names of the values in the enumerated list to match each application's state. If you're having trouble determining what actually happened in your application, you might find it equally useful.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube