Python and C++ Docs#

Documentation extends beyond the official documentation pages that you write. As the saying goes, “good code is its own best documentation.” Do not take this at face-value; certainly, while you should write the best code that you can (such that it is readable and predictable), even the best code required you to have some context when you designed and wrote it, and you must pass this context onto the reader when necessary.

For this, we have the concept of comments and inline documentation. First, we’ll look at when and where to use comments, and then we’ll look at how to include inline docs in Python and C++.

Using Comments#

  • Planning and collaborating: When you’re working on adding some new functionality in your code,

    it may be helpful to scaffold it with comments describing each step of your code at a high level. This helps you keep your thoughts together as you write out behavior, and also helps other members of the team more easily follow your vision so they can help you out. As an example:

    1# Step one: initialize with sensible defaults
    2# Step two: attempt to fetch actual data from server, error if failure
    3# Step three: send a message with these defaults to the action server
    

    These comments should only exist as scaffolding; just as a building’s scaffolding is removed when the construction is done, your scaffolding comments should be removed. It is not relevant to future readers in what order you implemented your function; all they care about is what it does and why.

  • Describing your code: If you’re writing a complicated piece of code, you should write down

    what it’s doing at a high level, to aid your reader in their understanding. For example,

    1// Fetch the latest repository from GitHub and clean out unnecessary files.
    2// Note that the URL is set in the rover node's configuration directory.
    

    Do not include too many of these. There are some lines of code that your readers do not need help figuring out. Here’s an example:

    1// Set the integer to 7.
    2int i = 7;
    

    🤦 … just don’t.

  • Describing an algorithm: There are cases where we do some pretty weird stuff in our controls

    software, whose purpose may not be immediately apparent. For example, the code that calibrates the rover for autonomous navigation takes the rover through a bit of moving, some spins, and some sleep. How would future readers know why the rover is doing that unless you describe it in the code? Other cases where describing your algorithm is important is when you’ve used another algorithm to make an optimization; for example:

    1# Use GriesSort for performance gain with ordered lists
    
  • To-dos: Picture this: it’s 1 AM, you have a class in several hours, and you need to leave the

    ELL to get rest (rest is important!), but you still want to commit your changes up to your branch because you are responsible and you don’t want to lose all your hard work. This is the perfect use case for a todo comment! Just say what you intend to do when you pick up from there. This is useful for two reasons: first, it helps you not forget what you were doing in your exhausted confusion last night; and second, it tells collaborators or reviewers if you’re aware of missing functionality.

    1// TODO: Add friendly error handling when user is not connected to the rover.
    

    There’s a useful plugin called TODO Highlight that makes your todo comments easy to find, by highlighting the word in orange.

    Never push a pull request that has existing TODO’s in it. They should all be fulfilled or deferred (and noted thusly in the PR comments) before any merging gets done, with few exceptions subject to your code reviewers.

Note

This list was adapted from this Real Python article on the matter.

Python Documentation#

It’s important that all your public-facing functions and classes are well-documented so that clients understand the purpose of each function, its parameters, and its return values. We use the Google Style Python Docstrings, which is not the default recommended by the language, but is a lot more human readable. Our documentation engine, Sphinx, will read these docstrings and give you the option of embedding formatted versions of your documentation in your project’s documentation page.

