Chapter 9

Documentation

Publisher Summary

This chapter describes documentation, which is a critical element of the API. It covers some of the reasons to care about documentation and then describes the various types of documentation that are provided along with several tools that can be used to aid the task of writing documentation. One of the easiest ways to document the API is to use a tool that automatically extracts comments from header files and builds API documentation. One of the most popular and full-featured of these tools is Doxygen, from Dimitri van Heesch. Professionally written documentation is an equally important component of delivering high-quality world-class APIs. In fact, good documentation can make the difference between a user adopting API or looking for an alternative. Good documentation describes how to use API and how it will behave under different inputs.

In the very first chapter of this book, I defined an API as one or more header files plus supporting documentation. In fact, I claim that an API is incompletely specified unless it includes accompanying documentation. This is because header files do not specify the behavior of the API, only the calling conventions for the various functions and methods. David L. Parnas stated this well (Parnas, 1994):

Reuse is something that is far easier to say than to do. Doing it requires both good design and very good documentation. Even when we see good design, which is still infrequently, we won’t see the components reused without good documentation.

Well-written documentation is therefore a critical element of your API. Accordingly, I am dedicating an entire chapter to this topic. I will start by covering some of the reasons why you should care about documentation and then describe the various types of documentation that you could provide along with several tools that can be used to aid the task of writing documentation.

One of the easiest ways to document your API is to use a tool that automatically extracts comments from your header files and builds API documentation for you. One of the most popular and full-featured of these tools is Doxygen, from Dimitri van Heesch. I will therefore spend some time looking at how you can use Doxygen in your projects and provide some sample templates for documenting various elements of your API.

Documenting your implementation code is good practice too, but that documentation is for your own internal use, whereas the need to document your API header files is more vital because that documentation is for your users.

I feel obliged to admit at this point that most of the source code examples in this book have little or no comments or documentation. This was done simply to keep these examples as minimal and focused as possible, both to make them easier to digest by you, the reader and also so that the book doesn’t grow to twice its current size. As a compromise, I have ensured that the source code examples that accompany this book are all well documented, as any API headers that you write should be too.

9.1 Reasons to Write Documentation

Hopefully I don’t need to convince you that providing your users with details on how to use your API is a good thing. It is certainly true that if you follow the core principles from the qualities chapter then your interfaces should already be consistent, discoverable, and easy to use. However, this is not a replacement for good documentation. Professionally written documentation is an equally important component of delivering high-quality world-class APIs. In fact, good documentation can make the difference between a user adopting your API or looking for an alternative.

9.1.1 Defining Behavior

An API is a functional specification. That is, it should define how to use an interface as well as how the interface should behave. Simply looking at a method in a header file will tell you the number and types of its arguments and the type of its return value, but it says nothing about how that method will behave. To illustrate this point, consider the following definition of a class that represents an RGB color.

class RGBColor

{

public:

RGBColor(float, float, float);

~RGBColor();

float Red() const;

float Green() const;

float Blue() const;

void Set(float, float, float);

};

Because this class definition fully specifies the arguments and return types for each method, you can write code that creates instances of this class and call each method. However, there are a number of things that are unknown about the behavior of this class. For example,

• Are the red, green, and blue floating-point values represented by a 0.0 to 1.0 range, as a percentage from 0 to 100%, as a floating-point range from 0.0 to 255.0 (so that the mid-value of 127.5 can be represented accurately), or 0 to 65,535?

• What happens if values outside of this range are passed to the constructor or Set() method? Will it leave them as is, clamp them, take the modulus of the values, or attempt to scale them into the valid range?

• What is the order of the parameters to the constructor and the Set() method? Presumably it’s red, green, and then blue, but this hasn’t been explicitly documented in the function signatures.

The following updated version of this class provides some documentation, in the form of comments, to help clarify these points. (The triple slash comment style will be explained later in the chapter when I talk more about Doxygen.)

/// Represents a single RGB (Red, Green, Blue) color

/// Where (0, 0, 0) represents black and (1, 1, 1) represents white

class RGBColor

{

public:

/// Create an RGB color from three floats in the range 0..1

/// Out of range values will be clamped to this range

RGBColor(float red, float green, float blue);

~RGBColor();

/// Return the red component of the color, in the range 0..1

float Red() const;

/// Return the green component of the color, in the range 0..1

float Green() const;

/// Return the blue component of the color, in the range 0..1

float Blue() const;

/// Set the RGB color with three floats in the range 0..1

/// Out of range values will be clamped to lie between 0..1

void Set(float red, float green, float blue);

};

