Errors

Errors #

We all make mistakes when we write code.

Developing efficient strategies to identify mistakes is an essential part of programming.

Compile time vs runtime errors #

In (pre)compiled languages (like Java, C/C++, C#, Python, Rust, Go, etc.), a distinction is generally made between compile time errors and runtime errors.

  • A compile time error prevents a program from compiling. It can be thought of as a “syntactic” error.
  • A runtime error (a.k.a. bug) occurs during the execution of a program. It may depend on the input of the program.

Hint. Compile time errors tend to be (much) easier to fix than bugs, especially in large projects. Besides, an IDE can identify some compile time errors (and highlight them) before the program is even compiled.

So it is often good practice to write your code in such a way that a maximal number of errors can be identified at compile time.

Note. For interpreted languages (like Javascript, Lua, etc.), the term “compile time error” is meaningless. However, a distinction can still be made between errors that can be preemptively identified (e.g. highlighted by an IDE), and errors that can only be detected at runtime.

Error messages #

A precise error message can significantly simplify debugging.

As a toy example, consider the following Java program:

public class City {

    String name;
    int zipCode;

    public City(String name, int zipCode) {
        this.name = name;
        this.zipCode = zipCode;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MyClass{

    void myMethod(City[] cities){

        City firstCity = cities[0];
        if(firstCity.zipCode >= 20000) {
            System.out.println(firstCity.name.length());
        }
    }
}

In this program, there are several reasons why the method MyClass.myMethod may fail at runtime. Can you identify some of them?

  • the input array may be null,
  • the input array may have length 0,
  • the input array may be empty,
  • the first city in this array may have a zip code $\ge$ 20 000 and no name.

In the above example, if we execute the following,

    myMethod(null);

then we obtain this error message:

java.lang.NullPointerException: Cannot load from object array because "cities" is null
	at MyClass.myMethod(MyClass.java:5)

Note that this message provides a clear indication of:

  • the file (MyClass.java), method in this file (MyClass.myMethod) and line of code (5) that caused the error,
  • the type of error (NullPointerException),
  • the cause of the error (the variable city has value null).

Hint. A NullPointerException in Java often indicates an attempt to access an object (or array) via a pointer (e.g. a variable) whose value is null.

Similarly, if the method is called with an empty array,

    myMethod(new City[]{});

we get the following error message:

java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
	at MyClass.myMethod(MyClass.java:5)

Which type of error will the following call to myMethod produce?

    myMethod(new City[]{ new City(30100, null) });
java.lang.NullPointerException: Cannot invoke "String.length()" because "firstCity.name" is null
	at MyClass.myMethod(MyClass.java:7)

Anticipation #

Consider the following method:

int least(int x, int y){
  return x <= y ?
      x : y;
}

This method has two desirable features (shared with mathematical functions):

  • any arguments for this method (i.e. any pair of integers in this example) are a valid input,
  • its execution only depends on its arguments, i.e. if the method is called twice with the same arguments, then it has the same behavior.

However, this is not the case of all methods.

If one of these two condition does not hold, then it may be useful to write your own error messages, in anticipation. This may benefit:

  • users of the program, and
  • collaborators (including your future self!) who will maintain (debug, extend, etc.) the program.

Example. For instance, consider a method

boolean isSolvable (int[][] grid);

that takes as input a grid of sudoku, and returns true iff it is solvable, meaning that this grid admits a unique solution. A valid input for this problem could be a 9 x 9 array of integers with values between 0 and 9 (where 0 indicate the absence of value). However, Java does not provide such a precise data type.

It may be useful to produce an (informative) error message if the input array:

  • is not 9 x 9, or
  • contains a value < 0 or > 9.

Controlled environment #

Anticipating all types of invalid inputs/scenarios (and producing error messages for these) may induce a lot of boilerplate code. If you have control over the input and/or execution environment of a method, then this may not be necessary. For instance, in the case of auxiliary methods (i.e. methods that are not visible to other classes or components).

Example (continued). Consider the method boolean isSolvable (int[][] grid) described above. Let us assume that it is private, and called only by a method generateGrid that generates a grid pseudo-randomly.

It may be safe to assume that the implementation of generateGrid only produces 9 x 9 arrays with values between 0 and 9. In this scenario, there is probably no need to implement error messages in isSolvable.