Errors: Forms Of Error Information

From Matt Morris Wiki
Jump to navigation Jump to search

Programming - Errors


To talk about which way of handling errors is best, we'll need to get a good understanding of what alternative forms error information can take. It will turn out that there is no single form that is always the best way to convey error information from the point where it occurs to the point where it is handled.

Return Values

Methods can have return values. The return value can be a simple number, or possibly a more complex structure. One can use return values to communicate whether the method succeeded or failed. The return value may also convey some information about the nature of the error.

Here is an example function getFileDiffReturnsValue(), as part of a system that uses return values to propagate error information:

ErrorClass getFileDiffReturnsValue(DiffHolder h, String fileName1, String fileName2)
{
  FileProcessor fp1 = new FileProcessor();
  FileProcessor fp2 = new FileProcessor();
  ErrorClass ret = fp1.openInputFile(fileName1); // might return error
  if( ret == null )
  {
    ret = fp2.openInputFile(fileName2); // might return error
    if( ret == null )
    {
      ret = h.calcDiff(fp1, fp2); // might return error
    }
    fp1.closeAnyOpenInputFile();
    fp2.closeAnyOpenInputFile();
  }
  return ret;
} 

Here is a function, calling getFileDiffReturnsValue(), that propagates errors. Note that errors need to be stored in a variable and returned back up to the calling function, otherwise the caller will not know that an error has occurred.

ErrorClass func(DiffHolder h, String fileName1, String fileName2)
{
  // Save any error information in a variable
  ErrorClass ret = getFileDiffReturnsValue(h, fileName1, fileName2);
  
  // Some more code goes here ...
  
  // Need to pass ret back to the caller eventually
  return ret;
}

Here is a code fragment, calling getFileDiffReturnsValue(), that handles errors. It needs to test for an error return value, and then take appropriate action.

ErrorClass ret = getFileDiffReturnsValue(h, fileName1, fileName2);
if( ret != null )
{
  errorHandlingRoutine(ret);
}

(Unchecked) Exceptions

Note: the Java language has two forms of exception: "unchecked" and "checked". The "unchecked" exceptions have the same behaviour as exceptions in a number of other languages such as C++, Python and C#, and are the ones described here.

If using exceptions, then when an error is encountered, an object holding information about that error is created and "thrown". The language then "unwinds" the call stack until it encounters a "catch" statement that is expecting a variable of an appropriate type.

The "catch" statement can then either continue execution, or throw an exception again (which may be the original exception, or something totally different).

Garbarge-collected languages (such as Java, C#, Python) also allow a "finally" statement to denote code that will be executed whether an exception is thrown or not. This helps ensure resources are released properly.

Here is an example function getFileDiffThrowsException(), as part of a system that uses exceptions to propagate error information:

void getFileDiffThrowsException(StuffHolder h, String fileName)
{
  FileProcessor fp1 = new FileProcessor();
  FileProcessor fp2 = new FileProcessor();
  try
  {
    fp1.openInputFile(fileName1); // might throw exception
    fp2.openInputFile(fileName2); // might throw exception
    h.calcDiff(fp1, fp2); // might throw exception
  }
  finally
  {
    fp1.closeAnyOpenInputFile();
    fp2.closeAnyOpenInputFile();
  }
  return; // no need to return status - if returns at all, we are ok
} 

Here is a code fragment, calling getFileDiffThrowsException(), that propagates errors to whatever called it. Note that no special action is required - one just calls the method, and the exception propagates automatically. Code using exceptions should throw exceptions far more often than it catches and rethrows them, since allowing exceptions to propagate automatically makes the flow of code clearer.

// No special action required: propogates automatically
getFileDiffThrowsException(h, fileName1, fileName2);

Here is a code fragment, calling getFileDiffThrowsException(), that handles errors. It needs to encase the code in a try-block and catch the right exception type.

try
{
  getFileDiffThrowsException(h, fileName1, fileName2);
}
catch( BaseExceptionClass e )
{
  errorHandlingRoutine(e);
}

Java's Checked Exceptions

Java's checked exceptions look like normal exceptions, but any function throwing them must declare the fact in its signature. Any other function calling the original must then either catch the checked exceptions, or specify them in its own signature: this is called the catch or specify requirement . If this is not done, the compiler will report an error.

This gives Java's checked exceptions an element of both return values (they are not automatically propagated, you have to deal with them manually), and of unchecked exceptions (they are "thrown", so don't need to be conveyed in a return value), together with having the added feature that the compiler can check whether they are being handled or not.

Java is the only mainstream business application language to offer checked exceptions, and there is a lot of difference of opinion on whether and how they should best be used. Frequently people try to convert them as soon as they see them into one of the forms of error information that they understand better, either by wrapping them in an unchecked exception, or by converting them into a return value, or (worst of all) by simply "swallowing" them and pretending they never happened.

A full discussion of checked exceptions is available here.

Signals

Signals are generally raised by the type of error that does not come from a single method call, but might happen anywhere: examples include an attempt to access an invalid memory location, or an invalid processor instruction being encountered.

Signals are dealt with by "handlers", which must be registered by the program. There is typically not much a handler can do to continue execution - the default signal handlers for program errors will generally terminate the program, possibly dumping the state to a core file.

Because of this, we'll be dealing with other, recoverable, forms of error handling in this document.