Project

General

Profile

Error Handling Policy

Back to Table of Contents

What is Error Handling?

Simply put, Error Handling is the process of preventing and recovering from unexpected conditions within a program. Errors can occur from many sources. Some of these sources include, but are not limited to: invalid data, user errors, computational errors, resource unavailability, and internal Java VM errors. The goal of a well written program is to minimize the number of unhandled errors. Unhandled errors impact proper functionality of the program and user experience.

Exceptions, Errors, and Assertions

Java provides an Exception Handling mechanism in the language specification. Java has provided this error handling mechanism from the first release of the JDK. A Java "Exception" can be thought of as an "exceptional condition," or condition that falls outside the expected or normal program conditions. Java provides a method for trapping and attempting to recover from theses unexpected conditions. As with exceptions, a Java "Errors" can be considered a condition that falls outside the expected or normal condition. However, errors occur from within the Java VM and they are usually unrecoverable. Exceptions and errors are discussed in detail within the Exceptions and Their Use section.

Another effective technique in error handling is the use of Assertions. Assertions are used to state conditions that the developer assumes are true. While assertions are not a new error handling technique, support for assertions at the language level will be introduced in version 1.4 of the JDK. However, the functionality of assertions can be simulated in earlier versions of the JDK. Assertions are discussed in detail within the Assertions and Their Use section.

What is the goal of this document?

The goal of this document is to describe the various error handling techniques available to Java programmers and the policies surrounding the usage of error handling on the GIFT project. This document is divided into three main sections. Exceptions and Their Use describes the exception handling system in Java. Assertions and Their Use describes assertions and the "Design By Contract" programming methodology. Error Handling Policy describes the requirements and policies that have been developed for the use of error handling within the GIFT project.

Exceptions and Their Use

Topics

Understanding exceptions.
Returns that don't.
Don't use as a flow control mechanism.

Java and Exceptions

One of the goals in a well written code is to catch and gracefully handle errors. As stated above, Java supports an error handling mechanism at the language specification level. Java's exception handling system is designed to enforce the use of error handling at the interface and compiler level. When used and implemented properly, this exception handling system can help developers write code that behaves well even under unexpected conditions. While not a complete solution to error handling, exceptions in Java provide a level of support for error handling not found in most other languages. This helps to prevent poorly written or non-existent error handling code.

Every exception in Java has an associated exception object. When an unexpected condition is encountered, an exception object is created. Once created, the exception object is optionally populated with any relevant data and/or message about the unexpected condition. Finally, the exception object is then thrown. The Java Language Specification states that, "An exception is said to be thrown from the point where it occurred and is said to be caught at the point to which control is transferred." Therefore, when an exception is thrown, control will be transferred to a point in code where the unexpected condition can be handled. Exceptions are handled in what is known as a try and catch blocks. For example:

  ...

  try {

    // statements that might throw an exception
    java.io.FileReader reader = new java.io.FileReader(filename);

  }
  catch (java.io.FileNotFoundException fnfe) {

    // handle the exception,
    //  do something about the missing file
    System.out.println("The file " + filename + " was not found.");
    System.out.println("Select another file.");
  }

  ...

Figure 01

In the above example, the try block attempts to create a FileReader object with the specified filename. If the FileReader constructor cannot find the specified filename it will create a FileNotFoundException object and throw it. If the exception is thrown, the try block will end abnormally and control will be transferred to the catch block. In this case, the exception is handled by informing the user that the file was not found. Should the exception not be thrown, any additional code within the try block will be executed and the try block will end normally.

Occasionally, a developer may desire a segment of code to execute after a try block no matter if that block should end normally or abnormally. Consider the following example:

import java.io.FileReader;
import java.io.IOException;
import java.io.FileNotFoundException;

  ...

  FileReader reader;

  try {

    // statements that might throw an exception
    reader = new FileReader(filename);

    int aCharacter = reader.read();
  }
  catch (FileNotFoundException fnfe) {

    // handle the exception,
    //  do something about the missing file
    System.out.println("The file " + filename + " was not found.");
    System.out.println("Select another file.");
  }
  catch (IOException ioe) {

    // handle the exception,
    //  do something about failing to read a character.
    System.out.println("Could not read a character from the file.");
  }
  finally {
    if (reader != null) {
        try {
            reader.close();
        }
        catch (IOException ioe) {
            // Error trying to close the file
        }
    }
  }

  ...

Figure 02

In this example, the code segment will attempt to close the FileReader object. The code in the finally block will execute even if an exception is encountered. The above code segment also demonstrates multiple catch blocks and nested blocks. The code contained within the try block is capable of throwing both a FileNotFoundException and an IOException. Multiple catch statements can exist for a single try block. Additionally, the reader.close() statement is capable of throwing an exception. As such, a try-catch blocks are required within the finally.

Returns that don't.

Return statements should be avoided in try-catch-finally blocks when the finally block is present. The use of return statements in a finally block will override any return statement in a try or catch block. Consider the following two examples:

public class ReturnsThatDont {

  public static int makeException() {

    try {
      int i = Integer.parseInt("a");
    }

    catch (NumberFormatException nfe) {
      System.out.println("In catch.");
      return 1;
    }

    finally {
      System.out.println("In finally.");
    }

    return 2;
  }

  public static void main(String[] args) {

    System.out.println("Returned value: " + 
        makeException());
  }
}

Figure 03

public class ReturnsThatDont2 {

  public static int makeException() {

    try {
      int i = Integer.parseInt("a");
    }

    catch (NumberFormatException nfe) {
      System.out.println("In catch.");
      return 1;
    }

    finally {
      System.out.println("In finally.");
      return 2;
    }

  }

  public static void main(String[] args) {

    System.out.println("Returned value: " + 
        makeException());
  }
}

Figure 04

Both ReturnsThatDont and ReturnsThatDont2 function identically. However, ReturnsThatDont will return a value of 1 and ReturnsThatDont2 will return a value of 2. The return statement in the finally block will override the return statement in the catch block.

Checked and Unchecked Exceptions

Exceptions within Java can be either checked or unchecked. Checked exceptions are exceptions that must be handled by some method within the call stack. In the above example, the FileNotFoundException and IOException exceptions are checked exceptions. Therefore, those exceptions must be specifically handled at some point. Consider the following example:

import java.io.FileReader;
import java.io.IOException;
import java.io.FileNotFoundException;

public class ReadACharacter {

  public static int readIt(String filename) 
    throws IOException {

    FileReader reader = new FileReader(filename);
    int aCharacter = reader.read();
    return aCharacter;
  }

  public static void main(String[] args) {

    try {
      System.out.println("The character is: " +
        readIt("junk.txt"));
    }
    catch (IOException ioe) {
      System.out.println("Failed to read the character.");
    }
  }
}

Figure 05

In this example, the decision was made not to handle the exceptions in the readIt method. Therefore, the readIt method declares the IOException as throwable by defining the exception in the throws clause. That moves the responsibility for handling the exception to the caller of the readIt method. (Note: See the Exception Hierarchy section for description of why the FileNotFoundException is not listed in the throws clause.)

Since IOException and FileNotFoundException are checked exceptions, they must be handled at some point. The Java compiler enforces this rule. Failure to catch a checked exception will result in an error during compiling. Therefore, all checked exceptions must be either caught or declared in a throws clause. Since the entry point for all Java applications is defined as:

                  public static void main(String[] args)

the IOException must be caught. It cannot be thrown above main.

Exceptions where the Java compiler does not enforce exception handling are said to be unchecked. Unchecked exceptions are exceptions that are extended from either Error or RuntimeException. (See Exception Hierarchy for details on exception inheritance.) Unchecked exceptions usually represent an unexpected programming error and not an unexpected condition. Generally, these exceptions should not be caught. They should be handled by fixing the source code that is causing the exception.

