How to debug a function like a computer scientist

Debugging is a valuable skill for every software developer. It is the art of diagnosing errors in programs and determining how to correct them. To debug a program, it’s important to break it into smaller functions so that you have natural checkpoints.

Today, I want you to learn how professional developers or computer scientists debug their functions. They all follow these three steps:

  1. Precondition
  2. Postcondition
  3. Return value(s)

Before diving into these steps, let’s first understand what a bug is.

What’s a bug ?

A bug is a semantic error that shows up as incorrect behavior rather than as a computation error. As a programmer, you’ll spend your time tracking down the cause of a bug in the program’s source code and eliminating it.

It’s possible to minimize bugs by careful design and careful coding, but the sad fact is that you will probably always find them. So, don’t be afraid of bugs.

These are some bug situations that can be hard to debug:

  • Using 1 instead of i when working with loops
  • Calling a function with a misspelled name: sendemail() instead of sendEmail()

To debug such errors, it can be helpful to have someone who doesn’t share your preconceptions look at your code. Or, you can use a debugger that comes with your programming environment.

Debugging statements
There’s also a traditional approach that consists of inserting debugging statements into your program. They are output statements that print out info about the state of the program. A typical debugging statement would look like:

print("At the beginning of the loop, x= ", x)

Now, let’s discuss our three steps.

Preconditions

Preconditions are requirements that should be satisfied by the caller before a function starts. If you run into issues with a function in your program, you first need to check that the preconditions are not violated.

Let’s take an example:

def greet(greeting, name):
    return greeting + name + '!'

This function accepts two parameters, but when you call it like this:

greet('Sarah')

You’ll get into trouble.

Why? Simply because you forgot one required argument. So, you should always make sure that the preconditions are satisfied.

# Function definition
def greet(greeting, name):
return greeting + name + '!'
# Call the function
print(greet("Sarah"))

Note: If you violate the preconditions as a function caller, don’t assume that the function is buggy. Instead, check what you’re passing to it.

Postconditions

If the preconditions are satisfied but your function still does not work well, you can assume that the bug is in the function itself. This is where postconditions come in. Postconditions are conditions in the function body. More precisely, they are the expectations of the function (like greeting someone with their name).

Let’s take the same example from the previous step and modify it a bit:

def greet(greeting, name):
    return name + '!'

You won’t get the expected result when you call this function, as the bug is not in the header but in the body.

greet('Morning', "Lifaefi")

The output will be Lifaefi!. This is not the expected output, which means that our function has a bug we need to fix.

# Function definition
def greet(greeting, name):
return name + '!'
# Call the function
print(greet('Morning', "Lifaefi"))

Return value(s)

If everything looks good in the function body, but you’re still experiencing any issue with your function, the last point to check is the return value(s). A return value is the value generated by any function call. Make sure you answer the following:

  • Did you make correct use of the function return value(s)?
  • Did you use the return value(s) at all?

Considering our greet function, let’s create another function that makes use of it.

def main():
    greeting = input("How can I greet you?")
    name = input("\nHow can I call you?")
    customGreet = greet(greeting, name) 
    return "\n" + greeting

Now, we can call the function using main(). However, you’ll notice that you won’t get the expected output. Why? It’s because we are not making use of the correct return value from the greet() function.

To run the code below, add the input in the cell and then press the Run button. Take a look at the following sample input:

Hello
Abel

# Function definition
def greet(greeting, name):
return greeting + name + '!'
def main():
greeting = input("How can I greet you?")
name = input("\nHow can I call you?")
customGreet = greet(greeting, name)
return "\n" + greeting
# Call the function
print(main())

Enter the input below

Conclusion

That is it for today. A bug is an unexpected error in a program. Debugging, the art of tracking down errors, can be frustrating at the beginning but it is an interesting part of programming.

So far, we’ve learned three steps to follow in order to debug a function:

  1. Precondition: a requirement that should be satisfied by the caller before a function starts.
  2. Postcondition: a requirement that should be satisfied by the function before it ends.
  3. Return value: result of a function call

The golden rule of debugging:

If you are absolutely sure that everything in your program is right, and if it still doesn’t work, then one of the things that you are absolutely sure of is wrong

Happy debugging!

Free Resources