Programming errors
Reading time10 minIn 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.
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
):
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.
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:
[3, 1, -2, -4, 8, 4, -9, -2, 3, 3]
[-9, -1, -6, -7, -4, -7, -10, -8, -9, -10]
Or sometimes:
[-2, -7, -9, -1, -6, -7, -7, -6, -2, -7]
[-5, -4, -3, -9, -10, -5, -1, -1, -7, -5]
Or:
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
[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)
[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)
[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!
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
andnumpy.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 forseed = 0
, it is unfair to report performance obtained for the first case. Indeed, it just means that42
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
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
-
This is a formal way of describing a program’s behavior to show its correctness.
-
A programming paradigm relying on notions above to design software.