[Java] I want to make a structured design even for exception handling

7 minute read

Motivation

I’ve read various exception handling articles, but since they didn’t fall into my mind, I broadened my perspective and considered them. It is summarized from the viewpoint of “how to make exceptions effective in the system for quality improvement”.

1. About exceptions

1.1. Features

Identify characteristics of exceptions.

  • In principle, occurs when the conditions required for processing are not met
  • Exception class can control catch target by specialization or generalization using inheritance
  • Can be ignored for unchecked exceptions
  • Can add data
  • Explain the exception class name to some extent (ex: NotFoundException)
  • Can be structured (exceptions can be nested)

1.2. Exception limits

The essence of an exception is that in the relationship between the caller and the exception generator, It only has the function of notifying the caller of a message. It is a cooperation destination such as a remote program, so it is necessary to devise to trace all the causes.

If you use it as it is

  • The exception originator can only notify the event that occurred.
  • The exception catcher only knows that the method failed
  • The generation method can be identified by stack trace

2. Implement cause investigation by exception handling

Consider how to realize it so that developers do not have to read the code as much as possible. The viewpoint of the cause investigation is as follows.

  • Bug or defect (generally missing information)
  • Isolation (who is bad)
  • Identification of occurrence location
  • Information for reproduction (generally missing information)

Organize based on the following sample implementation. Some methods have been omitted to simplify the explanation.

// Bank transfer class
class BankTransfer implements BankProcess {
  // remittance information
  Account from; // sender account
  Account to; // Remittance destination account
  long amount; // remittance amount

  // Transfer processing
  @override
  void process() throws BankException {
    try {
      from.withdrawal(amount); // Withdrawal process
      to.deposit(amount); // deposit process
      log("transfer completed."); // log output
    } catch (TransferException e) {
      // transfer failed
      log("transfer failed."); // log output
      rollback();
      log("rollback completed.");
      throw e;
    }
  }
}
// Account processing
abstract class Account {
  int no; // account number
  // Deposit processing
  void deposit(long amount) {
    try {
      // Assuming an exception will occur
      deposit(amount);
    } catch (ConnectionTimeoutException e) {// 2.2.3 Isolation (who is bad)
      // connection failed and account management system is unavailable
      var error = new TransferException(e); //2.2 Structure with logical exception
      error.setNo(this.no); //2.1 Utilization of additional information
      throw error;
    }
  }
  // Withdrawal processing
  void withdrawal(long amount) {
    try {
      // Assuming an exception will occur
      withdrawal();
    } catch (ConnectionTimeoutException e) {// 2.2.3 Isolation (who is bad)
      // connection failed and account management system is unavailable
      var error = new TransferException(e); //2.2 Structure with logical exception
      error.setNo(this.no); //2.1 Utilization of additional information
      throw error;
    }
  }

  // Banks are different for each account, so abstraction
  abstract void deposit(int no, long amount);
  abstract void withdrawal(int no, long amount);
}

2.1. Utilize additional information when an exception is thrown

When an exception is thrown, information in the method that implements the catch statement can be collected. Set the relevant data from the beginning of the method to the catch occurrence point as an exception. Collect relevant data only.

  • Class field
  • Method parameter value
  • Evaluation value of control statement such as IF/For (what data and what condition)
  • Stack trace (added by default)

Solved the following points of view.

  • Identification of occurrence location
  • Information for reproduction

2.2. Structure with logical exception

Provide the necessary information as a result of the cause investigation from the following viewpoints.

  • Bug or obstacle
  • Isolation (who is bad)

Hereafter, we will implement an implementation using a logical exception, using the “bank remittance process” that is easy to imagine as an example. Focus on the exception handling implementation.

2.2.1 Exception class inheritance tree

The exception class name informs you that the process has failed, but the class name acts as the subject. The following exception class shows the inheritance tree.

