Programming errors

Reading time10 min

In brief

Article summary

In this article, we analyze some errors that can occur when writing a program, and discuss the particular case of programs with random elements.

Main takeaways

  • There are two types of errors: syntactic and runtime errors.

  • Random numbers may make debugging hard, therefore setting a seed is a good practice.

Article contents

Programming is an incremental process, meaning that the code has to be built step-by-step, each step has to be tested before moving to the next one. Development time may be roughtly divided into three iterative phases:

  • Coding.
  • Debuging.
  • Documenting.

And debugging definitely takes up a lot of development time. An important skill to acquire is to efficiently identify the source of bugs and to fix them. Many tools have been conceived to help you quickly determine where programming errors and bugs are located. But before discovering such specific tools, it is mandatory to learn how to interpret error messages rendered by the interpreter (resp. compiler).

The Python interpreter may detect errors during the interpretation of your code that are mainly of two types.

1 — Syntactic errors

Syntactir errors are located in the text of your code, and can be detected without executing the code (during the parsing/compilation phases). They are quite explicitly explained by the interpreter as it provides you with a description of the error and the line where it occurs.

Example

Consider the following program:

# The string to manipulate
s = "IMT Atlantique"
print(s)

# Loop to append to a new string
result = ""
for i j in range(len(s)):
    result = s[i] + result

# Print the result
print("Reversed string:", result)
/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     * 
     * @param args Command line arguments received.
     */
    public static void main(String[] args) {
        // The string to manipulate
        String s = "IMT Atlantique";
        System.out.println(s);
        
        // Loop to append to a new string
        String result = "";
        for (int i j = 0; i < s.length(); i++) {
            result = s.charAt(i) + result;
        }

        // Print the result
        System.out.println("Reversed string: " + result);
    }

}

Here, the interpreter tells us that at line 7 of your code, the token j is unexepected at this position, as it cannot follow another token corresponding to variable name (i.e., i):

Output
File "session_1.py", line 7
  for i j in range(len(s)):
          ^
SyntaxError: invalid syntax
Main.java:25: error: ';' expected
        for (int i j = 0; i < s.length(); i++)
                  ^
Main.java:25: error: not a statement
        for (int i j = 0; i < s.length(); i++)
                            ^
Main.java:25: error: ')' expected
        for (int i j = 0; i < s.length(); i++)
                                        ^
Main.java:25: error: ';' expected
        for (int i j = 0; i < s.length(); i++)
                                             ^
4 errors

2 — Runtime errors

Runtime errors appear only when the program is running (during the execution phase). They are more tricky to solve because they often depend on the context of execution. Here are a few examples that can cause such error:

  • An unauthorized operation considering the actual types of the concerned values.
  • An input/output operation on an unavailable resource (e.g., a file).
  • An access to unavailable area of the memory.
Example

Consider the following program:

# Ask user at runtime for a value
den = input("Please enter the value of the denominator")

# Perform a computation
result = 42 / den
print(result)
// Needed imports
import java.util.Scanner;

/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     * 
     * @param args Command line arguments received.
     */
    public static void main (String[] args) {
        // Ask user at runtime for a value
        Scanner scanner = new Scanner(System.in);
        System.out.print("Please enter the value of the denominator: ");
        String denStr = scanner.nextLine();
        double den = Double.parseDouble(denStr);

        // Perform a computation
        double result = 42 / den;
        System.out.println(result);
    }

}

Although syntactically correct, the third line of the following code will generate a division by zero runtime error if 0 is assigned to den.

3 — The case of random numbers

Sometimes, programs make use of random numbers. Consider for instance the following program:

# Needed imports
import random

# Fill a list with 10 random numbers
numbers = [random.randint(-10, 10) for i in range(10)]

# Generate an error if 0 is in the list
if 0 in numbers:
    raise ValueError("Zero is in the list")

# Otherwise, print the list
print(numbers)
// Needed imports
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     * 
     * @param args Command line arguments received.
     */
    public static void main (String[] args) {
        // Create a random number generator
        Random random = new Random();

        // Fill a list with 10 random numbers
        List<Integer> numbers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            numbers.add(random.nextInt(11) - 10);
        }

        // Generate an error if 0 is in the list
        if (numbers.contains(0)) {
            throw new IllegalArgumentException("Zero is in the list");
        }

        // Otherwise, print the list
        System.out.println(numbers);
    }

}

Sometimes, when running your program, you will get the following output:

Output
[3, 1, -2, -4, 8, 4, -9, -2, 3, 3]
[-9, -1, -6, -7, -4, -7, -10, -8, -9, -10]

Or sometimes:

Output
[-2, -7, -9, -1, -6, -7, -7, -6, -2, -7]
[-5, -4, -3, -9, -10, -5, -1, -1, -7, -5]

Or:

Output
Traceback (most recent call last):
  File "session_1.py", line 9, in <module>
    raise ValueError("Zero is in the list")
ValueError: Zero is in the list
Exception in thread "main" java.lang.IllegalArgumentException: Zero is in the list
	at Main.main(Main.java:37)

Well, that’s expected. However, imagine a more complex scenario where the toy error above is a really important error you need to avoid. It is going to be pretty hard to debug, as sometimes your program will work as expected, and sometimes it will crash due to a semantic error.

In practice, it is possible to force a series of random numbers to always be the same. Indeed, random numbers are in fact pseudo-random. They are generated using a deterministic series that has good randomness properties, which is initialized using a “seed”, i.e., a first initial value. If the seed is known, then, the entire series can be predicted. Even better, if the seed can be set to an arbitrary value, then we can enforce that all executions of the same program will lead to the same generation of random numbers.

Each random number generator provides a way to set the seed to a given value (here, 42). This can be done as follows:

# Needed imports
import random

# Set the seed
random.seed(42)

# Fill a list with 10 random numbers
numbers = [random.randint(-10, 10) for i in range(10)]

# Generate an error if 0 is in the list
if 0 in numbers:
    raise ValueError("Zero is in the list")

# Otherwise, print the list
print(numbers)
// Needed imports
import java.util.Random;

/**
 * To run this code, you need to have Java installed on your computer, then:
 * - Create a file named `Main.java` in a directory of your choice.
 * - Copy this code in the file.
 * - Open a terminal in the directory where the file is located.
 * - Run the command `javac Main.java` to compile the code.
 * - Run the command `java -ea Main` to execute the compiled code.
 * Note: '-ea' is an option to enable assertions in Java.
 */
public class Main {

    /**
     * This is the entry point of your program.
     * It contains the first codes that are going to be executed.
     * 
     * @param args Command line arguments received.
     */
    public static void main (String[] args) {
        // Create a random number generator
        Random random = new Random();

        // Set the seed
        random.setSeed(42);

        // Fill a list with 10 random numbers
        List<Integer> numbers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            numbers.add(random.nextInt(11) - 10); 
        }

        // Generate an error if 0 is in the list
        if (numbers.contains(0)) {
            throw new IllegalArgumentException("Zero is in the list");
        }

        // Otherwise, print the list
        System.out.println(numbers);
    }

}

Let’s run our program three times again

Output
[10, -7, -10, -2, -3, -3, -6, -7, 7, -8]
Exception in thread "main" java.lang.IllegalArgumentException: Zero is in the list
	at Main.main(Main.java:36)
Output
[10, -7, -10, -2, -3, -3, -6, -7, 7, -8]
Exception in thread "main" java.lang.IllegalArgumentException: Zero is in the list
	at Main.main(Main.java:36)
Output
[10, -7, -10, -2, -3, -3, -6, -7, 7, -8]
Exception in thread "main" java.lang.IllegalArgumentException: Zero is in the list
	at Main.main(Main.java:36)

As you see, the result is always the same. Now, what you can do to debug your progam is to find a value of the seed that leads to an error. Then, keep that seed set and try to see what happens with this particular problematic random configuration!

Important

A few important remarks:

  • The list you obtain may be different from a computer to another, as the random number generator used may be different. Setting the seed only guarantees reproducibility on a single machine.

  • Each random number generator comes with its own seed. If you use functions from library random and numpy.random for instance, you will have to set the seeds of the two generators to guarantee reproducibility.

  • You cannot use the random seed to change the performance of your programs. For instance, if your algorithm that incorporates random elements solves a problem better with seed = 42 than it does for seed = 0, it is unfair to report performance obtained for the first case. Indeed, it just means that 42 leads to a lucky initialization of your algorithm for this particular problem instance, but it will not be the case in general. Reporting performance for random algorithms should always be done by running the program numerous time and providing averages and confidence intervals!

To go further

Important

The content of this section is optional. It contains additional material for you to consolidate your understanding of the current topic.

Looks like this section is empty!

Anything you would have liked to see here? Let us know on the Discord server! Maybe we can add it quickly. Otherwise, it will help us improve the course for next year!

To go beyond

Important

The content of this section is very optional. We suggest you directions to explore if you wish to go deeper in the current topic.

  • Hoare logic.

    This is a formal way of describing a program’s behavior to show its correctness.

  • Design by contract.

    A programming paradigm relying on notions above to design software.