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