Chapter 16. Making Your Code Debugger-Friendly

Have you ever tried to look inside some object in the debugger and been frustrated that the debugger shows the details of the object’s physical implementation instead of the logical information that the object is supposed to represent? Let me illustrate this using an example of a Date class that represents calendar dates, such as December 26, 2011. If you look into this object in the debugger, chances are you will not see anything resembling “December 26, 2011” or any human-readable information at all, but rather an integer that requires some decoding to convert into a date it represents.

It all depends on how the Date type is implemented. I have seen the following three implementations:

  1. class Date {
      // some code
    
     private:
      int day_, month_, year_;
  2. typedef Date int; // in YYYYMMDD format
  3. class Date {
      // some code
    
     private:
      int number_of_days_; //  Number of calendar days since the "anchor date"

The first implementation is pretty self-evident and is a pleasure to debug. In the second case, the date December 26, 2011 is represented by an integer 20111226, which is also easily readable by a human once you know the formula behind it.

In the last case, the internal representation of a Date is the number of days that have passed since some arbitrarily chosen date far enough in the past, that the day represented by 1 is 1/1/1900 or 1/1/0000 or something of this sort.

While the first two implementations are very debugger-friendly, they have a serious problem. The Date type is supposed to support “date arithmetic,” i.e., operations such as adding a number of days to a date, or calculating the number of days between two dates. In the cases of implementations 1 and 2 such number arithmetic is extremely slow, while in the case of implementation 3 it is as efficient as adding and subtracting integers.

For this reason, any serious implementation of Date uses approach 3. However, when you look at this Date object in the debugger, it is a pain to figure out what the actual calendar date is. For example, in the class Date we will consider momentarily, the date December 26, 2011 looks like 734497 in the debugger, and when you are working with code that contains a lot of dates—for example, some financial contract that pays quarterly for the next 30 years, and also has some additional dates a couple of days before each payment date relevant for calculation—debugging becomes a challenge.

But it doesn’t have to be. The solution to this problem is to make the code of the class Date “debugger-friendly,” meaning that when compiled in debug mode, it provides additional information in the debugger to represent the date in a human-readable form (either as “December 26, 2011” or at least 20111226). However, given that this additional functionality requires some calculations and increases the size of the object, I’ve decided to compromise and settle on the second solution, representing the debugging info of the date in YYYYMMDD format, i.e., as 20111226.

The complete source code for the class Date is provided in Appendix J in the scpp_date.hpp and scpp_date.cpp files. Here I just include snippets from these files that provide this additional debugging information. In the header file we find:

class Date {
 public:
  // some code

 private:
  int date_; // number of days from A.D., i.e. 01/01/0000 is 1.

#ifdef _DEBUG
  int yyyymmdd_;
#endif

void SyncDebug() {
#ifdef _DEBUG
  yyyymmdd_ = AsYYYYMMDD();
#endif
}

void SyncDebug(unsigned year, unsigned month, unsigned day) {
#ifdef _DEBUG
  yyyymmdd_ = 10000*year + 100*month + day;
#endif
}
};

First, the implementation is based on a number of days since some day in the past. In addition, when compiled in debug mode, the symbol _DEBUG is defined and the class has an additional data member int yyyymmdd_, which will contain the date in the YYYYMMDD format. To fill this data member out, there are two functions SyncDebug(), so named because they synchronize the debug information with the actual date_ contained in the object. When compiled in release mode, these two functions do nothing, and in debug mode they update the yyyymmdd_ data member. These functions are called from every non-const method of the class after modifying the date_ data member, for example:

Date& operator ++ () {
  ++date_;
  SyncDebug();
  return *this;
}

// some other non-const methods

Date& operator += (int nDays) {
  date_ += nDays;
  SyncDebug();
  return *this;
}

// even more non-const methods

and also in a constructor:

Date::Date(unsigned year, unsigned month, unsigned day) {
  SCPP_TEST_ASSERT(year>=1900, "Year must be >=1900.")
  SCPP_TEST_ASSERT(JAN<=month && month<=DEC,
    "Wrong month " << month << " must be 1..12.")
#ifdef SCPP_TEST_ASSERT_ON
  unsigned ml = MonthLength(month, year);
  SCPP_TEST_ASSERT(1<=day && day<=ml,
    "Wrong day: " << day << " must be 1.." << ml << ".");
#endif
  int n_years_before = year - 1;
  date_ = 365*n_years_before + n_years_before/4 - n_years_before/100
          + n_years_before/400 + day + NumberOfDaysBeforeMonth(month, year);

  SyncDebug(year, month, day);
}

Figure 16-1 shows how the Date object looks in the XCode debugger as a result of all this additional activity in debug mode.

Looking at the “debuggable” Date classDate object in the XCode debugger

Figure 16-1. Looking at the “debuggable” Date object in the XCode debugger

The variable d of type Date is shown in the upper right columns. In the “Arguments” column find d, and under it you can see its data members, while in the next column, “Values,” you can see that:

  • date_ is equal to 734497.

  • yyyymmdd_ is equal to 20111226.

The presence of the latter value makes decoding the date in the object as easy as separating the last two pairs of digits from the first four.

The example of the Date class discussed here is just that: an example of an approach to making your class friendly to a debugger. I started to work on this mostly out of frustration when trying to look into STL containers in the debugger and finding a lot of interesting details about their implementation instead of what numbers or strings or other objects they actually contained. Making STL containers debugger-friendly on the level of code could be (and was) done, though it makes the code compiled in debug mode exceptionally slow. However, this problem was addressed recently on the level of the debugger: Microsoft Visual Studio 2010 shows the logical contents (as opposed to implementation details) of STL containers, such as a vector, set, or map (Figure 16-2).

STL vector, set, and map in the Microsoft Visual Studio 2010 debugger

Figure 16-2. STL vector, set, and map in the Microsoft Visual Studio 2010 debugger

Thus, there is hope that this idea will soon reach debuggers working under Unix, Linux, and Mac OS too.

In the case of a specific class you create, if its implementation differs from the logical information it represents, it is up to you to make it debugger-friendly. Usually it is not difficult, and you will be glad you did it as you debug your program.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.165.115