BYU logo Computer Science

Quality Code

When you write code, you want to write code that is easy to read, understand, debug, and maintain. In this section, we will cover our standards for writing quality code in Python. These standards include intent, decomposition, naming, and whitespace.

Intent

Each assignment has an intent; we create the assignments so you can learn how to use the tools we give you in specific ways. Most assignments will state this intent in the grading section of each assignment. If not explicitly stated, the intent is that you will use the topic of the day in a way consistent with what you learned in lecture and lab.

A common intent for your assignments is generality, meaning that your code should be able to solve problems that all have the same requirements (for example, solve two different Bit worlds that have the same general qualities).

When your solution takes advantage of a particular quirk of the input not stated in the problem, we call that “hardcoding”. In this case, your code can only solve one very specific problem.

Many of the assignments in Unit 1, including some of the project problems, require fully hardcoded solutions. However, in Unit 2 and beyond, we expect general solutions.

Your solution can assume a particular input format (i.e. the test will always input a number when a number is expected); however, your code should not assume a specific input value (i.e. the bit world will be 5 columns wide, or the input will always be “bob”).

Decomposition

Decomposition is the process of breaking your code into smaller, more manageable pieces. When you decompose your code, you make it easier to read, understand, debug, and maintain.

Functions

Break your code into functions. If the problem asks for a sequence of distinct steps, use a function for each step.

# Good
def run(bit):
    get_to_garden(bit)
    paint_garden(bit)
    go_to_end(bit)

Avoid duplicate code. If code is repeated, put the code in a function, and call the function multiple times.

The following examples paint a rectangular world blue, as is shown in the blue ocean guide.

# Good
def half_loop(bit):
    bit.left()
    while bit.front_clear():
        bit.move()
        bit.paint("blue")
    bit.left()


def blue_column(bit):
    half_loop(bit)
    half_loop(bit)
    

def blue_ocean(bit):
    blue_column(bit)
    while bit.front_clear():
        bit.move()      # Glue code
        blue_column(bit)
        

# Good
def turn_around(bit):
    bit.left()
    bit.left()


def blue_column(bit):
    bit.left()
    bit.paint('blue')
    while bit.front_clear():
        bit.move()
        bit.paint('blue')
    turn_around(bit)
    while bit.front_clear():
        bit.move()
    bit.left()
    

def blue_ocean(bit):
    blue_column(bit)
    while bit.front_clear():
        bit.move()        # Glue code
        blue_column(bit)

        
# Bad
def turn_around(bit):
    bit.right()
    bit.right()


def paint_first_column_blue(bit):
    bit.left()
    bit.paint('blue')
    while bit.front_clear():
        bit.move()
        bit.paint('blue')
    turn_around(bit)
    while bit.front_clear():
        bit.move()
    bit.left()


def paint_column_blue(bit):
    bit.move()
    bit.left()
    bit.paint('blue')
    while bit.front_clear():
        bit.move()
        bit.paint('blue')
    turn_around(bit)
    while bit.front_clear():
        bit.move()
    bit.left()


def blue_ocean(bit):
    paint_first_column_blue(bit)
    while bit.front_clear():
        paint_column_blue(bit) # No glue code

        
# Very Bad, one function
def blue_ocean(bit):
    while bit.front_clear():   
        bit.left()
        bit.paint('blue')
        while bit.front_clear():
            bit.move()
            bit.paint('blue')
        turn_around(bit)
        while bit.front_clear():
            bit.move()
        bit.left()
        bit.move()
    
    bit.left()
    bit.paint('blue')
    while bit.front_clear():
        bit.move()
        bit.paint('blue')
    turn_around(bit)
    while bit.front_clear():
        bit.move()
    bit.left()

The good examples use functions to reduce duplicate code. Some repeated glue code is ok. The ‘Very Bad’ example does not uses functions.

The ‘Bad’ example has two functions that do nearly the same thing. To avoid this, use glue code. Glue code is purposefully outside a function, enabling varying behavior before or after a function call. In the ‘Bad’ example, paint_column_blue() is nearly the same as paint_first_column_blue(), but it has an extra bit.move(). To decompose this problem well, the bit.move() should be outside the function. To see the bit.move() used as glue code, take a look at both ‘Good’ examples.

In Units 1 and 2, if you have two nearly identical functions, put the duplicated code in one function, and use glue code to change the behavior before and after the function call.

Unit 3 onward, if you have two similar functions, combine them into a single function that uses a parameter to control the difference.

# Good
def get_list(prompt):
    items = []
    while True:
        response = input(prompt)
        if response = '':
            break
        items.append(response)
    return items
    

def main():
    renters = get_list("Renter: ")
    places_to_rent = get_list("Rental Property: ")



# Bad
def get_renters():
    renters = []
    while True:
        name = input("Renter: ")
        if name = '':
            break
        renters.append(name)
    return renters


def get_addresses():
    addresses = []
    while True:
        address = input("Rental Property: ")
        if address = '':
            break
        addresses.append(address)
    return addresses


def main():
    renters = get_renters()
    places_to_rent = get_addresses()

Imports

  • Import modules at the top of your file.
# Good
import math


def main():
    pass


# Bad
def main():
    pass

import math


# REALLY BAD
def main():
    import math

In this class, we will import modules at the top of the file. However, in some rare cases, you may see imports inside functions, but this is outside the scope of this class.

