Chapter 4: Refactoring

Refactoring your prime number validator

face Josiah Wang

Did you manage to implement is_prime_number() (and maybe even read_integer())? Great!

Here is my implementation. Do you find this easier to read and understand than the original version?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def is_prime_number(number):
    if number <= 1:
        return False

    divisor = 2
    while divisor < number:
        if number % divisor == 0:
            return False
        else:
            divisor += 1
    return True


def read_integer():
    entry = input("Please enter an integer: ")
    return int(entry)


n = read_integer()
if is_prime_number(n):
    print(f"{n} is a prime number")
else:
    print(f"{n} is not a prime number")

What you have done is called refactoring. This is the process of restructuring your code without changing its underlying behaviour.

You can actually go further with the refactoring process and abstract Line 7 in the code above: number % divisor == 0. When I look at that line of code, I still need to think what the modulus operation is doing and why are we comparing it to 0 etc. But if you give it a semantically meaningful label, say is_divisible_by(), suddenly you may find that the code is even easier to read!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def is_divisible_by(number, divisor):
    return number % divisor == 0


def is_prime_number(number):
    if number <= 1:
        return False

    divisor = 2
    while divisor < number:
        if is_divisible_by(number, divisor):
            return False
        else:
            divisor += 1
    return True


def read_integer():
    entry = input("Please enter an integer: ")
    return int(entry)


n = read_integer()
if is_prime_number(n):
    print(f"{n} is a prime number")
else:
    print(f"{n} is not a prime number")

You will find that by just reading this code, you can already understand the main algorithm itself without even needing any comments. It does read quite like English. If you name your functions well, you can use them at a good level of abstraction.

The key takeaway is that you should break down your program into smaller, manageable, and reusable chunks. Ideally, the chunks should also be independent and not depend on other parts (modular). Other than making it easier to read, writing code this way will be less overwhelming and more pain-free.

You can think about your algorithm at a higher level of abstraction, freeing your mind from the small details until you need to think about them (then you can zoom into the details). A bit like trying to assemble a car part by part – have the wheels ready, have each engine parts ready, hook the right parts together, etc., rather than trying to just “build a whole car” (sounds hard!)

This is the kind of code I will be expecting of you from now on, moving forwards!