These comments are quite minimal, but nonetheless they do specify the behavior of the API to a sufficient degree for a client to use it. Documentation doesn’t have to be long-winded to be thorough. While more details will be appreciated by your users, minimal documentation is better than none at all.

Tip

Good documentation describes how to use your API and how it will behave under different inputs.

It’s good practice to write these kinds of comments as you are writing the header file itself. You can always go back and add more content or revise the details once the API is more stable and is approaching release quality. But if you do run out of time and need to get the API out the door quickly, you will at least have some basic level of documentation. Even better, you should explicitly schedule a task to write the documentation for your API before it is released.

You may even consider creating commit hooks (also known as triggers) for your revision control system to reject new public API code that does not include documentation. If that is too draconian a policy, you can still perform the code checks but treat them as advisory feedback rather than roadblocks to checking in new code.

Tip

Write API documentation as you implement each component. Then revise it once the API is complete.

9.1.2 Documenting the Interface’s Contract

The term “design by contract” was introduced by Bertrand Meyer as an approach to defining formal specifications for software components (Meyer, 1987). Meyer later trademarked the term in the United States, so many developers now refer to it as contract programming instead. The central notion is that a software component provides a contract, or obligation, for the services that it will provide, and that by using the component a client agrees to the terms of that contract. This concept is founded upon Hoare logic, a formal system developed by Tony Hoare for reasoning about the correctness of programs (Hoare, 1969). The main principles of this model are threefold:

1. Preconditions: the client is obligated to meet a function’s required preconditions before calling a function. If the preconditions are not met, then the function may not operate correctly.

2. Postconditions: the function guarantees that certain conditions will be met after it has finished its work. If a postcondition is not met, then the function did not complete its work correctly.

3. Class invariant: constraints that every instance of the class must satisfy. This defines the state that must hold true for the class to operate according to its design.

The best way to specify the details of a contract is in the class and function documentation for your API. In other words, your functions should specify any expected preconditions that must be in force before calling them as well as any resulting postconditions that will be met after they have completed. Also, your class documentation should specify any invariants that will hold true during the observable lifetime of each class, that is, after construction, before destruction, and before and after each public method call. Let’s look at a couple of examples to demonstrate this concept more concretely.

A square root function will have the precondition that the input number must be a positive number, or zero. It will have a postcondition that the result of the function squared should equal the input number (within an appropriate tolerance). These conditions can be documented using Doxygen syntax as follows. The next chapter on testing will discuss about how you can enforce these conditions in code.

///

/// rief Calculate the square root of a floating-point number.

/// pre value >= 0

/// post fabs((result * result) - value) < 0.001

///

double SquareRoot(double value);

To give a more object-oriented example, consider a string container class. This has a class invariant that the length of the string, as returned by the size() method, must always be zero or greater. Also, the pointers returned by c_str() and data() must always be non-NULL. For the class’s append() method, the length of the string being changed must grow by the length of the input string. These contract terms are expressed in the following documentation comments. (The term @pre.size() is meant to represent the length of the string before the method is called.)

///

/// rief A container that operates on sequences of characters.

/// invariant size() >=0 && c_str() != NULL && data() != NULL

/// invariant empty() implies c_str()[0] == ‘’

///

class String

{

public:

///

/// rief Append str to this string object.

/// post size() == @pre.size() + str.size()

///

void append(const std::string &str);

};

Tip

Contract programming implies documenting the preconditions and postconditions for your functions and the invariants for your classes.

In terms of inheritance relationships between classes, Ken Pugh notes that the preconditions for derived classes can be weaker, but not stronger, than those in its base classes (Pugh, 2006). That is, a derived class should handle all cases that its base classes handle, but it may decide to handle additional cases by requiring fewer preconditions. In contrast, a derived class should inherit all postconditions from its base classes. That is, a function in a derived class must meet all of its base class postconditions, as well as the further postconditions that it defines itself.

9.1.3 Communicating Behavioral Changes

There are many cases where an API change does not involve changes to any class definitions or function signatures. This happens when you change the underlying implementation for a function without affecting its interface. From a code perspective, this change will be invisible to your clients because it doesn’t affect the function signatures in your header files. However, the API has indeed changed because its behavior has changed. In other words, the update will be source and binary compatible, but not functionally compatible.

In this case, you can communicate the change by modifying the documentation for the API call. For example, consider the following function that returns a list of children in a hierarchy of nodes:

/// Return a list of children in the hierarchy.

///

/// eturn A NULL-terminated list of child nodes,

///       or NULL if no children exist.

///

const Node *GetChildren() const;