Developers should also avoid creating exceptions by extending the RuntimeException or Error exceptions. It is an easy temptation for developers to extend RuntimeException to avoid the checked exception enforcement by the compiler. Additionally, Errors should not be extended because they are used to represent internal Java errors.

Exceptions as a flow control mechanism.

Exceptions provide a useful mechanism for handling errors and unexpected conditions. However, exceptions should only be used when the situation is truly "exceptional." Developers may be tempted to use exceptions for their flow control capabilities. For example, a method is designed to return an object from a custom collection container based on a lookup value. The object not being present in the container is considered a normal condition. A poor use of exceptions would be for the method to throw an exception if the object is not present in the container. It would be better to return a null to the calling method. Throwing the exception will incur additional overhead of the exception handling system and add no benefit. (See the Performance section.)

Another example of the good use of exceptions is to throw an exception when trying to open a non-existent file. The file not being present represents an "exceptional" condition. However, a method that tests for the existence of a file shouldn't throw an exception if the file is not present. Returning a boolean value would be preferred.

Exception Hierarchy

Thrown exceptions can propagate upwards as more generic exceptions.
Throwing methods throws specific exceptions.
Multiple catches are ordered from more specific to more general.
GIFT shall not create duplicate exceptions.
Java exception classes are organized in a class hierarchy. All exceptions and errors in Java extend from the java.lang.Throwable class. (See Figure 06) When an exception is thrown, Java will attempt to find a "matching" catch block for that exception. An exception is caught when the VM finds a catch block of the exact class type as the exception or any super class of the exception. Figure 07 shows the class hierarchy of java.io.InvalidClassException.

                 java.lang.Throwable
                   |
  java.lang.Error--+--java.lang.Exception
                        |
                        +--java.lang.RuntimeException

Figure 06

    java.lang.Throwable
      |
      +--java.lang.Exception
           |
           +--java.io.IOException
                |
                +--java.io.ObjectStreamException
                     |
                     +--java.io.InvalidClassException

Figure 07

Therefore, if a method throws java.io.InvalidClassException, the first catch block that the JavaVM encounters of type: Throwable, Exception, IOException, ObjectStreamException, or InvalidClassException will catch that exception. This ability to catch exceptions based on a super class allows catch blocks to catch a "set" of exceptions with one block of code. This is one of the more robust features of Java's exception handling. For example, a method that handles file IO can throw a number of exceptions: EOFException, FileNotFoundException, or InterruptedIOException. However, since all of those above exceptions are subclasses of IOException the method only has to declare IOException as being thrown. Additionally, if the object calling the file IO method is not concerned with what caused the failure, it only has to catch IOException. For example:

  ...

  public void doFileIO() 
    throws IOException {

    // perform file IO.
    //  can throw EOFException, FileNotFoundException,
    //  InterruptedIOException, and IOException
    ...

  }

  public void performAction() {

    ...

    try {
      doFileIO();
    }

    catch (FileNotFoundException fnfe) {
      // want to handle file not found differently
      // handle exception

      ...
    }
    catch (IOException ioe) {
      // another type of file error occurred
      // can't handle exception, return.
      return;
    }
  }

  ...

Figure 08

This exception hierarchy can be extended to the creation of user exceptions. For example, assume the following (example / fictitious) user exception hierarchy:

  java.lang.Throwable
    |
    +--java.lang.Exception
         |
         +--mil.arl.gift.common.EnumException
              |
              +-- mil.arl.gift.common.EnumNotFoundException

Figure 09

If the main launching method was not concerned with exactly why the simulation failed, it can catch an exception based on the mil.arl.gift.common.EnumException exception. This also allows additional exceptions to be added to the hierarchy in the future without requiring modification to the calling method. For example, mil.arl.gift.common.EnumNotFoundException could be added as a subclass of mil.arl.gift.common.EnumException without affecting exiting catch blocks that catch mil.arl.gift.common.EnumException. This provides the Java developer with the ability to extend the exception class hierarchy with additional exception hierarchies.

