This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

09. Code Readability

Making code easier to understand.

You are getting the first edition of all these pages. Please let me know if you find an error!

Motivation

Learning to program often focuses on syntax and semantics – avoid errors and get the correct answer.

You probably also learned about rules to follow for how your code looks. You were probably also told that you should write good comments. Why?

A tremendous amount of research in programming language development and in software engineering focuses on program comprehension a.k.a., understandability. How much effort does it take to understand your source code? Software engineers care deeply about understandability because most of the effort in software development is spent fixing bugs or adding functionality to existing code. To do that without breaking everything, you need to understand what the existing code does!

Understandable code is a function of several things:

  1. The programming language syntax and semantics. Python is objectively more human-friendly than assembly language.
  2. Coding conventions and documentation.
  3. Design and organization of the code.

We are going to focus on #2 first and #3 in the future.

Both coding conventions and code documentation promote readability: how difficult is it for someone to read your source code and understand it. Let’s look at these topics separately.

1 - Coding conventions

Readability is a function of names and style.

You are getting the first edition of all these pages. Please let me know if you find an error!

Motivation

“Readability counts.”

– Tim Peters, long-time Python contributor, The Zen of Python

You were probably taught to give your variables descriptive names, such as total = price + tax, as opposed to t = p + tax. But, sometimes, you are told there are traditional variable names, like

for i in range(1, 4):  # i is the outer loop index
    for j in range(1, 4):  # j is the inner loop index
        print(i, j)  

Consider the following code with poor variable names and improper spacing:

def bs(a,x):
  there=False
  fst,lst = 0,len(a)-1
  while fst<=lst and not there:
    mid=(fst+lst)//2
  if x<a[mid]:
    lst=mid-1
  elif x>a[mid]:
    fst=mid+1
  else:
    return True
  return False

As a developer, it would certainly take me a minute to figure out what this function does. Better names would go a long way for sure. But also, the improper spacing makes it needlessly difficult to see what each line is doing. In Python, every operator should have a single space around it. For example, lst=mid-1 should be lst = mid - 1.

Now compare to a properly named, properly spaced solution:

def binary_search(lst, target):
    found = False
    first, last = 0, len(lst) - 1

    while first <= last and not found:
        mid = (first + last) // 2
    if x < lst[mid]:
        last = mid - 1
    elif x > lst[mid]:
        first = mid + 1
    else:
        return True

    return False

Coding conventions in Python

Coding conventions are the rules for naming, spacing, and commenting adopted by an organization. These conventions are often language-specific. Google has coding conventions for many languages that they expect their developers to follow, for example. Many organizations will use their own conventions. One of the nice things about coding conventions is that they can be checked by tools in the IDE to let you know if you’re violating them.

The creators of Python have published a set of coding conventions for the whole language, called PEP 8 - Style Guide for Python Code, which we will follow in this class.

The sections below are a subset of the rules that I consider the most impactful on readability.

Naming rules

  1. Variable and function names are lowercase_with_underscores only.
    • Function names are verbs or begin with a verb.
    • Variable and class names should be nouns.
  2. Class names are CamelCase beginning with an uppercase letter.
  3. File names (modules) are lowercase letters. You may use _ if it improves readability.

Blank lines

  1. Surround top-level function and class definitions with two blank lines.
  2. Method definitions inside a class are surrounded by a single blank line.
  3. Extra blank lines may be used, sparingly, to separate groups of related functions.
  4. Use blank lines in functions, sparingly, to indicate logical sections.
  5. Otherwise, avoid unnecessary blank lines!

Whitespace within lines

  1. Do not put whitespace immediately inside parentheses, brackets, braces.
    • Do: spam(ham[1], {eggs: 2})
    • No: spam( ham[ 1 ] , { eggs: 2 } )
  2. Do not put whitespace immediately before a comma, semicolon, or colon:
    • Do: if x == 4: print(x, y); x, y = y, x
    • No: if x == 4 : print(x , y) ; x , y = y , x
  3. Most operators get one space around them.
  4. Otherwise, avoid unnecessary whitespace!

Summary

Consistently applying coding conventions makes your code easier to understand.

We can use tools to help enforce coding conventions, and we will do so soon. For now, concentrate on learning the Python naming and spacing conventions above.

Knowledge check

  • Define coding conventions.
  • What are the PEP8 violations in the following code block? How do you fix them?
    class patient:
    
      def __init__(self,firstName,lastName,age):
        self.firstName=firstName
        self.lastName=lastName
        self.age=age
    
    
      def computeBill(self,fee,interest):
        return fee*(1+interest)
      def printRecord(self):
        print(f"{self.firstName} {self.lastName} {self.age}")
    
    if __name__ == "__main__":
      bob = patient('bob', 'bobberton', 55)
      bob.printRecord()
    