According to the documentation, this function returns a NULL pointer if there are no children in the hierarchy. This behavior forces clients to guard against a NULL return value, such as

const Node *children = hierarchy.GetChildren();

if (children)

{

while (*children != NULL)

{

// process node

children++;

}

}

Now let’s say that in a future version of this API you realize that you can make the lives of your clients easier—and improve the stability of their code—if you instead return a valid Node* pointer that points to a NULL value when there are no children. This obviates the need for special NULL checks, so clients could instead write code such as

const Node *children = hierarchy.GetChildren();

while (*children != NULL)

{

// process node

children++;

}

This change did not involve any modification to the function signature for the GetChildren() method. However, the change can still be communicated to the client by updating the documentation for the method. For example,

/// Return a list of children in the hierarchy.

///

///   eturn A non-NULL pointer to a list of child nodes.

///        The list is terminated with a NULL pointer.

///

const Node *GetChildren() const;

9.1.4 What to Document

You should document every public element of your API: every class, function, enum, constant, and typedef. If your clients can access it, you should tell them what it is and how they can use it (Bloch, 2008). In particular, you should focus on any important aspects that will help them use your classes and methods productively. This includes describing the behavior of methods, as well as describing their relationship to the rest of the API.

Specifying the units of parameters and return values is another particularly important element of good documentation. When doing so, you should consider whether you need to define the nature, accuracy, and precision of the values, too. A particularly pertinent example is that of a timer class that calls client code every n seconds. You may document the units of the timeout to be seconds, but it would still be reasonable for a client to ask:

• Does time refer to real-world (wall clock) time or process time?

• What is the accuracy of the timer?

• Will other API operations affect the accuracy of, or block, the timer?

• Will the timer drift over time or will it always fire relative to the start time?

Defining these additional characteristics will help your users work out whether the class is appropriate for their tasks. For example, an idle task that just needs to perform some trivial work roughly every second doesn’t care if it gets woken up after 1.0 seconds or 1.1 seconds. However, an analog clock that increments its second hand on every invocation would soon show the wrong time under the same conditions.

As an aid to working out what you should document for your APIs, the following list provides a checklist of questions to ask yourself when you’re producing the documentation for your classes and functions.

• What is the abstraction that a class represents?

• What are the valid inputs, for example, can you pass a NULL pointer?

• What are the valid return types, for example, when does it return true versus false?

• What error conditions does it check for, for example, does it check for file existence?

• Are there any preconditions, postconditions, or side effects?

• Is there any undefined behavior, for example, sqrt(-1.0)?

• Does it throw any exceptions?

• Is it thread safe?

• What are the units of any parameters?

• What is the space or time complexity, for example, O(log n) or O(n2)?

• What is the memory ownership model, for example, is the caller responsible for deleting any returned objects?

• Does a virtual method call any other method in the class, that is, what methods should clients call when they override the method in a derived class?

• Are there any related functions that should be cross-referenced?

• In which version of the API was this feature added?

• Is this method deprecated; if so, what’s the alternative?

• Are there any known bugs in this feature?

• Do you wish to share any planned future enhancements?

• Are there any code examples you can provide?

• Does the documentation add extra value or insight, for example, the comment “sets the color” doesn’t tell you much you didn’t already know for a function called SetColor().

In addition to this list of API behaviors, Diomidis Spinellis presents a short list of qualities that all good code documentation should strive for (Spinellis, 2010). He recommends that documentation should be:

1. Complete

2. Consistent

3. Effortlessly accessible

4. Non-repetitive

Tip

Document every public element of your API.

It’s also good practice to have another person review your documentation. As the developer who wrote the code, you are often too close to the problem space and may assume knowledge that the normal user will not have. As Michi Henning puts it, the developer of the API is “mentally contaminated by the implementation” (Henning, 2009). Therefore, have a fellow developer, QA engineer, or technical writer look over your API documentation and be open to the feedback from their fresh perspective.

9.2 Types of Documentation

There are various types of documentation that you can provide for your API. Not all of these will be appropriate for your particular project, but it’s worth being aware of the various options that your users will expect.

It’s also worth noting that many software products provide documentation that can accept contributions from users. This may be via the use of a wiki, where any user can actually create and edit documentation, such as the massive World of Warcraft wiki at http://www.wowwiki.com/, or Web pages that support user comment addendums, such as the PHP manual at http://www.php.net/manual/. This is obviously a very powerful capability that can build a community around your API and create collective documentation that goes beyond what you could achieve on your own. Even if you don’t employ an automated system for user contributions, you should provide a way for them to send feedback to you so that it can be incorporated into the documentation manually.