Here is a very detailed example containing all the possible sorts of docstrings you can write in this style. Pay close attention to the very top of the file, which is the module-level docstring, and the subsequent functions, since you will be writing modules and functions most often. There is also an example of how to use docstrings with classes further downward.

  1# -*- coding: utf-8 -*-
  2"""Example Google style docstrings.
  3
  4This module demonstrates documentation as specified by the `Google Python
  5Style Guide`_. Docstrings may extend over multiple lines. Sections are created
  6with a section header and a colon followed by a block of indented text.
  7
  8Example:
  9    Examples can be given using either the ``Example`` or ``Examples``
 10    sections. Sections support any reStructuredText formatting, including
 11    literal blocks::
 12
 13        $ python example_google.py
 14
 15Section breaks are created by resuming unindented text. Section breaks
 16are also implicitly created anytime a new section starts.
 17
 18Attributes:
 19    module_level_variable1 (int): Module level variables may be documented in
 20        either the ``Attributes`` section of the module docstring, or in an
 21        inline docstring immediately following the variable.
 22
 23        Either form is acceptable, but the two should not be mixed. Choose
 24        one convention to document module level variables and be consistent
 25        with it.
 26
 27Todo:
 28    * For module TODOs
 29    * You have to also use ``sphinx.ext.todo`` extension
 30
 31.. _Google Python Style Guide:
 32   http://google.github.io/styleguide/pyguide.html
 33
 34"""
 35
 36module_level_variable1 = 12345
 37
 38module_level_variable2 = 98765
 39"""int: Module level variable documented inline.
 40
 41The docstring may span multiple lines. The type may optionally be specified
 42on the first line, separated by a colon.
 43"""
 44
 45
 46def function_with_types_in_docstring(param1, param2):
 47    """Example function with types documented in the docstring.
 48
 49    `PEP 484`_ type annotations are supported. If attribute, parameter, and
 50    return types are annotated according to `PEP 484`_, they do not need to be
 51    included in the docstring:
 52
 53    Args:
 54        param1 (int): The first parameter.
 55        param2 (str): The second parameter.
 56
 57    Returns:
 58        bool: The return value. True for success, False otherwise.
 59
 60    .. _PEP 484:
 61        https://www.python.org/dev/peps/pep-0484/
 62
 63    """
 64
 65
 66def function_with_pep484_type_annotations(param1: int, param2: str) -> bool:
 67    """Example function with PEP 484 type annotations.
 68
 69    Args:
 70        param1: The first parameter.
 71        param2: The second parameter.
 72
 73    Returns:
 74        The return value. True for success, False otherwise.
 75
 76    """
 77
 78
 79def module_level_function(param1, param2=None, *args, **kwargs):
 80    """This is an example of a module level function.
 81
 82    Function parameters should be documented in the ``Args`` section. The name
 83    of each parameter is required. The type and description of each parameter
 84    is optional, but should be included if not obvious.
 85
 86    If \*args or \*\*kwargs are accepted,
 87    they should be listed as ``*args`` and ``**kwargs``.
 88
 89    The format for a parameter is::
 90
 91        name (type): description
 92            The description may span multiple lines. Following
 93            lines should be indented. The "(type)" is optional.
 94
 95            Multiple paragraphs are supported in parameter
 96            descriptions.
 97
 98    Args:
 99        param1 (int): The first parameter.
100        param2 (:obj:`str`, optional): The second parameter. Defaults to None.
101            Second line of description should be indented.
102        *args: Variable length argument list.
103        **kwargs: Arbitrary keyword arguments.
104
105    Returns:
106        bool: True if successful, False otherwise.
107
108        The return type is optional and may be specified at the beginning of
109        the ``Returns`` section followed by a colon.
110
111        The ``Returns`` section may span multiple lines and paragraphs.
112        Following lines should be indented to match the first line.
113
114        The ``Returns`` section supports any reStructuredText formatting,
115        including literal blocks::
116
117            {
118                'param1': param1,
119                'param2': param2
120            }
121
122    Raises:
123        AttributeError: The ``Raises`` section is a list of all exceptions
124            that are relevant to the interface.
125        ValueError: If `param2` is equal to `param1`.
126
127    """
128    if param1 == param2:
129        raise ValueError('param1 may not be equal to param2')
130    return True
131
132
133def example_generator(n):
134    """Generators have a ``Yields`` section instead of a ``Returns`` section.
135
136    Args:
137        n (int): The upper limit of the range to generate, from 0 to `n` - 1.
138
139    Yields:
140        int: The next number in the range of 0 to `n` - 1.
141
142    Examples:
143        Examples should be written in doctest format, and should illustrate how
144        to use the function.
145
146        >>> print([i for i in example_generator(4)])
147        [0, 1, 2, 3]
148
149    """
150    for i in range(n):
151        yield i
152
153
154class ExampleError(Exception):
155    """Exceptions are documented in the same way as classes.
156
157    The __init__ method may be documented in either the class level
158    docstring, or as a docstring on the __init__ method itself.
159
160    Either form is acceptable, but the two should not be mixed. Choose one
161    convention to document the __init__ method and be consistent with it.
162
163    Note:
164        Do not include the `self` parameter in the ``Args`` section.
165
166    Args:
167        msg (str): Human readable string describing the exception.
168        code (:obj:`int`, optional): Error code.
169
170    Attributes:
171        msg (str): Human readable string describing the exception.
172        code (int): Exception error code.
173
174    """
175
176    def __init__(self, msg, code):
177        self.msg = msg
178        self.code = code
179
180
181class ExampleClass(object):
182    """The summary line for a class docstring should fit on one line.
183
184    If the class has public attributes, they may be documented here
185    in an ``Attributes`` section and follow the same formatting as a
186    function's ``Args`` section. Alternatively, attributes may be documented
187    inline with the attribute's declaration (see __init__ method below).
188
189    Properties created with the ``@property`` decorator should be documented
190    in the property's getter method.
191
192    Attributes:
193        attr1 (str): Description of `attr1`.
194        attr2 (:obj:`int`, optional): Description of `attr2`.
195
196    """
197
198    def __init__(self, param1, param2, param3):
199        """Example of docstring on the __init__ method.
200
201        The __init__ method may be documented in either the class level
202        docstring, or as a docstring on the __init__ method itself.
203
204        Either form is acceptable, but the two should not be mixed. Choose one
205        convention to document the __init__ method and be consistent with it.
206
207        Note:
208            Do not include the `self` parameter in the ``Args`` section.
209
210        Args:
211            param1 (str): Description of `param1`.
212            param2 (:obj:`int`, optional): Description of `param2`. Multiple
213                lines are supported.
214            param3 (:obj:`list` of :obj:`str`): Description of `param3`.
215
216        """
217        self.attr1 = param1
218        self.attr2 = param2
219        self.attr3 = param3  #: Doc comment *inline* with attribute
220
221        #: list of str: Doc comment *before* attribute, with type specified
222        self.attr4 = ['attr4']
223
224        self.attr5 = None
225        """str: Docstring *after* attribute, with type specified."""
226
227    @property
228    def readonly_property(self):
229        """str: Properties should be documented in their getter method."""
230        return 'readonly_property'
231
232    @property
233    def readwrite_property(self):
234        """:obj:`list` of :obj:`str`: Properties with both a getter and setter
235        should only be documented in their getter method.
236
237        If the setter method contains notable behavior, it should be
238        mentioned here.
239        """
240        return ['readwrite_property']
241
242    @readwrite_property.setter
243    def readwrite_property(self, value):
244        value
245
246    def example_method(self, param1, param2):
247        """Class methods are similar to regular functions.
248
249        Note:
250            Do not include the `self` parameter in the ``Args`` section.
251
252        Args:
253            param1: The first parameter.
254            param2: The second parameter.
255
256        Returns:
257            True if successful, False otherwise.
258
259        """
260        return True
261
262    def __special__(self):
263        """By default special members with docstrings are not included.
264
265        Special members are any methods or attributes that start with and
266        end with a double underscore. Any special member with a docstring
267        will be included in the output, if
268        ``napoleon_include_special_with_doc`` is set to True.
269
270        This behavior can be enabled by changing the following setting in
271        Sphinx's conf.py::
272
273            napoleon_include_special_with_doc = True
274
275        """
276        pass
277
278    def __special_without_docstring__(self):
279        pass
280
281    def _private(self):
282        """By default private members are not included.
283
284        Private members are any methods or attributes that start with an
285        underscore and are *not* special. By default they are not included
286        in the output.
287
288        This behavior can be changed such that private members *are* included
289        by changing the following setting in Sphinx's conf.py::
290
291            napoleon_include_private_with_doc = True
292
293        """
294        pass
295
296    def _private_without_docstring(self):
297        pass

Note

This example is an official Google Style Python Docstrings example from this external documentation link

C++ Documentation#

Your C++ code must also be very well commented, especially because the C++ side of our codebase tends to be trickier to understand. There is a separate page in our CMR documentation that talks about how to write this documentation. Click the link below:

C++ Documentation using Doxygen