2 - Documenting code

Properly commenting your code goes a long way toward understandability.

You are getting the first edition of all these pages. Please let me know if you find an error!

Motivation

Comments in code provide a way for you to leave notes to yourself and others about what your code does. These are very useful, if not essential, in a team setting. The term code documentation in general refers to the set of comments in source code that, hopefully, explain something about that code.

Code documentation is a double-edged sword. Done well, it helps you and others understand your code. Done poorly, it provides no value and can even mislead. Further, code documentation needs to be updated when the code is updated!

Three simple rules

We want our code documentation to be clear and concise, just like the code itself. Here is what we will focus on documenting.

  1. Code should be self-documenting to the greatest extent possible.
  2. Document the purpose of classes and modules (files).
  3. Document the purpose, parameters, return values, and exceptions of functions.

You can apply these rules to almost any language you encounter, and you will find that the recommendations for creating class and function comments different per language.

Self-documenting code

Self-documenting code is a popular term for “I can look at the code and understand it’s purpose.” How do you achieve that?

Naming

Use descriptive variable, function, and class names according to your team’s coding conventions.

Variables and classes should be nouns that describe the data.

  • Keep them short and concise, say, 16 characters max. Shorter is better.
  • Use plural nouns to represent lists, sets, and other collections.
  • Do not use built-in names for variables, like max, min, sum.
  • Examples:
    • for name in birds: where birds is a list of strings.
    • total = sum(scores)

Functions should be verbs or start with a verb. They should describe what the function does.

  • Again, strive to be concise.
  • If a phrase better describes the function, split the words with underscores (Python convention), such as compute_average_score(). In Java, you would use camelCase

Comments

In-line comments are useful but should not be abused. Use in-line comments to:

  1. Summarize a complex block of code.
  2. Explain an implementation or design choice.

Do not write a comment for every line. A programming proficient in the programming language should be able to understand your code if you use good variable names and your logic is clear. In cases where the logic is unclear or convoluted, a code comment is warranted to explain your implementation.

Docstrings

In Python, we document modules (.py files), classes, and functions with docstrings. Docstrings are part of the Python language syntax.

Some tools look for these docstring content in a particular content These tools can give you pop-up information about a module, class, or function:

An intellisense popup

Installing autoDocstring

We will install a Visual Studio Code extension to make writing docstrings simpler.

Go to the Extensions pane on the left side Extensions icon or press Ctrl+Shift+X.

Search for autoDocstring and install the extension by Nils Werner.

The store page for the autoDocstring extension

Creating docstrings for a module/file

On the first line of the file, put something similar to the following:

"""This module contains functions useful for counting birds."""

That’s it. You can add multi-line docstrings where needed like so:

1
2
3
4
5
"""
This module contains functions to load a bird osbervation file and count it.

It is used by the ornithologist package to load data for further processing.
"""

Place your cursor on the first line of the file (for modules), just below the class name, or just below the function name. then type """ and hit Enter. autoDocstring will create a template for you.

Creating docstrings for a class

Place a blank line below the class name line and type """. autoDocstring will prepare a template for you.

1
2
3
4
5
6
class Patient:
    """_summary_
    """

    def __init__(self, name, age, weight, height):
        # More code here

Simply replace the word _summary_ with whatever you want to say. Be concise and state the purpose of the class. Use multiple lines if desired.

Creating docstrings for a function

Place a blank line below the function name and type """. autoDocstring will prepare a template for you.

    def __init__(self, name, age, weight, height):
        """_summary_

        Args:
            name (_type_): _description_
            age (_type_): _description_
            weight (_type_): _description_
            height (_type_): _description_
        """
        self.name = name
        self.age = age
        self.weight = weight
        self.height = height

autoDocstring will create a _summary_ area to explain the purpose of the function. It will have an Args region for you to describe the types and purpose of each argument. It will also create an Exceptions region if your function explicitly raises exceptions.

Fill in the contents like so.

    def __init__(self, name, age, weight, height):
        """Constructor for the Patient class

        Args:
            name (_str_): first and last name
            age (_int_): age in years
            weight (_int_): weight in pounds
            height (_int_): height in inches
        """
        self.name = name
        self.age = age
        self.weight = weight
        self.height = height

Now with your docstrings set up, you will see helpful pop-ups in your IDE when you type class and function names!

Knowledge check

  • When are the two cases where an in-line comment is appropriate?
  • In Python, why is sum a bad variable name?
  • Why is doc() a bad function name?
  • For which three Python program elements do you write docstrings?
  • What are the four possible elements of a function docstring?
  • Does the docstring go inside or above the program element?