Much of the documentation that you (or your users) write will apply to specific versions of the API. One trap that is easy to fall into with wiki-based solutions is that the wiki content will eventually refer to a range of versions of the API. It is worth considering how you can create distinct versioned sets of documentation, perhaps creating a completely separate copy of the documentation for each major release. This will allow clients who are using older versions to view the documentation for that specific release, while also letting you keep the latest documentation focused on the current version of the API.

Figure 9.1 shows a screenshot of the Qt Reference Documentation index page. This is included as one example of extensive and well-written documentation that demonstrates all of the various types that I will cover next. It’s also worth noting that all of the Qt documentation is versioned along with the API.

image
Figure 9.1 Screenshot of the Qt Reference Documentation Web page, at http://doc.qt.nokia.com/. Copyright © 2010 Nokia Corporation.

9.2.1 Automated API Documentation

The most obvious kind of documentation for an API is the type that is automatically generated from comments in your source code. This allows you to keep the documentation for classes and methods right next to their declaration in the header files. The result is that you’re more likely to keep the documentation up to date when you change the API. It’s also easy to generate the latest documentation for a new release, and this documentation can have cross-references between related functionality of the API.

Tip

Use an automated documentation tool that extracts API documentation from your header comments.

It’s perhaps obvious, but any user documentation comments should appear in your public headers, not .cpp files so that they are more readily discoverable by your users. This is particularly important for closed source APIs, where users do not have access to your .cpp files.

These source code comments are often maintained by developers. However, the quality of your API documentation can be enhanced greatly if you have professional technical writers perform a pass over the comments for style, grammar, and consistency checks. Also, as a fresh set of eyes, these writers can often provide good feedback and suggest additional information to highlight or improved examples to include. Some technical writers may not feel comfortable changing source code directly, but in those cases they can easily mark up a hardcopy of the documentation and let the developer make the actual code changes.

A number of tools let you create API documentation from comments in your C++ source code. These can normally generate output in various formats, such as HTML, PDF, and LaTeX. The following list provides links to several of these tools:

9.2.2 Overview Documentation

In addition to autogenerated API documentation, you should have manually written prose that provides higher-level information about the API. This will normally include an overview of what the API does and why the user should care about it. In a large organization, this task is normally performed by a technical writer. It may even be localized into several different languages. The overview documentation will often cover the following topics:

A high-level conceptual view of the API: what problem is being solved and how the API works. Diagrams are great if they are appropriate.

Key concepts, features, and terminology.

System requirements for using the API.

How to download, install, and configure the software.

How to provide feedback or report bugs.

A statement on the life cycle stage of the API, for example, prerelease, maintenance, stability, or deprecated (see the versioning chapter).

9.2.3 Examples and Tutorials

Examples are really, really important. An API overview is often too high level to allow users to glean how to use your API, and even though you have documentation for every class and method in your API, this doesn’t immediately tell the user how to actually use the API to perform a task. Adding even a few small examples can enhance the utility of your documentation greatly. Remember that an API is software for other developers. They just want to know how they can use your interface to get their job done. This can be part of a Getting Started section in your documentation and may include any of the following.

• Simple and short examples. These should be minimal and easy to digest snippets of code that demonstrate the API’s key functionality. They are not normally code that can be compiled, but instead focus on your API calls without all the boilerplate code that goes around it.

• Working demos. These are complete real-world examples that show how the API can be used to perform a more complex and practical task. These should be easy to reuse so that your users have a working starting point for their own projects. You should provide the source code for these with your API.

• Tutorials and walkthroughs. A tutorial illustrates the steps that you go through in order to solve a problem rather than simply presenting the end result. This can be a useful way to build up a complex example and to address specific features of the API as you gradually add more calls to the worked example.

• User contributions. Your users can be a great source of examples, too. Encourage your users to send you example code that can be added to your collection of demos, perhaps under a specific contrib directory so that it is clear that these are not supported by you.

• FAQs. A set of answers to frequently asked questions can be a very helpful addition to your documentation set. It lets your users discover quickly and easily if the API suits their needs, how to avoid common pitfalls, or how to solve typical problems.

The act of writing documentation forces you to think from the user’s perspective. As such, it can help you identify shortcomings in the API or areas that could be simplified. It’s therefore good practice to write the skeleton for high-level documentation and some initial example code early on as a way to force you to think more deeply about the overall design and the use cases of your library.

9.2.4 Release Notes

Each release after the first release should include release notes. These tell your users what has changed since the last release. Release notes are normally terse documents that can include:

• An overview of the release, including a description of what’s new or what the focus was for the release, for example, bug fixes only.

• A link to where the release can be found.

• Identification of any source or binary incompatibilities from the previous release.

• A list of bugs fixed in the release.

• A list of features that were deprecated or removed.

• Migration tips for any changes in the API, such as how to use any upgrade scripts provided with the release.

• Any known issues, either introduced in this release or remaining from previous versions.

• Troubleshooting tips to work around known issues.

• Information on how users can send feedback and bug reports.

9.2.5 License Information

You should always specify the license that you are distributing your API under. This lets your clients know what rights you are granting them and what their obligations are. Generally, you will use a license from one of the following two categories.

1. Proprietary: For software whose ownership remains with you the publisher and where you prohibit certain uses of the software. Often these software products are closed source and require a licensing fee (although, as a counterexample, the Qt commercial license includes the source code). Proprietary licenses may restrict certain activities such as reverse engineering the software, the number of users or machines, the number of developers, or concurrent use of the software by multiple users.

2. Free and Open Software: For software that can be used, studied, and modified without restriction. It can also be copied and redistributed in either modified or unmodified form with no, or minimal, restrictions. The term free refers to the usage rights of the software, not necessarily its price. Software that conforms to this category is referred to as Free and Open Source Software, commonly abbreviated to FOSS or also FLOSS (Free, Libre, Open Source Software).

There are two major bodies that approve FLOSS licenses. The Free Software Foundation (FSF), founded by Richard Stallman in 1985, approves licenses that comply with The Free Software Definition, and the Open Source Initiative (OSI), founded by Bruce Perens and Eric S. Raymond in 1998, approves licenses that comply with their Open Source Definition.

Tip

Specify the license terms for your API prominently.

There are also two principal types of FLOSS licenses.

1. Copyleft: Offers the right to distribute modified and unmodified copies of a piece of software and requires that any such derived works must be released under the same terms. There is a further subcategory of weak copyleft, which is often applied to software libraries to allow clients to distribute code that links with that library without requiring that product to be distributed under the library’s copyleft license.

2. Permissive: Offers the right to distribute modified and unmodified copies of a piece of software and allows the derived work to be distributed under terms that are more restrictive than those in the original license. This means you can provide an open source library, but your clients are not required to make all distributed derivates available as open source.

The following table provides a list of some of the common FLOSS software licenses and a brief overview of their impact on your users. All of these licenses are approved by both the FSF and the OSI. This list is obviously incomplete and meant only as a rough guide. If you have access to a general counsel, you should consult with that person on the best license choice for your product. For more details, refer to http://www.fsf.org/ and http://www.opensource.org/.

License Name Brief Description
No license If you do not specify a license, your users have no legal right to use your API unless they ask for your permission directly (as you are the copyright holder).
GNU GPL License The GNU General Public License (GPL) is a copyleft license, which means that any derived works must also be distributed as GPL. An open source GPL library therefore cannot be used in a proprietary product. The Linux kernel and the GIMP image processing tool are released under the GPL. Nokia’s Qt library was originally released under either a commercial or a GPL license.
GNU LGPL License The GNU Lesser General Public License (LGPL) is a weak copyleft license that allows an open source API to be binary linked to proprietary code. The derived work can be distributed under certain specific conditions, such as providing the source code of the modified or unmodified LGPL library, among others constraints. GTK+ is licensed under the LGPL. Nokia added the LGPL license to Qt as of version 4.5.
BSD License The BSD license is a simple permissive license that includes a legal disclaimer of liability by a named owner/organization. Normally a modified version of BSD is used, without the “advertising clause.” Proprietary code that links against BSD code can be distributed freely. Google released its Chrome browser under the BSD license. The Zend framework and libtiff library also use BSD.
MIT/X11 License This is a simple permissive license in the same vein as the BSD license. Proprietary code that links against MIT-licensed code can be distributed freely. MIT-licensed software includes Expat, Mono, Ruby on Rails, Lua 5.0 onwards, and the X11 Window System.
Mozilla Public License This is a weak copyleft license that allows your open source library to be used to build proprietary software. Any code modifications must be redistributed under the MPL license. The Mozilla software products Firefox and Thunderbird are made available under the MPL.
Apache License This is another permissive license that allows proprietary software to be distributed that is built upon Apache-licensed code. The Apache Web server obviously uses the Apache license. Google also uses it for many of its products, such as Android and the Google Web Toolkit.

This concludes our discussion of the various types of documentation that you might provide for your API. In summary, Figure 9.2 presents another example of the documentation overview of a well-respected software project: the Apache HTTP Server. Note that this documentation is also localized into several different languages.