GIFT is free to develop class hierarchies as needed to organize exception handling. However, care should be taken not to over subclass exceptions. GIFT is encouraged to use existing Java exceptions and extend from existing exceptions when possible.

Ordering catch blocks.

When multiple catch blocks are associated with a try block, the catch blocks must be ordered from most specific (sub classes) to most generic (super classes). For example, consider the following code:

  try {

    ...

  }
  catch(mil.arl.gift.common.EnumException ee) {

    ...

  }
  catch(mil.arl.gift.common.EnumNotFoundException enfe) {

    ...

  }

Figure 10

The Java compiler will generate an error during compiling of the above code. The compiler will consider the EnumNotFoundException exception as being already caught by EnumException.

GIFT shall not create duplicate exceptions. Another common mistake made by developers is the duplication of exceptions already defined in the Java API. For example, if a method needs to report that one of the method parameters is invalid, it is not necessary to create an exception to report the invalid parameter argument. The Java API defines java.lang.IllegalArgumentException to indicate an illegal argument has been passed. The method need only create an IllegalArgumentException object and throw the exception.

Performance

Performance impact.
Java's exception handling mechanism doesn't come without costs. Overhead exists in the Java throw / catch mechanism. This overhead is incurred in both the process of throwing the exception and in catching the thrown exception. For each exception that is thrown, an exception object must be created. This creation incurs the initial cost in throwing the exception. Additional costs exist for each level in the call stack the exception is not caught.

Assume the following exception hierarchy: Sub2Exception extends Sub1Exception extends BaseException extends Exception. Take the following code segment:

  ...

  public void throwingMethod() 
    throws Sub2Exception {

    ...

    throw new Sub2Exception(); 

    ...
  }

  public void methodInTheMiddle() 
    throws BaseException {

    try {
      throwingMethod();
    }
    catch (NumberFormatException nfe) {
      ...
    }
  }

  public void methodThatStartedIt() {

    try {
      methodInTheMiddle();
    }
    catch (BaseException be) {
      ...
    }
  }    

  ...

Figure 11

When throwingMethod() throws the Sub2Exception Java must initially create the exception. Since the exception is not handled by throwingMethod(), the Java VM must return to the previous method on the calling stack. Now the VM must scan methodInTheMiddle() method to find a catch statement that is a match for the exception or its base classes. Since a match is not found, the VM returns to the next method on the call stack. Within methodThatStartedIt(), the exception is caught and handled. In the above example, the VM must create the exception object, abnormally exit throwingMethod(), scan all the catch blocks in methodInTheMiddle() for a match, abnormally exit methodInTheMiddle(), and finally scan methodThatStartedIt() for the match.

While this overhead can be significant, it is not meant to dissuade developers from using the exception handling capability of Java. Design consideration should be given to what constitutes a good need and use of an exception verses another error handling technique. Using exceptions as a flow control mechanism is a good example of this. (See the Exceptions as a flow control mechanism section.)

An example of performance impact using exceptions as a flow control is demonstrated by the following code and sample output:

public class ExceptionCreationCosts {

  final static int NUM_TESTS = 10000;

  long nullTime = 0;
  long exceptionTime = 0;

  public Object lookupReturnNull(String key) {
    // Simulation of some "lookup" routine.
    // Assume key is not found, therefore return
    //  a null.

    if(!key.equals("junk"))
      return null;

    return new Object();
  }

  public Object lookupThrowsException(String key) 
    throws KeyNotFoundException {

    // This method will throw an exception if
    //  the key is not found.

    if(!key.equals("junk"))
      throw new KeyNotFoundException();

    return new Object();
  }

