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)