image
Figure 9.2 A screenshot of the documentation Web page for the Apache HTTP Server, http://httpd.apache.org/docs/. Copyright © 2009 The Apache Software Foundation.

9.3 Documentation Usability

There are several research groups that investigate API documentation usability (Jeong et al., 2009; Robillard, 2009; Stylos and Myers, 2008). This work involves performing usability studies to see how well users can navigate through documentation and perform focused tasks. The desire is to be able to inform API designers about better ways to document their interfaces. The following list presents a summary of some of these findings.

• Index page. Provide an overall index page that serves as a jumping off point into the individual documentation elements. This gives users a conceptual map of the entire documentation set. Additionally, each starting point should provide some indication of what it will cover and what class of users it targets (developers, managers, legal, etc.).

• Consistent look and feel. API documentation will normally be composed of different elements, some autogenerated, some written by hand, some contributed by users. You should use a consistent and unique style for all these pages so that users are always aware when they are browsing your documentation content or if they have navigated to another Web site.

• Code examples. There can often be a lot of documentation for users to read. Consider the massive size of the Microsoft Developer Network library. Providing example code snippets and working demos can help users find and assimilate the information they need to use the API in their own code more quickly.

• Diagrams. Clear and concise diagrams that illustrate the high-level concepts can be helpful, particularly for users who just want to scan the documentation quickly. You should use familiar diagram formats where possible, such as UML or entity relationship diagrams.

• Search. A good search facility is important to let users find the information they need as fast as possible. All parts of the documentation should be searchable, including the autogenerated API specification as well as any manually written text.

• Breadcrumbs. Use navigation aids that let users keep track of their location within the documentation hierarchy and move back up the hierarchy easily. The term “breadcrumbs” is used to describe the common technique of displaying the current location as a series of pages with a separator symbol, for example, “index > overview > concepts.” In addition, it can be useful to let users backtrack to various high-level pages easily.

• Terminology. Crucial terminology should be defined and used consistently. However, you should avoid specialized or esoteric terminology where it is not necessary as this can serve to confuse and frustrate users.

Related to API usability is the property of ease of learning: a difficult-to-use API will likely also be difficult to learn. To this end, Martin Robillard investigated the question of what makes an API difficult to learn. He found that one of the largest obstacles to learning an API is the supporting documentation and resources. For example, he lists the following hindrances to API learning (Robillard, 2009):

• Lack of code examples: Insufficient or inadequate examples are provided.

• Incomplete content: Documentation is missing or presented inadequately.

• Lack of task focus: No details are offered on how to accomplish specific tasks.

• No design rationale: Insufficient or inadequate details provided on the high-level architecture and design rationale.

• Inaccessible data formats: Documentation isn’t available in the desired format.

In addition to these points, a lot of research has focused on the fact that developers are often reluctant to read API documentation carefully or thoroughly (Zhong et al., 2009). This suggests that providing more documentation can sometimes actually be detrimental because your users may miss the really important points and caveats due to them being buried too deeply. The use of higher-level tutorials and example code can help address this problem. Also, cross-references between related classes and methods can lead the user to discover features that they were not aware of. Some researchers have also suggested using word clouds, or other variable-font techniques, to highlight important or common classes (Stylos et al., 2009).

9.4 Using Doxygen

Doxygen is a utility to automatically generate API documentation in various formats based on the comments that you write in your source code. It has support for many languages, including C, C++, Objective-C, Java, Python, Fortran, C#, and PHP, and it can generate output in several formats, including HTML, LaTeX, PDF, XML, RTF, and UNIX man pages, among others.

Doxygen is open source (released under the GNU General Public License), and binaries are provided for several platforms, including Windows, Linux, and Mac. It has been developed since 1997 and is now a mature and powerful system. Due to the fact that it’s a very popular tool and is used on many projects, and my goal of providing a practical resource, I’ve dedicated the rest of this chapter to cover the basics of how you can use Doxygen in your own projects. However, similar guidelines apply to any of the other tools listed earlier in the Automated API documentation section.

The Doxygen Web site is http://www.doxygen.org/.

9.4.1 The Configuration File

Doxygen is highly configurable, with over 200 options in recent versions. All of these options can be specified via the Doxygen configuration file. This is an ASCII text file with a simple key = value format. You can generate a default configuration file by running Doxygen with the -g command line argument.

You will then want to edit this configuration file to specify some details about your source code and to change the default behavior in certain cases. Some of the entries I find that I change a lot are as follows:

PROJECT_NAME = <name of your project>

