5.4 Error Checking

One of the most important aspects of writing programs is making sure they work properly. This can sometimes be a little tedious but is vital. Errors in programs can run the gamut from making programs crash to producing incorrect results. These errors are often called runtime errors because they happen when the program is running. These are harder to catch and fix than syntax errors which will prevent a program from running.

In this chapter we've been working with arrays. There are two ways of getting into trouble with arrays. The most common is having an index that is out of bounds. The other is an issue with the value that is being stored in the array element. The best way to avoid an error with the index when working with an entire array is to use a for loop to traverse the array. There are times however, when you don't necessarily need to go through the whole array. You need to be more careful when using a loop to traverse the array.

In the following example we are going to look through an array of names to see if we can find a particular name. It will print the index of where it was found, or tell us that it wasn't found. We will need to exit the loop if we find the name. If it is not found we need to be careful not to keep going past the end of the array.

var names : array 1 .. 10 of string := init("Bob", "Mary", "James", "Steve",
                         "Jane","Lebron", "Kawhi", "Beth", "Tanya", "Eric")

var find: string
var i, loc : int := 0

put "What name do you want to look for?"
get find:*

loop
   i := i + 1
   exit when i > 10
   if names(i) = find then
      loc := i
      exit
   end if
end loop

if loc > 0 then
   put "The name was found at index ", loc
else
   put "The name was not found"
end if 

We keep increasing the index (i) looking for the name. We need to make sure that we don't go past the end of the array (i = 11). If i gets to 11 then we know we've checked the whole array and didn't find what we were looking for. We need to get out of the loop before we try to look at names(11) - we would crash the program.

Getting input from a user is another area where errors are common. The input could be outside the range you are looking for. The input could even be of the wrong type (we saw how to guard against that in section 4.3). The following example will demonstrate how to guard against both of those types of errors. This will allow a user to enter scores for a game of golf. The user will first enter what hole they want to enter a score for, and then what the score on that hole will be. To quit entering scores they will enter a negative number for the hole. There will be extensive error checking done in this code. The scores will be stored in an array (indices from 1-18; one for each hole). A score will need to be in the range 1-30 to be considered valid.

var hole, score : int
var input: string
var scores : array 1 .. 18 of int

% Initialize all the scores to 0
for i : 1 .. 18
  scores(i) := 0
end for

loop
  % Get hole - if negative then exit
  loop
    loop
      put "What hole do you want to enter a score for?"
      get input:*
      exit when strintok(input)
      put "Invalid input - Integer only"
    end loop
    hole := strint(input)
    if hole > 18 or hole = 0 then
      put "Invalid input - Hole should be in range (1-18)"
      put "Try again"   
    else
      exit    
  end loop

  exit when hole < 0
  
  % Get score for that hole
  loop
    loop
      put "What was your score on hole #", hole, "?"
      get input:*
      exit when strintok(input)
      put "Invalid input - Integer only"
    end loop
    score := strint(input)
    if score < 1 or score > 30 then
      put "Invalid input - Score should be in range (1-30)"
      put "Try again"
    else
      exit
    end if
  end loop
  scores(hole) := score
end loop

for i : 1 .. 18
  put "Hole #", i, " Score = ", scores(i)
end for 

Another topic that is closely related to error-checking is testing. Before a program is released it should be extensively tested for errors. A set of tests needs to be designed that will probe every possible path through the code. This will ensure that the program is crash proof and that it always produces correct results. As a program increases in complexity, trying to find all these paths becomes increasingly difficult. That is why many large programs need to constantly push out updates to fix errors and vulnerabilities. Testing should be given a large priority when writing programs, but due to deadlines and costs it sometimes does not get the emphasis it deserves. This can lead to very large problems.

Exercise 5.4

  1. Write a fragment that will ensure that a valid value is entered into the variable mark that represents a report card mark. A valid report card mark is an integer value in the range 0 - 100 inclusive.
  2. Depending on the user's input, there are various ways this program could crash:

    var a : array 1 .. 5 of int := init(1, 2, 3, 4, 5)
    var b, c : int
    
    put "Which element do you want to change?"
    get b
    put "What value do you want to change it to?"
    get c
    a(b) := c 

    1. Give examples of different types of input that could cause this program to crash.
    2. Rewrite this program so that it will not have "Array subscript is out of range." errors.
    3. Rewrite this program so that the user cannot enter anything that will cause the program to crash.
  3. A program asks a user for a filename. It will then read the contents of that file and count how many words it contains. What type of errors could occur in this situation?
  4. A program asks the user for a filename. It then repeatedly asks for non-negative numbers to store in that file. It will continue asking for numbers until the user enters a negatvie value. What type of errors could occur in this situation?
  5. Find examples of software that were released with significant errors that were not discovered until later.