  public void doTest() {

    // Perform a lookup test, first using 
    // lookupReturnNull then lookupThrowsException.
    // Compare time difference.

    int iLoop;
    Object value;

    long startTime = System.currentTimeMillis();
    for(iLoop=0; iLoop<NUM_TESTS; iLoop++) {
      value=lookupReturnNull("something");
      if(value == null) {
        // Perform error handling
      }
    }
    nullTime = System.currentTimeMillis() - startTime;

    startTime = System.currentTimeMillis();
    for(iLoop=0; iLoop<NUM_TESTS; iLoop++) {
      try {
        value=lookupThrowsException("something");
      }
      catch (KeyNotFoundException knfe) {
        // Perform error handling
      }
    }
    exceptionTime = System.currentTimeMillis() - startTime;
  }

  public static void main(String[] args) {

    ExceptionCreationCosts ecc = new ExceptionCreationCosts();
    ecc.doTest();

    System.out.println("Ran pseudo lookup tests that fails to find a match.");
    System.out.println("Test times the duration it takes to return a null");
    System.out.println("object to denote no match and to throw an exception");
    System.out.println("to denote no match.  Test runs the lookup " + NUM_TESTS + " times.");
    System.out.println("\nResults in milliseconds:");
    System.out.println("  Time to return null:     " + ecc.nullTime);
    System.out.println("  Time to throw exception: " + ecc.exceptionTime);
  }

  public class KeyNotFoundException extends Exception { }
}

[output]

Ran pseudo lookup tests that fails to find a match.
Test times the duration it takes to return a null
object to denote no match and to throw an exception
to denote no match. Test runs the lookup 10000 times.

Results in milliseconds:
Time to return null: 4
Time to throw exception: 24
Figure 12
The overhead with exceptions can be slightly reduced by following the following guidelines:

  1. Catch exceptions as early as possible.
  2. Order multiple catch blocks in order of the exception most likely thrown to least likely thrown. (Note: Do not apply this rule to catch blocks for inherited exceptions. See Ordering Catch Blocks for details.)
  3. Don't use exceptions for flow control.

Assertions and Their Use

Topics

  • Assertions
    • Written assumptions should translate into asserts.
    • Enabled during design.
    • Disabled during production.
  • Pre-condition / Post-condition asserts.
  • All non-trivial asserts should be documented.
  • Asserts should not contain code that changes program results.
  • When is it an assert?
    • Assertions should not be used to validate data sent to public APIs.
    • Should only check non-public (and package level) methods only.
    • Should be used to detect illegal conditions and not real errors.

Another error handling technique is the use of Assertions. Assertions are programmatic statements about conditions the developer assumes are true. Typically written assumptions and/or requirements can be translated directly into assertions. For example, a method might assume that an integer argument is always between the values of 0 and 10, inclusive. This represents an assumption about the conditions when this method is called. An assertion could be placed at the start of the method code that would enforce this assumption. Should a condition exist that violates that assumption, the assertion would fail and program execution would halt.

At first glance, abruptly ending the program for a failed condition would seem like poor programming practices. However, assertions are used to enforce coding assumptions about internal program states. For example, generally an assertion shouldn't be thrown for failure to open a file. Failure to open a file typically doesn't represent a programmatic error. However, should a method designed to return a random number between 0 and 1, an assertion could be used to verify that the random number is valid. If the asserted condition is not valid, the program will halt at the assertion position and debugging information can be output. This process is designed to discover and correct bugs within the program early in the development process. In the production environment, assertion handling is disabled.

Pre-condition / Post-condition

Assertions can be used at any point within a code block. However, assertions are typically used at the start of a method call (usually called a pre-condition) or at the end of a method before any return (post-condition). Pre-condition assertions are used to validate the arguments passed to the method or the state of the object prior to method execution. Post-conditions are used to validate the result of the method execution prior to returning or the end state of the object after execution.

Non-trivial assertions should be documented.

Non-trivial assertions, or assertions that are not self-documenting, must be documented. A clear understanding of why an assertion is present should be understood from the surrounding code and comments. An understanding of the base assumption must be present to help determine why any assertion has failed during development.

