Code documentation
Current Repository Revision: 18
Something else I was working on this week made me think about code documentation. None has been done in the example code so far, but as part of the development process it would be good to set up an automatic, nightly build of documentation from the code. It’s better to do this now, as retro-documenting is a big task if there is a lot of code already in place!
The tool I tend to use for producing code documentation is Doxygen. It does an excellent job and, looking around, there isn’t anything better in the price range (free
). I did look at NaturalDocs recently, but while I like the more natural documenting style and the output, it doesn’t have full language support for C/C++ or Python. This means you don’t get anything for free – undocumented functions produce no output and there are no useful inheritance diagrams. According to the last news page I could find, version 2.0 is in development, so I may look at this again when some of it’s features are announced.
For now, on with Doxygen…
Marking up the code
The Doxygen website contains a good manual which can be read online or downloaded, so I’m not going to give a tutorial here on all possible combinations of special commands and magical incantations. I will, however, show some examples of how I mark up code.
My preferred method (from the many choices) for specifying a Doxygen comment block is:
//! This is a single line, brief description
/*!
This is a longer description
\param p Some parameter called p
\return Something is returned from this function
*/
This is referred to as the Qt style in the Doxygen manual.
I like to place the majority of the Doxygen documentation for C/C++ in the .h files, though a \file block is usually placed in the .cxx files and in test/example source files.
Note: I have found in the past that tabs in the comment block (for example, it may be tempting to tab the \param or \return lines) can put tabs into the final documentation, and make alignment messy. I always use spaces.
For documenting Python I use the style that enables the use of Doxygen’s special commands:
## This is a single line brief description # # This is a longer description # # \param p Some parameter called p # \return Something is returned from this function #
For Python I only document the mi_scons.py file in the example code, not the SConstruct or SConscript files.
Below are some more detailed examples of how the code is documented with Doxygen. The full documentation output for this can be seen here.
File Headers
At the beginning of each .h and .py file I place a header boilerplate. This block begins with a \file command, and contains descriptions (brief and/or detailed), and a change log. An example of a .h file boilerplate would be:
/*!
\file clever_class.h
\brief Defines MI_CleverClass to do something clever
\clog
Date Comment SVN Revision Developer
========== ================ ============ =========
2009/11/20 Initial draft 3 chris
2009/11/21 Added clever bit 4 chris
of code
\endclog
*/
The header template could also include license information. You could add a more detailed description if this file was defining more than just a single class, but you should remember that you are only stating here things relevant to the whole file, so don’t get into specifics about the contents. You could also add a \todo if it was relevant to the whole file. For example:
\todo Tidy up Doxygen comments
The equivalent in Python of the above boilerplate would be:
## # \file clever_class.py # # \brief Defines MI_CleverClass to do something clever # # \clog # Date Comment SVN Revision Developer # ========== ================ ============ ========= # 2009/11/20 Initial draft 3 chris # 2009/11/21 Added clever bit 4 chris # of code # \endclog #
The result of running these headers through Doxygen can be seen here for the .h file, and here for the Python file
Note: \clog is a command added as an alias in the Doxyfile configuration file (short for ‘change log’). If you use your own configuration file, \clog won’t work. Every \clog requires a matching \endclog.
For the rest of the examples you’ll see only the C++ style mark up, but you can easily infer the Python style from that.
Classes/Structures
For classes (and structures other than C-style, data-only structures) a brief description is given, followed by a more detailed description. Any \bug or \todo items relevant to the whole class can also be added. Again, be careful not to get into specifics here, just a general overview of the class, and any items relevant to the whole class. Try to avoid starting the comment with “A class to…”
.
//! Do something very clever
/*!
When something clever is needed, call on MI_CleverClass to do
it.
\todo Add a method to implement world peace.
*/
class MI_CleverClass
{
...
};
Methods/Functions
For methods and functions the style used is to give a brief, one line description, followed by a longer description (if necessary), similar to the class/structure comment. Then describe any parameters (for parameters that don’t change use \param and for any parameters expected to change we use \retval) and any return value (using \return). In addition, any known bugs for this method/function can be specified with \bug and anything that needs doing with \todo. If a method/function expects certain conditions to be true when entering, and they are not described in a \param or \retval command, then \pre commands should be used. If the function makes changes to the state of something, and that change is not described in a \retval or \return command, then a \post command should be used. Other, relevant functions should also be referenced with the \sa command.
//! Return the appropriate value for the caller's IQ
/*!
The caller passes an IQ, and the appropriate value is
returned.
\param iq The IQ to get the appropriate value for
\return A string containing a word appropriate to the
iq parameter.
\post The class will store the iq for use with calls
to \ref stringFromLastIQ
\sa stringFromLastIQ, displayStringFromIQ
\bug The method returns "76" when it should return
"mollycoddle"
*/
const std::string stringFromIQ(const unsigned int iq);
Variables
When marking up variables, whether they are member variables, global variables or any other, I usually use a single line brief-style comment.
//! Holds the last IQ used in \ref stringFromIQ unsigned int last_iq_;
They can be expanded to multiple lines if necessary, but (as with all documentation) are best kept short.
Enumerated types, data-only structs, unions and the like
Anything whose form is similar to that of an enumerated type is usually documented with a brief, one line description, a longer description (if necessary), and then a short description for each element.
//! The types of smells
/*!
Used when associating IQs with smells. The value of the
enumeration isn't related to the IQ level!
*/
enum IQSmells
{
NO_SMELL=0, //!< IQ levels which give off no smell
FISH, //!< Fishy IQs
CHEESE, //!< Cheesy IQs
MILK, //!< The milky IQs
SYRUP //!< Sweet smelling IQs
};
Examples and tests
Examples and tests can be added at the end of a header file using the \example command. It is simply a comment block with the name of the file containing the test, and a description about each test.
/*! * \example clever_test.cxx * Test the MI_CleverClass works */
I found that, for Doxygen to find the test, I had to change the Doxyfile configuration file to add an examples directory
EXAMPLE_PATH = src/
The example was in ./src/tests relative to the directory Doxygen was executed in.
That’s probably enough for now. The final documentation can be seen here.
More examples may be added as I think of them. I am also planning to add a style guide to the documentation pages on the main site which will include Doxygen styling. A link from this blog will be added once the guide has been created (it will be an organic guide – growing as I think of things to add). The code from this post has been added to the svn repository, and can be retrieved using the command
svn checkout http://www.mereidea.com/svn/testing/trunk/doxy_example
The svn revision 15 in the testing repository was the initial import, revision 16 corrected a typo in the versionfilter.sh file, and revision 17 added the stylesheet and changes to Doxyfile to include it, as well as a revised version of the tab images (note that the tab images are manually copied into the html directory, otherwise the original images are used). Revision 18 updated a link in the intro.txt which points to this post. I couldn’t update that until this post was published!
The code in the doxy_example repository has never been compiled. Only the documentation has been built, so the code may not build or run!
The next thing to do is add Doxygen comments to the example code. Then set up an automatic overnight build of the documentation from the svn repository.
Note: Why the MI_CleverClass is clever, and how the smells are applied to IQ levels are left as exercises for the reader.
Frequent updates on Twitter