if __name__ == '__main__':

  • If you want your code to be run as a python program, include if __name__ == '__main__' and call your primary function from there.
  • Put the code you want to run in the if __name__ == '__main__': block.
# Good
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2


def main():
    radius = 5
    area = calculate_area_of_circle(radius)
    print(area)


if __name__ == '__main__':
    main()


# Bad
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2


radius = 5
area = calculate_area_of_circle(radius)
print(area)


# REALLY BAD
if __name__ == '__main__':
    def calculate_area_of_circle(radius):
        return 3.14 * radius ** 2

    radius = 5
    area = calculate_area_of_circle(radius)
    print(area)

Style

In this section, we will cover the style conventions for writing Python code. These conventions are important because they help you write code that is easy to read and understand. When you write code that is easy to read and understand, it is easier to debug and maintain. Aspects of style that we will cover include: naming conventions and whitespace.

Naming

Case

There are many different ways to name things in programming. For example, you can use snake case or camel case.

In this class, we will use snake case for variables and functions.

Snake case is when you use lowercase letters and separate words with underscores. For example:

# Snake case 
pie_flavor = 'key lime'

def get_flavors():
    pass

Camel case is when you use lowercase letters for the first word and uppercase letters for the first letter of each subsequent word. For example:

# Camel case
pieFlavor = 'key lime'

def pieFlavor():
    pass

Again in this class, since we are using Python, we will use snake case for variables and functions names which is the standard for Python. You will see camel case in other classes and languages such as c++ and Java.

Variables

  • Use snake case for variable names.
  • Be descriptive with variable names.
  • Short names are fine, to an extent.
# Good
first_name = 'Emma'
last_name = 'Smith'
small_number = 5
small_num = 5
pokemon_type = 'water'
poke_type = 'water'

# Bad
name = 'Emma'
otherName = 'Smith'
num = 5
typ = 'water'

# REALY BAD
thing = 'Emma'
this = 'Smith'
that = 5
other = 'water'

In the example above, the good example uses snake case for variable names and is descriptive. The bad example uses camel case and vague variable names. While the really bad example uses non-descriptive variable names.

Functions

  • Use snake case for function names.
  • Be descriptive with function names.
  • Short names are good, to an extent.
# Good
def calculate_area_of_circle(radius):
    ...

def calc_circle_area(radius):
    ...

# Bad
def calculateAreaOfCircle(radius):
    ...

def cir_ar(radius):
    ...

# REALLY BAD
def doThing(radius):
    ...

In the example above, the good example uses snake case for function names and is descriptive. The bad example uses camel case for function names. The really bad example uses non-descriptive function names.

Arguments

  • Use snake case for function arguments.
  • Be descriptive with function arguments.
# Good
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2

# Bad
def calculate_area_of_circle(Num):
    return 3.14 * Num ** 2

# REALLY BAD
def calculate_area_of_circle(foo):
    return 3.14 * foo ** 2

In the example above, the good example uses snake case for function arguments and is descriptive. The bad example uses camel case and vague funciton arguments. The really bad example uses non-descriptive function arguments.

White space

Indentation

  • Use 4 spaces for indentation.

Notice the spacing

# Good
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2


# Bad
def calculate_area_of_circle(radius):
  return 3.14 * radius ** 2


# Really Bad
def calculate_area_of_circle(radius):
        return 3.14 * radius ** 2

Pay close attention to the indentation in the examples above. The good example uses 4 spaces for indentation while the bad example uses 2 spaces. The really bad example has the code indented twice after the function definition.

Blank lines

  • Use 2 new lines before and after functions.
# Good
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2

    
def calculate_area_of_square(side):
    return side ** 2


# Bad
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2
def calculate_area_of_square(side):
    return side ** 2

In the example above, the good example uses 2 blank lines before and after functions. The bad example does not use blank lines before and after functions.

Spaces before and after operators

  • Use 1 white space before and after operators.
# Good
def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2


# Bad
def calculate_area_of_circle(radius):
    return 3.14*radius**2

In the example above, the good example uses 1 space before and after operators. The bad example does not use spaces before and after operators.

Format Code

In Pycharm you can format your code by pressing Ctrl + Alt + L on Windows or Cmd + Option + L on Mac. This will format your code according to the style conventions we have covered in this section.

It will turn this:

def calculate_area_of_circle(radius):
           return 3.14 * radius ** 2

def calculate_area_of_square(side):
    return side**2


def main():
    radius = 5
      area = calculate_area_of_circle(radius)
                print(area)

Into this:

def calculate_area_of_circle(radius):
    return 3.14 * radius ** 2


def calculate_area_of_square(side):
    return side ** 2


def main():
    radius = 5
    area = calculate_area_of_circle(radius)
    print(area)

General Example

import sys


def get_items(how_many, prompt):
    items = []
    while len(items) < how_many:
        item = input(prompt + ': ')
        items.append(item)
    return items


def display_items(items):
    for item in items:
        print(f'- {item}')
        
        
def main(how_many, prompt):
    items = get_items(how_many, prompt)
    display_items(items)


if __name__ == "__main__":
    how_many = int(sys.argv[1])
    prompt = sys.argv[2]
    main(how_many, prompt)

Notice how the code is broken into functions and the primary function is called from the if __name__ == '__main__': block. This makes the code easier to read, understand, debug, and maintain. Also notice how the imports are at the top of the file and the code is well styled. Meaning it uses snake case for variable and function names, detailed variable and function names, 4 spaces for indentation, 2 blank lines before and after functions, and 1 space before and after operators.