Writing and Reading Code
Writing code is hard, reading code is harder
I participate in a lot of code reviews and one thing I've realized is - developers spend most of their time coding a solution, not enough time explaining it.
There are two ways you could explain how your code works:
- Write simple code that is self-explanatory
- Write "good" documentation
Value of Good Documentation
In this post, I am going to make a case for how writing good documentation can improve the quality of your code and should be done along with writing self-explanatory code.
Let me illustrate with an example.
I've defined my_function
below, that computes some "arbitrary" logic:
def my_function(input_string, input_list=None):
head = "Start:"
tail = "" if not input_list else " ".join(input_list)
output = f"{input_string} {tail}"
if not input_string.startswith(head):
output = f"{head} {input_string} {tail}"
return output.strip()
In a real-world problem, this piece of code might be much more complex.
As a reader, you could read this code and understand what it is doing. However, I can make the reader's job easier by using docstring.
Docstrings: Make code easy to read
Docstring holds immense value when developing code in a large organization and/or in a collaborative community.
It lets any developer understand what's happening in the function/module without them having to read through the entire codebase.
It can also be used with tools like sphinxdoc to generate beautiful documentation for your project.
def my_function(input_string, input_list=None):
"""Sanitize input string and append extra text if required
The function checks if input_string starts with 'Start:'
if not, it will add the string to the input_string
It also converts input_list to a string using join
and appends to the input_string
"""
head = "Start:"
tail = "" if not input_list else " ".join(input_list)
output = f"{input_string} {tail}"
if not input_string.startswith(head):
output = f"{head} {input_string} {tail}"
return output.strip()
Docstring in the above code explains what the code is doing, but it still feels like a repetition of what is written in the code block.
How do we improve this?
Using doctest!
Doctest: Read, Test and Document better
doctest allows you to not only test interactive Python examples but also makes sure your documentation is up to date.
Let us take a look at improved docstring
for the same function using doctest
def my_function(input_string, input_list=None):
"""Sanitize input string and append extra text if required
>>> my_function('hi')
'Start: hi'
>>> my_function('Start: some string')
'Start: some string'
>>> my_function('hi', ['other', 'item'])
'Start: hi other item'
:param input_string: string to sanitize
:type input_string: str
:param input_list: extra items to append, defaults to None
:type input_list: list, optional
:return: sanitized string
:rtype: str
"""
head = "Start:"
tail = "" if not input_list else " ".join(input_list)
output = f"{input_string} {tail}"
if not input_string.startswith(head):
output = f"{head} {input_string} {tail}"
return output.strip()
Here, I have defined some input-output examples for the given function which illustrate what the function is doing. For instance:
my_function('hi', ['other', 'item'])
should return:
'Start: hi other item'
The above documentation tells developers/readers the following:
- What the function does
- Parameters and their types
- Return value and its type
- Expected behavior - describes input/output examples
Conclusion
Generating documentation
I used sphinxdoc to generate documentation for the above function:
Practicing TDD
Writing documentation in Python also allows me to follow Test Driven Development, where I first define the behavior in docstring
then write the code.
doctest
can be run by any testing framework like unittest
or pytest
$ pytest --doctest-modules -vv top.py
=================== test session starts ======================
platform darwin -- Python 3.8.2, pytest-6.2.4
-- /projects/virtualenvs/dev-to/bin/python3
cachedir: .pytest_cache
rootdir: /Users/chaitanyadwivedi/projects/dev-to
collected 1 item
top.py::top.my_function PASSED [100%]
==================== 1 passed in 0.05s =======================
Top comments (0)