Computer Programming/Error handling

This computer programming article is available in pseudocode and Ada.

Error handling techniques

edit

This chapter describes various error handling techniques. First the technique is described, then its use is shown with an example function and a call to that function. We use the √ function which should report an error condition when called with a negative parameter.

Return code

edit
function √ (X : in Float) : Float
begin
   if (X < 0) :
      return -1
   else
      calculate root from x
   fi
end
C := √ (A2 + B2)

if C < 0  then
   error handling
else
   normal processing
fi

Our example make use of the fact that all valid return values for √ are positive and therefore -1 can be used as an error indicator. However this technique won't work when all possible return values are valid and no return value is available as error indicator.

Error (success) indicator parameter

edit

An error condition is returned via additional out parameter. Traditionally the indicator is either a boolean with "true = success" or an enumeration with the first element being "Ok" and other elements indicating various error conditions.


function √ (
   X       : in Float; 
   Success : out Boolean ) : Float
begin
   if (X < 0) :
      Success := False
   else
      calculate root from x
      Success := True
   fi
end
C := √ (A2 + B2, Success)

if not Success then
   error handling
else
   normal processing
fi

This technique does not look very nice in mathematical calculations.

Global variable

edit

An error condition is stored inside a global variable. This variable is then read directly or indirectly via a function.


function √ (X : in Float) : Float
begin
  if (X < 0) :
     Float_Error := true
  else
     calculate root from x
  fi
end
Float_Error := false
C := √ (A2 + B2)

if Float_Error then
   error handling
else
   normal processing
fi

As you can see from the source the problematic part of this technique is choosing the place at which the flag is reset. You could either have the callee or the caller do that.

Also this technique is not suitable for multithreading.

Exceptions

edit

The programming language supports some form of error handling. This ranges from the classic ON ERROR GOTO ... from early Basic dialects to the try ... catch exceptions handling from modern object oriented languages.

The idea is always the same: you register some part of your program as error handler to be called whenever an error happens. Modern designs allow you to define more than one handler to handle different types of errors separately.

Once an error occurs the execution jumps to the error handler and continues there.


function √ (X : in Float) : Float
begin
   if (X < 0) :
      raise Float_Error
   else
      calculate root from x
   fi
end
try:
   C := √ (A2 + B2)
   normal processing
when Float_Error:
   error handling
yrt

The great strength of exceptions handling is that it can block several operations within one exception handler. This eases up the burden of error handling since not every function or procedure call needs to be checked independently for successful execution.

Design by Contract

edit

In Design by Contract (DbC) functions must be called with the correct parameters. This is the caller's part of the contract. If the types of actual arguments match the types of formal arguments, and if the actual arguments have values that make the function's preconditions True, then the subprogram gets a chance to fulfill its postcondition. Otherwise an error condition occurs. Now you might wonder how that is going to work. Let's look at the example first:


function √ (X : in Float) : Float
    pre-condition (X >= 0)
    post-condition (return >= 0)
begin
   calculate root from x
end
C := √ (A2 + B2)

As you see the function demands a precondition of X >= 0 - that is the function can only be called when X ≥ 0. In return the function promises as postcondition that the return value is also ≥ 0.

In a full DbC approach, the postcondition will state a relation that fully describes the value that results when running the function, something like result ≥ 0 and X = result * result. This postcondition is √'s part of the contract. The use of assertions, annotations, or a language's type system for expressing the precondition X >= 0 exhibits two important aspects of Design by Contract:

  1. There can be ways for the compiler, or analysis tool, to help check the contracts. (Here for example, this is the case when X ≥ 0 follows from X's type, and √'s argument when called is of the same type, hence also ≥ 0.)
  2. The precondition can be mechanically checked before the function is called.

The 1st aspect adds to safety: No programmer is perfect. Each part of the contract that needs to be checked by the programmers themselves has a high probability for mistakes.

The 2nd aspect is important for optimization — when the contract can be checked at compile time, no runtime check is needed. You might not have noticed but if you think about it:   is never negative, provided the exponentiation operator and the addition operator work in the usual way.

We have made 5 nice error handling examples for a piece of code which never fails. And this is the great opportunity for controlling some runtime aspects of DbC: You can now safely turn checks off, and the code optimizer can omit the actual range checks.

DbC languages distinguish themselves on how they act in the face of a contract breach:

  1. True DbC programming languages combine DbC with exception handling — raising an exception when a contract breach is detected at runtime, and providing the means to restart the failing routine or block in a known good state.
  2. Static analysis tools check all contracts at analysis time and demand that the code written in such a way that no contract can ever be breached at runtime.


Language overview

edit

This list gives an overview of the standard or primary behavior and error handling techniques used in various programming languages. This does not mean that other techniques or behavior are not possible in the programming languages named.

Error handling in programming languages
Language Technique null / 0 array int float type
Ada Book of the Month September 2005 exceptions/DbC1 handled handled handled handled handled handled
C return/var crash crash fault fault2 fault fault2
C++ exceptions crash crash fault fault2 fault fault2
Python exceptions handled handled handled handled handled handled
SPARK DbC handled handled handled handled handled handled
Error Types
null null pointer or nil element access.
/ 0 division by 0.
array out of bound array access.
int out of bound integer range/overflow.
float out of bound floating point calculations.
type out of bound type conversions.
Handled by
handled the error is handled by the language in a proper manner - for example with the use of error or exception handlers.
fault the program continues to run in an undefined or faulty manner.
crash program crashes.
Technique
DbC The Language uses Design by Contract to avoid error condition.
exceptions Language raises an exception or uses a similar technique when an error condition appears.
return Language uses an return code to indicate an error condition.
var Language uses one or more global variables to indicate an error condition.

1 : Ada supports a very limited form of DbC through its strong typing system.
2 : In C and C++ the behavior is defined and sometimes intentionally used however in this comparison we consider unintentional use.