FULL_PATH_NAMES = NO

TAB_SIZE = 4

FILE_PATTERNS = *.h *.hpp *.dox

RECURSIVE = YES

HTML_OUTPUT = apidocs

GENERATE_LATEX = NO

With this initial setup performed, you can simply run Doxygen in your source directory, where it will create an apidocs subdirectory with your API documentation (assuming that you adopted the HTML_OUTPUT setting given earlier). The following sections describe how comments can be added to your code that Doxygen will pick up and add to this generated documentation.

9.4.2 Comment Style and Commands

You must use a special comment style to signal to Doxygen that you wish to add the comment text to the API documentation. There are various comment styles that Doxygen supports, including the following:

/**

* … text …

*/

/*!

* … text …

*/

///

/// … text …

///

//!

//! … text …

//!

Which style you adopt is a matter of personal taste: they all behave exactly the same. I will adopt the triple slash style (///) for the rest of this chapter.

Within a comment, there are a number of commands that you can specify to provide specific information to Doxygen. This information will often be formatted specially in the resulting documentation. The following list provides a summary of some of the most useful commands. Refer to the doxygen manual for the complete list.

file [<filename>]

class <class name> [<header-file>] [<header-name>]

rief <short summary>

author <list of authors>

date <date description>

param <parameter name> <description>

param[in] <input parameter name> <description>

param [out] <output parameter name> <description>

param[in,out] <input/output parameter name> <description>

eturn <description of the return result>

code <block of code> endcode

verbatim <verbatim text block> endverbatim

exception <exception-object> <description>

deprecated <explanation and alternatives>

attention <message that needs attention>

warning <warning message>

since <API version or date when the entity was added>

version <version string>

ug <description of bug>

see <cross-references to other methods or classes>

In addition to these commands, Doxygen supports various formatting commands to change the style of the next word. These include  (bold), c (typewriter font), and e (italics). You can also use to force a new line, \ to enter a backslash character, and @ to enter the at sign.

9.4.3 API Comments

Doxygen allows you to specify overview documentation for your entire API using the mainpage comment. This lets you provide a high-level description of the API, as well as provide a breakdown of the major classes. This description will appear on the front page of the documentation that Doxygen produces. It is common to store these comments in a separate file, such as overview.dox (this requires updating the FILE_PATTERNS entry of the Doxygen configuration file to include *.dox).

The text in this overview documentation may be long enough to justify breaking into separate sections. In which case, you can use the section and subsection commands to introduce this structure. You can even create separate pages to contain more detailed descriptions for certain parts of your API. This can be done with the page command.

Also, you may find it useful to define groups of behavior for your API so that you can break up the various classes in your API into different categories. For example, you could create groups for classes or files pertaining to file handling, container classes, logging, versioning, and so on. This is done by declaring a group with defgroup and then using the ingroup to add any specific element to that group.

Putting these features together, the following comment provides overview documentation for an entire API, which is broken down into three sections and cross-references two additional pages for more detailed descriptions. The pages include a link to show all of the API elements that have been tagged as part of a given group.

///

/// mainpage API Documentation

///

/// section sec_Contents Contents

///

/// li ef sec_Overview

/// li ef sec_Detail

/// li ef sec_SeeAlso

///

/// section sec_Overview Overview

///

/// Your overview text here.

///

/// section sec_Detail Detailed Description

///

/// Your more detailed description here.

///

/// section sec_SeeAlso See Also

///

/// li ef page_Logging

/// li ef page_Versioning

///

///

/// page page_Logging The Logging System

///

/// Overview of logging functionality

///

/// link group_Logging View All Logging Classes endlink

///

///

/// page page_Versioning API Versioning

///

/// Overview of API Versioning

///

/// link group_Versioning View All Versioning Classes endlink

///

/// defgroup group_Logging Diagnostic logging features

/// See ef page_Logging for a detailed description.

/// defgroup group_Versioning Versioning System

/// See ef page_Versioning for a detailed description.

The resulting Doxygen HTML output for this comment (using the default style sheet) is shown in Figure 9.3.

image
Figure 9.3 The Doxygen HTML output for the mainpage example.

9.4.4 File Comments

You can place a comment at the top of each header file to provide documentation for the entire module. Here’s a sample template for these per-file comments.

///

/// file <filename>

///

/// rief <brief description>

///

/// author <list of author names>

/// date <date description>

/// since <API version when added>

///

/// <description of module>

///

/// <license and copyright information>

///

If this file contains functionality that you want to add to a group that you’ve defined, then you can also add the ingroup command to this comment.

9.4.5 Class Comments

Each class in your header can also have a comment to describe the overall purpose of the class. In addition to the sample template given here, you may consider including the ingroup command if the class belongs to a group you’ve defined, deprecated if the class has been deprecated, or codeendcode if you want to provide some example code.

///

/// class <class name> [header-file] [header-name]

///

/// rief <brief description>

///

/// <detailed description>

///

/// author <list of author names>

/// date <date description>

/// since <API version when added>

///

9.4.6 Method Comments

You can provide documentation for individual methods, detailing the name and a description for each parameter (and optionally if they are in, out, or in/out parameters), as well as a description of the return value. As with the class sample template, you may also consider adding ingroup or deprecated, as appropriate.

///

/// rief <brief description>

///

/// <detailed description>

///

/// param[in] <input parameter name> <description>

/// param[out] <output parameter name> <description>

/// eturn <description of the return value>

/// since <API version when added>

/// see <methods to list in the see also section>

/// ote <optional note about the method>

///

If you have methods in a class that fall into one or more logical groupings, you can specify this to Doxygen so that it will group the related methods together under a named subsection. This can be used to provide a more appropriate ordering of the class members instead of Doxygen’s default behavior. The following code snippet demonstrates the specification of two such member groups:

class Test

{

public:

/// ame <group1-name>

//@{

void Method1InGroup1();

void Method2InGroup1();

//@}

/// ame <group2-name>

//@{

void Method1InGroup2();

void Method2InGroup2();

//@}

};

9.4.7 Enum Comments

Doxygen also lets you provide comments for enums, including documentation for individual values in the enum. The latter can be done using Doxygen’s < comment syntax, which attaches the documentation to the previous element instead of the next element.

///

/// rief <brief description>

///

/// <detailed description>

///

enum MyEnum {

ENUM_1,  ///< description of enum value ENUM_1

ENUM_2,  ///< description of enum value ENUM_2

ENUM_3   ///< description of enum value ENUM_3

}

9.4.8 Sample Header with Documentation

Putting all of the preceding advice together, here’s an entire header file with Doxygen style comments to describe the file, class, and each method. This example is provided in the supporting source code for the book, along with the generated HTML output that Doxygen produced for this file.

///

/// file version.h

///

/// rief Access the API’s version information.

///

/// author Martin Reddy

/// date 2010-07-07

/// since 1.0

/// ingroup group_Versioning

///

/// Copyright (c) 2010, Martin Reddy. All rights reserved.

///

#ifndef VERSION_H

#define VERSION_H

#include <string>

///

/// class Version version.h API/version.h

///

/// rief Access the version information for the API

///

/// For example, you can get the current version number as

/// a string using c GetVersion, or you can get the separate

/// major, minor, and patch integer values by calling

/// c GetMajor, c GetMinor, or c GetPatch, respectively.

///

/// This class also provides some basic version comparison

/// functionality and lets you determine if certain named

/// features are present in your current build.

///

/// author Martin Reddy

/// date 2010-07-07

/// since 1.0

///

class Version

{

public:

/// ame Version Numbers

//@{

///

/// rief Return the API major version number.

/// eturn The major version number as an integer.

/// since 1.0

///

static int GetMajor();

///

/// rief Return the API minor version number.

/// eturn The minor version number as an integer.

/// since 1.0

///

static int GetMinor();

///

/// rief Return the API patch version number.

/// eturn The patch version number as an integer.

/// since 1.0

///

static int GetPatch();

///

/// rief Return the API full version number.

/// eturn The version string, e.g., "1.0.1".

/// since 1.0

///

static std::string GetVersion();

//@}

/// ame Version Number Math

//@{

///

/// rief Compare the current version number against a specific

/// version.

///

/// This method lets you check to see if the current version

/// is greater than or equal to the specified version. This may

/// be useful to perform operations that require a minimum

/// version number.

///

/// param[in] major The major version number to compare against

/// param[in] minor The minor version number to compare against

/// param[in] patch The patch version number to compare against

/// eturn Returns true if specified version >= current version

/// since 1.0

///

static bool IsAtLeast(int major, int minor, int patch);

//@}

/// ame Feature Tags

//@{

///

/// rief Test whether a feature is implemented by this API.

///

/// New features that change the implementation of API methods

/// are specified as "feature tags." This method lets you

/// query the API to find out if a given feature is available.

///

/// param[in] name The feature tag name, e.g., "LOCKING"

/// eturn Returns true if the named feature is available.

/// since 1.0

///

static bool HasFeature(const std::string &name);

//@}

};

#endif

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

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