RuntimeException unchecked exception
    └── BankException Banking business failed
        └── NameResolveException Processing for addressing fails (not relevant below)
        └── TransferException Money transfer processing failed

2.2.2. Is it a bug or a defect?

In order to judge it as a bug, it can be solved by making exception catch rules. To put it the other way around, it’s important to be able to catch the expected exception pinpoint. Therefore, in case of abstract exception (SQLException etc.), it is necessary to narrow down the exception instance to be processed by the additional information of exception.

  • Bug: uncaught exception
  • Fault: Logical exception (indicates expected event)

2.2.3 Isolation (who is bad)

In the investigation of the cause, the necessary things are separated, but the characters who are generally suspicious are as follows.

  • User (maybe the operation is bad)
  • Operator (Is the configuration/operation bad?)
  • Other connected component systems (DB, other web services, etc.)

This problem can be identified by the physical exception (cause exception) that was caught. An example is shown below.

  • Exception occurred at the point of contact with user/operator operation -(Solved by validation)
  • Exception caused by misconfiguration -FileNotFoundException If the configuration file does not exist -ParseException The configuration file is corrupted
  • Exception occurred at the contact of other connected component system -ConnectionExecption For example, when the DB connection fails. However, judge by looking at additional information and stack trace -HttpRequestException Connection to other web services, incomplete request (bug in this case), etc. -SQLException It is caused by the state of DB. Error code etc. are added.

The method of notifying higher-level methods is arbitrary. It is also good to set it as additional information in the logical exception, It is good to create a new OperatorException class (a class that indicates an operation error) and increase the number of abstract exception classes. It is okay to write it in the message. (The connection failed, so it failed.)

2.2.4 Survey results

As a result, the stack trace is added and the survey result is completed. An example of the sample will be described.

  • Obstacles and accreditations (because of logical exceptions)
  • Due to connection problem with external service (cause exception set as logical exception occurs at contact with other service)
  • Source account number, destination account number, amount (additional information)
  • Actual method (stack trace)

3. What to do with exceptions

The exception that occurs must always be caught somewhere and processed. The motivation for this design concept is to “use exceptions in the system for quality improvement”, so I thought that the following rules were necessary.

3.1. Principle rule to catch all exceptions

There are few reasons to use “Exception” that catches all exceptions.

  • Not stop the system
  • When the provided library etc. enforce it (when it is not good)

The cases that can be implemented are shown below.

  • Methods that may throw the exception and halt the system -main function -Your own backend processing endpoint -Event loop
  • Method that cannot be stopped because there is subsequent processing -Job-based run loop -HTTP request control

There are things such as web application servers that handle exceptions properly, so In that case, you don’t have to catch everything. It is necessary to verify the behavior when WAS catches an exception.

3.2. Report bugs to developers

If you catch all the exceptions shown in 3.1., you will also catch the physical exceptions you did not expect. Therefore, it is necessary to record the bug report to the developer as a whole in some form. Possible methods are as follows.

  • Record in log file
  • Display on web screen etc.
  • Send to log collection tool or infrastructure
  • Output to standard error

Logical exceptions (expected exceptions) and physical exceptions (unexpected exceptions) can be easily distinguished. Generalization and specialization of exception classes come into play here. The code example is shown below.

try {
  var job = new BankTransfer(from, to);
  job.process();
} catch (BankException e) {
  // handle the expected exception

} catch (RuntimeException e) {
  // Handling unexpected exceptions
}

4. Summary

If you do not create an exception class, you cannot determine whether it is a bug or a failure only by the name of the exception class that occurred. In addition, it is not possible to obtain information to reproduce it. Therefore, by implementing the following, it is possible to discover information effective for identifying the cause at an early stage.

  • Create your own exception class and design structured
  • Wrap the cause exception in its own exception class and set the data to reproduce the exception
  • Process only the expected exception class to determine the bug/fault
  • Set information for separation from other components
  • Exceptions are caught by the principles mentioned above