Saving 10 Minutes on Documentation Wasted Hours of My Client’s Time

A recent experience drove this point home for me: insufficient documentation wastes everyone’s time.

In Adding Delimiters Between String Elements in C++, I described the creation of a “convenience function” that would simplify the use of a logger in a client project. When I made that change in the client’s code base, I added the new implementation and converted some of the existing logger call sites to the new style. We had already discussed these changes in the issue tracker, including examples of how I envisioned it being used. I also believed that my changes to existing logging calls would further show the function’s use. For these reasons, I figured it was “safe” to cut a corner: I did not write interface documentation for the functions.

The change passed review, so I merged it and pushed the changes from my mind. A few days later, the client started using the new logging function. I expected they would look at the example changes as a reference, and that they would recall what we discussed in the issue tracker. How naïve these expectations were. Much more naturally, they returned to the header file to look at the function prototypes.

That’s when everything went amiss.

The variadic template syntax was unfamiliar to them, as the existence of multiple logging functions added to the confusion (“which is the right one to use?”). They didn’t understand what valid input argument types were. They sent me some questions, but I did not notice until it was too late. So the client did their own research, decided it was safe to ignore the templated version altogether, and that my intent was for users to package everything into initializer_list<string_view> variables to pass in to the function. Eventually, I saw my e-mail inbox and quickly switched gears to get them back on track.

What a mess. Our client wasted a couple of hours that could have otherwise been spent making forward progress. I stopped what I was doing to provide support. Both of us were frustrated. All because I skipped out on ten minutes of documentation writing.

Let that be a lesson.

/** System Logging Interface
 *
 * There are three primary function variations that you will use:
 * - logLine, which simply takes an already-formatted string and writes it to the file as-is
 * - logEngLine(component_id, args...), which formats the supplied variadic argument list into
 *    the appropriate engineering log format.
 *     @code
 *     Logger::logEngLine(VERBOSE, "param1", "param2", string_param);
 *     @endcode
 *
 * The general format of a logged engineering line is:
 * - STX + component_id + DELIM + timestamp + DELIM + current state + [(DELIM + args)...] + ETX
 */
class Logger
{
  // ... elided details
  
  /** Save a line to the log file without further modification.
   *
   * This logging function should be used to write contents to the log file.
   * Unlike logEngLine, this function does not modify the supplied contents or
   * coerce them into any special format.
   *
   * @pre Logger module has been initialized
   * @post Input line has been logged to the currently open log file without
   *  further modification.
   *
   * @param verbosity [in] Verbosity level of the log statement.
   * @param line [in] The string contents to write to the log file.
   */
    static unsigned logLine(LogLevels verbosity, const std::string_view line);

  /** Logger "worker" function
   *
   * This function does the work of taking a list of string arguments and
   * formatting them into the proper engineering line format, e.g.,
   * [STX]param1[delimiter]param2[delimiter]param3[ETX].
   *
   * The actual formatting is controlled by the implementation of this function.
   *
   * Users are not required to invoke this function unless it is desired
   * to use a std::initializer_list. Instead the templated logEngLine variant
   * is intended to be used - this will coerce all the arguments into a list
   * and invoke this function.
   *
   * If you wish to use this version, parameters can be suppiled either with an
   * initializer_list variable, or in the following manner:
   * @code
   * Logger::logEngLine(VERBOSE, {"param1", "param2", string_param});
   * @endcode
   *
   * @pre Logger module has been initialized
   * @post Arguments have been formatted into engineering line format and saved
   *  to the currently open log file.
   *
   * @param verbosity [in] Verbosity level of the log statement.
   * @param args [in] List of arguments to format into an engineering log
   *
   * @input A std::initializer_list of string_view arguments that
   */
  static void logEngLine(LogLevels verbosity, const std::initializer_list<std::string_view> args);

  /** Log a list of parameters in engineering line format.
   *
   * This function does the work of taking a list of arguments and putting them
   * into the target enginering line format, such as:
   *  [STX]param1[delimiter]param2[delimiter]param3...[ETX].
   *
   * The actual formatting is controlled by the std::initializer_list variation
   * of logEngLine. This function is invoked like:
   *
   * @code
   * Logger::logEngLine(VERBOSE, "param1", "param2", string_param);
   * @endcode
   *
   * @pre Logger module has been initialized
   * @post Arguments have been formatted into engineering line format and saved
   *  to the currently open log file.
   *
   * @param [in] vs... Variadic list of values that are to be logged in engineering
   * line format. The parameters can be any type that can be implicitly converted
   * to std::string_view, including:
   *  - const char*
   *  - char[]
   *  - std::string
   *  - std::string_view
   */
  template<typename... Values>
  static inline void logEngLine(LogLevels verbosity, Values const&... vs )
  {
      logEngLine(verbosity, {vs...});
  }
};

References

2 Replies to “Saving 10 Minutes on Documentation Wasted Hours of My Client’s Time”

  1. IMHO: I would leave @pre and @post out of the documentation. @post just states what is already documented in the description and @pre could probably be an internal check.

  2. Sure, I agree on @post. But I don’t on @pre. It is an internal check (as preconditions usually are?). But it is a precondition of using the function, and the user should know what is required for it to work.

Share Your Thoughts

This site uses Akismet to reduce spam. Learn how your comment data is processed.