Conditionally executed code.

Assertions by their nature are conditionally executed. When in the development phase, assertions will execute testing the validity of internal program states and data. However as stated above, assertions do not execute in the production environment. Therefore, expressions that alter variables or states, must not be used in assertions. For example (note, the following examples are based on JDK 1.4 assert mechanism):

assert (result = process(x)) > 0);

Figure 15
result = process(x);
assert result > 0;
Figure 16
In the above example, the process(x) method would not be called in a production environment due to the fact that assertion handling is disabled. This can lead to unexpected results. Assertion statements should only test conditions or states.

When is it an assert?

As stated at the start of this section, asserts should be used to detect programmatic errors. As a result, assertions are commonly used to enforce conditions between the interfaces of private and/or package level methods. In this capacity, asserts help prevent propagation of internal errors back out to a public interface.

Generally, assertions should be avoided on public interfaces (APIs). Invalid arguments to a public interface is an error condition and not an internal illegal condition. Invalid arguments should result in a thrown exception. A general rule of thumb with assertions is that they should not mask a valid exception. Assertions deal with illegal internal conditions, not real errors.

Error Handling Policy

Exception Policy

  • When to use exceptions.
  • Performance impact
    • When applicable, conditional coding will be used instead of exceptions for performance reasons.
    • Catch blocks shall be ordered to catch most frequently thrown exceptions first unless exception class hierarchy is violated.
    • Assertions should be used to verify the correctness of input to a method, the internal state of a method, and output of a method, respectively.
  • Exception guidelines
    • GIFT should avoid the use of return statements in finally and catch blocks.
    • GIFT should not subclass RuntimeException.
    • GIFT should not use "catch-all" constructs.
    • GIFT should not use exceptions to handle simple non-extraordinary results.
    • GIFT shall not use exceptions as flow control constructs.
    • GIFT shall not create duplicate exceptions.
  • Native code
    • Exception guidelines apply to native code (JNI).
    • Native code shall throw exceptions when appropriate.
  • Logging policy
    • The GIFT module logger shall be used for logging.
    • System.err and System.out are not to be used for the reporting of exceptions and errors.
    • All exceptions that result in a visible result to the user (either program halt, or user action required) must be logged.
    • Exceptions that can be recovered from and handled without user involvement are logged according to the logging level set (info level?).
    • Operator errors should be logged according to the logging level set.

When to use exceptions.

Exceptions should be used to report "exceptional" conditions. This exceptional condition can be the result of (this is not an exhaustive list):

  • Unexpected method parameters / values (for public interfaces).
  • Internal computational errors.
  • Attempting to use unavailable resources.

Exceptions should not be generated for simple status returns. For example, testing resource availability, key lookup in a collection finding no match, or the success or failure of an algorithm. While these are examples where the result may be an error condition, it is not an "exceptional" condition. It is a valid and expected result.

Performance impact

GIFT will design all error handling functions to be as efficient as possible. This means that during code design, effort is made to design a code implementation that returns simple invalid results in the form of conditional errors. Checking and validating conditional results represents the fastest possible execution time. However, code should not be developed to return exceptional conditions as conditional values just to save performance time.

GIFT will order multiple catch blocks to improve efficiency. First catch blocks will be ordered by inheritance level of the exception (more specific to more general). Second, the catch blocks will be ordered such that the most likely exceptions will be listed first.

Exception guidelines

In general, developers will follow these guidelines in the development of exception handling. GIFT should avoid the use of return statements in finally and catch blocks. This can lead to the unexpected results. Remember, finally blocks are always executed. If a return is encountered in a catch block and in a finally block, the first return statement will be ignored. GIFT should avoid the subclassing of RuntimeExceptions. Since runtime exceptions are not checked, subclassing runtime exceptions can lead to poor error handling by failing to include the appropriate catch blocks.

