Sergey Smelov Sergey Smelov - 1 month ago 11
C++ Question

How to return optional debug information from a method?

Lets say I have a static method which compares two objects for a close match and returns some confidence level [0,1].

class Foo
{
...
static double Compare(const Foo& foo1, const Foo& foo2);
...
};


Now I need to return additional debug information containing comparison details depending on a setting in the configuration.
Since this debug information won't be used at production, but only in testing/debugging purposes I was wondering what is the appropriate way to implement it.

I see at least three options:

1: Create an additional class CompareResult and store confidence + optional info there. Don't populate the optional info if you don't need to.

class CompareResult
{
...
private:
double confidence_;
CompareOptionalInfo compare_optional_info_;
...
};

...

static CompareResult Compare(const Foo& foo1, const Foo& foo2);


It seems to be the cleanest one, but I'm not sure if I should combine return result with an optional info.

2: Use output variable (this way we won't need to create an additional class, but our method signature will grow a bit)

static double Compare(const Foo& foo1, const Foo& foo2, CompareOptionalInfo* out_compare_info = nullptr);


3: Separate compare method with optional-info retrieval method.

static double Compare(const Foo& foo1, const Foo& foo2);
static CompareOptionalInfo GetCompareOptionalInfo();


This option would probably require to store this optional-info between the method invocations and shift from static compare method to the instance compare method.
But again, I'm not sure if it is appropriate or not.

From your experience, what is the appropriate way in OOP world to return optional information (which will be mostly used only in debug mode) from a method?

Answer

Option 3 is not a good idea at all : having functions depending on static data is not practical and could become even be a source of bugs in the debugging. Such a design is in addition not be thread safe ; what a pity it would be, to create such a restriction only for debugging purpose !

Examples of problems:

double closest = std::max (Foo::compare (x, y), Foo::compare (y,z));   
clog << Foo::GetCompareOptionalInfo();  // undefined which info since order of eval
                                        // of function parameter is not guaranteed

double d = Foo::compare (x, y); 
DoSomething();                    // what if this one does an unexpected  compare ? 
clog << Foo::GetCompareOptionalInfo();  

Option 2 is a viable solution, but it's not very convenient: it forces you to create info objects, pass them by address etc:

Foo::OptionalCompareInfo o1,o2;    // cumbersome 
double closest = std::max (Foo::compare (x, y, &o1), Foo::compare (y,z, &o2));   

In addition, you would create these optional infos and pass the extra argument in production as well, even if you no longer update the info there (unless you put a lot of extra conditional compilation) !


Option 1 is excellent ! Go for it ! It really takes benefit of the OOP paradigm and uses a clean design. It's practical to use and don't impose constraints on your code to use it.

All you need is to provide some (implicit) conversion function to use your CompareResult almost as if it were a double:

class CompareResult
{
public:
  CompareResult(double d=0.0) : confidence_(d) {}; 
  operator double() { return confidence_; }
  operator bool() { return confidence_>0.5; }   
private:
  double confidence_;
  CompareOptionalInfo compare_optional_info_;
};

online demo

Your production code would be unaffected by the debugging information. And you could in your debugging ALWAYS trace back the explanation of any given comparison result, at least if you store it:

Example:

auto result = Foo::compare (x, y)
if (result) ... // uses automatically the conversion

auto closest = std::max (Foo::compare (x, y), Foo::compare (y,z));
// here you not only know the closest match but could explain it ! 

vector<CompareResult> v;  
... // populate the vector with 10 000 comparison results
auto r = std::max_element(v.begin(), v.end());  
    // you could still explain the compare info for the largest value 
    // if you're interested in the details of this single value
    // how would you do this with option 3 or option 2 ?     

Ok, for the very last one to work you'd also need a comparison operator for your additional class. But that's one line of code more (see the online demo) ;-)

Finally, it could turn out that your "optional debug info" could prove to be more useful than expected, for example to provide the user additional explanations upon request. All you need to do then, would be to remove the conditional #ifdef DEBUG surrounding the calculation of the optional info.