5.1. Understandable and robust code#

5.1.1. Understandable code#

Once you become an expert at coding, it will become very easy to write code that Python can understand. However, the big challenge is actually writing code 1. that works, 2. that is efficient, and 3. that people can easily understand. Why is it important to write understandable code?

  1. In this course, your teachers and TAs will need to understand your code in order to grade it. If we can’t quickly understand what you have done, this will affect your grade.

  2. In the future, you may want to share your code or collaborate on code with someone else. If they have to spend a lot of time figuring out what you’ve done, then your code is useless to them.

  3. Weeks, months, or even years later, you may want to go back and reuse your own code. If your code is not written in a clear and understandable way, you yourself will waste a lot of time trying to figure out what you did back in the day. This happens to both beginner and advanced programmers if the code is not clearly written.

So how do you make sure that your code is understandable to you and others? There are three good practices that can help you out:

  • Meaningful variable names

  • Logical code structure

  • Comments explaining what the code does

Let’s look at an example in which an incomprehensible piece of code is modified into an easily understandable one using these practices.

5.1.1.1. Bad code#

foo1=10; foo2=0.1; foo3=300 
def foo4(foo1,foo2,foo3):
    R=8.3  
    foo5 = (foo2*R*foo3)/foo1
    return foo5
foo6=foo4(foo1,foo2,foo3)
print(round(foo6,2))

Can you figure out what this code does? It’s rather convoluted, right? While this code is technically correct (it will give the right answer), it’s very difficult to read it. Note: correct code can be badly written code.

Note

The example code in this book section includes Python code that looks like this:

def f(x,y):
    z = x + y
    retun z

These are the so-called functions in Python and we will learn more about them in a later book chapter. Specifically, here we defined function f that takes parameters x and y, calculates z as x+y, and then returns z as output. Once defined, we can invoke this function later in our Python code.

It’s OK if you don’t understand all the details of Python functions at this moment - the code here is used only for illustration of good coding practices and very detailed understanding of the code is not crucial.

5.1.1.2. Better code#

Let’s modify the bad code above by using better variable names and a clearer structure.

def p(V, n, T):
    R = 8.3  
    p = (n * R * T) / V
    return p

V = 10  
n = 0.1 
T = 300 

P = p(V, n, T)

print("p = ", round(P, 1), " Pa")

Why is this better?

  • Not all variables and functions are named foo, but they have somewhat meaningful names like p, n, and T.

  • Functions are defined at the top (a common convention to make your code understandable).

  • There are blank lines separating different logical parts of the code:

    1. function definitions

    2. defining the values of the input variables

    3. definition and printing the answer / output

You may recognize the formula used in this code from your chemistry classes as the ideal gas equation. The variable names also match the formulas from the textbook you used in the past, and you probably almost instantly recognize all the variable names as well as what the code does.

However, if you come back at a later time, when chemistry is not so fresh in your mind, you may ask yourself “what was I doing here?”

5.1.1.3. Even better code#

While the above code is already better, and if chemistry is fresh in your mind, the meaning of the parameters p, V, n, etc. are clear. However, if you haven’t seen the code before, it may not be immediately obvious. For this reason, it is even better to use descriptive variable names that themselves already describe what the meaning of the variable is:

def calculate_pressure(volume, molarity, temperature):
    R = 8.3 
    pressure = (molarity * R * temperature) / volume
    return pressure


volume = 10
molarity = 0.1 
temperature = 300 

pressure = calculate_pressure(volume, molarity, temperature)

print("The pressure of the gas is: ", round(pressure, 1), " Pa")

This requires a bit more typing, but you can also use “tab completion” to save typing: just type the first few letters, push the “Tab” key, and VS Code will automatically type the rest for you.

5.1.1.4. Commenting the code#

By taking some time to explain what you’re doing and why, this code becomes instantly understandable:

# Calculating pressure of an ideal gas based on ideal gas equation pV = nRT

def calculate_pressure(volume, molarity, temperature):

    R = 8.3  # Approx. ideal gas constant, J·K^-1·mol^-1
    
    # Calculate the pressure using the ideal gas law
    pressure = (molarity * R * temperature) / volume
    return pressure


volume = 10  # Gas volume, m^3
molarity = 0.1  # Amount of gas, mol
temperature = 300  # Temperature, K

# Calculate the pressure
pressure = calculate_pressure(volume, molarity, temperature)

print("The pressure of the gas is: ", round(pressure, 1), " Pa")

Here, we explain what we are doing, so this code is likely understandable by anyone who reads it, including ourselves in a year’s time. Notice also that it’s important to actually know the units of our variables for the calculation, e.g., whether the given temperature is in degrees Kelvin or Celsius.

It is always a good idea to add comments explaining what you do. However, you don’t want to write code that is more comments than actual code. See, for instance, this example:

# Defining a function to calculate pressure based on volume, molarity, and temperature
# The equation is pV = nRT
# Beware that the units must match
def calculate_pressure(volume, molarity, temperature):
    # Define the ideal gas constant, R
    R = 8.3  # Approx. ideal gas constant, J·K^-1·mol^-1
    
    # Calculate the pressure using the ideal gas law
    # Pressure is calculated by multiplying molarity with R and temperature, 
    # then dividing by volume
    pressure = (molarity * R * temperature) / volume
    return pressure  # Return the calculated pressure value

# Define variables
# Make sure that you use the mentioned units, otherwise make unit conversion
# Set the volume of the gas to 10 cubic meters
volume = 10  # Gas volume, m^3

# Set the molarity of the gas to 0.1 moles
molarity = 0.1  # Amount of gas, mol

# Set the temperature of the gas to 300 Kelvin
temperature = 300  # Temperature, K

# Calculate the pressure using the calculate_pressure function
pressure = calculate_pressure(volume, molarity, temperature)

# Print the calculated pressure value
# Round the value to one decimal place with round() function 
# Also add unit pascal " Pa"
print("The pressure of the gas is: ", round(pressure, 1), " Pa")

This code is cluttered by unnecessary comments. But how do you draw the line? When do you decide whether to add a comment, or if the code is already clear enough without it?

Good guideline: if there is something that you had to fiddle around with for a while to get the code correct, add a comment in your code so that you remember this, while others get to learn from your hard work. In addition, using descriptive variable names helps, as then you may not have to add a comment where you otherwise would.

The first commented example above contains a reasonable amount of comments, pointing out things that might not be immediately obvious (such as units), but the variable names are well chosen so that the code is mostly understandable when reading it. Note also that there are separated “logical blocks” in the code, introduced by blank lines, which makes it easier to quickly visually grasp the purpose of each code section.

5.1.2. Hard coding#

Hard coding is when you fill in values repeated at multiple places in your code. If at a later point in time you have to change a value, finding all places with the given value and replacing them with a new one becomes very bug-prone. Hard-coded code therefore lacks flexibility when it needs to be modified. That’s why you should always avoid hard coding.

How to avoid hard coding to make your code more robust and maintainable?

  • Replace hard-coded parameter values with variables.

  • Use functions that can automatically determine the appropriate value to use.

5.1.2.1. Bad code - hard-coded#

Let’s look at a piece of code which uses radius of a circle of 5 cm to calculate its diameter, circumference, area, and area of a sector defined by an angle \(\pi\). Here is an example of a bad piece of code that does this using hard coding:

diameter = 2 * 5
circumference = 2 * 5 * 3.14
area = pow(5, 2) * 3.14
sector = pow(5, 2) * (3.14 / 2)

print("Diameter = ", diameter, " cm")
print("Circumference = ", round(circumference,2), "cm")
print("Area = ", area, " cm^2")
print("Sector area = ", sector, " cm^2")

In this example code, we say that we have “hard coded” the radius of a circle as well as a sector angle. If you now wanted to change the radius to 7 cm, you would have to go through all of the code by hand and change 5 each time into 7. If you want, give it a try with the code above. This time that is not very difficult since it is a short piece of code, but when you build more complex programs with tens or hundreds of lines, this will become a lot more work, and the chance of getting a bug in your code will increase exponentially.

5.1.2.2. Good code - no hard coding#

To improve this code, we will replace hard-coded numbers with a variable - instead of typing in 5 each time manually, we define a variable at the top that will define the radius of our circle. This way, if we want to change the radius of the circle, all we have to do is change this variable and everything will still work. The same applies to the angle defining the sector area.

radius = 5 
angle = 3.14
pi = 3.14

diameter = 2 * radius
circumference = 2 * radius * pi
area = pow(radius, 2) * pi
sector = pow(radius, 2) * (angle / 2)

print("Diameter = ", diameter, " cm")
print("Circumference = ", round(circumference,2), "cm")
print("Area = ", area, " cm^2")
print("Sector area = ", sector, " cm^2")

The big advantage here is that you can just change radius and angle, and all the code works immediately with no further changes.

In general, another good option is to replace hard-coded numbers with automatically calculated values. For instance, if you’re working with an array and you need to perform a calculation on each of its elements, you can use the len() function to automatically calculate the array length and plug this into your code (e.g., into a for loop, such that you never accidentally loop over the end of the array).

import micropip
await micropip.install("jupyterquiz")
from jupyterquiz import display_quiz
import json

with open("questions.json", "r") as file:
    questions=json.load(file)
    
display_quiz(questions, border_radius=0)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[8], line 1
----> 1 import micropip
      2 await micropip.install("jupyterquiz")
      3 from jupyterquiz import display_quiz

ModuleNotFoundError: No module named 'micropip'