GIFT should not use "catch-all" constructs. Theses constructs are catch blocks that catch the Exception class or the Throwable class. These "catch-all" blocks can hide unexpected internal errors because they trap all exceptions including RuntimeExceptions and/or Errors, and they perform no useful work associated with the exception. GIFT should catch exceptions that are subclassed from the Exception class. Exceptions shall not be used as flow control structures. Finally, GIFT is encouraged to look for a JDK exception prior to subclassing and creating a new exception. This avoids unnecessary explosion of exception classes.

However, daemon or worker threads may include "catch-all" constructs. This is suggested so that these threads will not end abruptly. This allows the system to handle previously unhandled exceptions. This is done due to the fact that worker threads should be performing small / frequent tasks. This represents a situation where a single task may fail, but continued execution of future tasks is required. For worker or daemon threads, the following guidelines represent best practices:

GIFT methods should throw specific meaningful exceptions.
Exceptions should be caught as early as possible, from specific to generic.
Preferably before the main loop of the worker thread.
Within thread's main loop, put a "catch-all" to catch any unhandled or unchecked exceptions that have occurred.
The "catch-all" should use "catch (Exception e)" and not "catch (Throwable t)," if possible. Throwables include "Errors" which are typically unrecoverable Java VM errors.
All uses of the "catch-all" construct must be fully documented in code. Worker thread uses of the catch-all constructs shall log all applicable data when an exceptional condition is caught and processing is to continue. Logging is not required when the exception is expected as a normal occurrence of the handler and is handled correctly. The exception handler must be robust enough to report on the exception generated to aid in debugging errors.
With the release of Java 1.4, new functionality was added to Java exceptions. This functionality is known as the chained exception facility. The Throwable class in 1.4 now supports a "cause." The cause is itself a Throwable. When a thrown exception is caught and re-thrown as a new exception, the original exception is the cause of the new exception. A cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another. GIFT code should always set the cause of an exception if a new exception is generated as a result of the original exception. Examples setting the cause:

{
 //...
}
catch (IOException ioe) {
    throw new MyException("An error message.", ioe);

    (or)

    MyException me = new MyException("An error message.");
    me.initCause(ioe);
    throw me;
}

Figure 17
To help support the chained exception facility, all GIFT extended exception classes should implement constructors to accept the exception cause if any constructors are defined.
public class MyException extends Exception {

  public MyException() {
    super();
  }

  public MyException(String message) {
    super(message);
  }

  public MyException(String message, Throwable cause) {
    super(message, cause);
  }

  public MyException(Throwable cause) {
    super(cause);
  }
}

Figure 18

Native code

Java supports the ability to create Java objects from within native code. This includes Exception objects. Developed native libraries shall follow the exception policy. Exceptions should be created and thrown from the JNI code when an "exceptional" condition occurs and needs to be handled in Java code.

Logging policy

GIFT shall use the system logger to log all "exceptional" conditions. Error messages shall not be written to System.err or System.out. The system logger has been developed to provide a central resource / location for the logging of system errors and messages. This will allow for better control over error logging in a distributed environment.

All error conditions that result in a visible result to the user must be logged.

Displaying Error Messages to the User

When an error occurs that prevents the user form performing some action, The exceptions that were thrown can be used to present a very detailed message to the user. Most of these exceptions will have a ‘cause’ attibute, a ‘message’ attribute, and stacktrace associated with them. In GIFT there is an abstract dialog box object called a DetailsDialogBox that can be called when a line of exceptions reaches its end. The DetailsDialogBox includes a general message that is usually hard-coded by the developer to explain the problem in the most novice-level user friendly fashion possible. Below the general message, the user can open a collapsible panel that contains a detailed message, usually directly retrieved from the thrown exception. Within that panel there is another that can be opened into the entire stack trace, including the stack trace of each exception in the line. This dialog should be used whenever possible to provide not only the user with a general idea of the problem along with a possible solution to fix it themselves, but also allows the developers to get a more refined idea of the problem without needing to use traditional debugging.

