08 Oct 2012

Functionality != Semantics

Very rarely, a case comes up where a new function is required even if it is functionally identical to another one. In these cases another meaning is attached to these functions beyond their interface, and machine instructions; reusing it will actually be counterproductive. We examine such a case I encountered at my workplace.

Assertions

I’m sure you’re wondering what the heck I’m talking about- reuse is a cornerstone of programming! Well, consider the case of assertions. Peppered throughout the codebase of a project, are macro invocations to FOO_ASSERT, which is defined thus:

#ifdef ENABLE_ASSERTIONS
#define FOO_ASSERT( COND, MESSAGE ) \
    if ( !(COND) ) {\
        printf(\
        "===========================================\n"\
        "Failed Assertion at line " __LINE__ " in " __FILE__ "!\n"\
        "Condition: " #COND "\n"\
        "Message: " MESSAGE "\n"\
        "===========================================\n"\
        );\
        exit(1);\
    }\
}
#else
#define FOO_ASSERT( COND, MESSAGE )
#undef

The assert macro provides information on the failure, including a message, and all is good. It also allows one to disable the assertion if required, during compilation. If we consider the interface and functionality of this macro, it achieves the following:

  • Check a condition COND
  • On failure:
    • Print a nice MESSAGE
    • Exit the program

Handling errors

As it turns out, given the above functionality, the macro started being used in smaller applications to do regular error handling.

FILE* file = fopen( "somefile.txt", "r");
FOO_ASSERT( file != NULL, "Could not open file!" );

The macro simplified handling errors such as opening files, while inadvertently corrupting its original meaning:

Assertions check invariants which have to hold during normal program execution. If a condition is not satisfied, the logic of the program broken.

Failing to open a file is not an invariant, and is not under the program’s control. The FOO_ASSERT macro is being abused to handle regular errors, and as a consequence, disabling assertions would now break these applications- the check has to be there to preserve functionality.

After doing some profiling on the algorithms used, more than 50% of the time was spent checking (legitimate) assertions. Without being able to remove their usage, the code is stuck with the assertions. Granted, speed/efficiency was not a requirement, but that is a lot of wasted cycles simply due to the misuse of a macro.

Conclusion

Handling assertions and regular errors are fundamentally different, even if functionally they are the same from the perspective of the source code. This suggests that the usage/semantics of a construct is not only tied to its functionality, but also to its documentation and project coding style.

  • c++
  • design