Assertion Policy

  • When to use assertions.
  • Assertion guidelines
    • Assertions should contain a message or comment.
    • Assertions must not contain code that changes results, values, or object state.
    • Assertions must only test a condition.
    • All non-trivial assertions should be documented.
    • The proper use of assertions within GIFT is encouraged.
    • Unit tests run with and without asserts, compare for consistent results.

When to use assertions.

As stated above, assertions are statements about code assumptions. Assertions are used to validate those assumptions. For example:

  public class AssertExample {

    private static void privateFcn(int nonNegative) {

        assert (nonNegative >= 0) : "privateFcn was passed a negative value";

        // function code

    }

    public static void publicFcn(int nonNegative) {

        if (nonNegative < 0) {
            throw new IllegalArgumentException();

        // function code

        privateFcn(nonNegative);
    }
  } 

Figure 19
In the above example, the code assumes that privateFcn will always receive a value for nonNegative that is positive. The assert statement enforces that assumption during development. Should privateFcn receive a negative value, the assert line will catch that programming error. The publicFcn also assumes that it will only receive a positive value for the nonNegative variable. However, this method is a part of a public interface of the class. It is outside the control of this class to ensure that another user of this class will not call publicFcn with a negative value. This would represent an exceptional condition and an exception is thrown.

The use of assertions within the GIFT project is highly recommended.

Assertion guidelines

Assertions must not contain code that changes program results, values, or an object state. Assertions must only test a true / false condition. Non-trivial assertions should be documented. Documentation should be in the form of inline comments immediately surrounding the assert statement. Should extended documentation be required to explain the assertion, the inline comments should provide a reference (URL) to additional documentation in the SDF (Software Development Folders).

Assertions should be commented. To comment an assertion, include a colon and the assertion comment string following the assertion conditional.
For example: assert k < 100 : "Loop exceeded 100 iterations.";

Code inspection verification of policy adherence

  1. Look for assignment, increment, or decrement operators in assert statements.
  2. Look for method calls in assert statements that are not simple condition check / lookup methods (typically methods that start with is or can check conditions and do not alter program state).
  3. Methods within an assertion call should not throw an exception. Look for surrounding try-catch blocks.
  4. Program result should not change based on whether assertions are enabled or not. Run unit tests with and without assertions enabled.

Policy Summary

  • Exception Policy
    • When to use exceptions.
  • Performance impact
    • When applicable, conditional coding will be used instead of exceptions for performance reasons.
    • Catch blocks shall be ordered to catch most frequently thrown exceptions first unless exception class hierarchy is violated.
    • Assertions should be used to verify the correctness of input to a method, the internal state of a method, and output of a method, respectively.
  • Exception guidelines
    • GIFT should avoid the use of return statements in finally and catch blocks.
    • GIFT should not subclass RuntimeException.
    • GIFT should not use "catch-all" constructs unless they are used in worker / daemon threads.
    • GIFT should not use exceptions to handle simple non-extraordinary results.
    • GIFT shall not use exceptions as flow control constructs.
    • GIFT shall not create duplicate exceptions.
  • Native code
    • Exception guidelines apply to native code (JNI).
    • Native code shall throw exceptions when appropriate.
  • Logging policy
    • The GIFT module logger system logging facility shall be used for the reporting of exceptions.
    • System.err and System.out are not to be used for the reporting of exceptions and errors.
    • All exceptions that result in a visible result to the user (either program halt, or user action required) must be logged.
    • Exceptions that can be recovered from and handled without user involvement are logged according logging level set.
  • Assertion Policy
  • When to use assertions.
  • Assertion guidelines
    • Assertions must not contain code that changes results, values, or object state.
    • Assertions must only test a condition.
    • All non-trivial assertions should be documented.
    • The proper use of assertions within the GIFT is encouraged.
    • Unit tests run with and without asserts, compare for consistent results.