Exceptions - Sanjeev435/MyJavaPractiseSets GitHub Wiki

1. Use exceptions only for exceptional conditions

  • The exception-based idiom is far slower than the standard one
  • Not only does the exception-based loop obfuscate the purpose of the code and reduce its performance, but it’s not guaranteed to work.
  • Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.
  • A well-designed API must not force its clients to use exceptions for ordinary control flow.

2. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

  • Java provides three kinds of throwables: checked exceptions, runtime exceptions, and errors.
  • Use checked exceptions for conditions from which the caller can reasonably be expected to recover.
  • By throwing a checked exception, we force the caller to handle the exception in a catch clause or to propagate it outward. Each checked exception that a method is declared to throw is therefore a potent indication to the API user that the associated condition is a possible outcome of invoking the method.
  • There are two kinds of unchecked throwables: runtime exceptions and errors.
    They are identical in their behavior: both are throwables that needn’t, and generally shouldn’t, be caught.
  • Use runtime exceptions to indicate programming errors.
  • If we believe a condition is likely to allow for recovery, use a checked exception; if not, use a runtime exception. If it isn’t clear whether recovery is possible, you’re probably better off using an unchecked exception.
  • There is a strong convention that errors are reserved for use by the JVM to indicate resource deficiencies, invariant failures, or other conditions that make it impossible to continue execution. Given the almost universal acceptance of this convention, it’s best not to implement any new Error subclasses.
  • All of the unchecked throwables we implement should subclass RuntimeException (directly or indirectly).
  • Not only shouldn’t we define Error subclasses, but with the exception of AssertionError, we shouldn’t throw them either.
  • Throw checked exceptions for recoverable conditions and unchecked exceptions for programming errors. When in doubt, throw unchecked exceptions. Don’t define any throwables that are neither checked exceptions nor runtime exceptions. Provide methods on your checked exceptions to aid in recovery.

3. Avoid unnecessary use of checked exceptions

  • Overuse of checked exceptions in APIs can make them far less pleasant to use.
  • If a method throws checked exceptions, the code that invokes it must handle them in one or more catch blocks, or declare that it throws them and let them propagate outward. Either way, it places a burden on the user of the API. The burden increased in Java 8, as methods throwing checked exceptions can’t be used directly in streams
  • The easiest way to eliminate a checked exception is to return an optional of the desired result type
    Instead of throwing a checked exception, the method simply returns an empty optional.
    The disadvantage of this technique is that the method can’t return any additional information detailing its inability to perform the desired computation. Exceptions, by contrast, have descriptive types, and can export methods to provide additional information.
  • Checked exceptions can increase the reliability of programs; when overused, they make APIs painful to use.
    If callers won’t be able to recover from failures, throw unchecked exceptions.
    If recovery may be possible and you want to force callers to handle exceptional conditions, first consider returning an optional. Only if this would provide insufficient information in the case of failure should you throw a checked exception.

4. Favor the use of standard exceptions

  • Reusing standard exceptions has several benefits.
    Chief among them is that it makes your API easier to learn and use because it matches the established conventions that programmers are already familiar with.
    A close second is that programs using your API are easier to read because they aren’t cluttered with unfamiliar exceptions.
    Last (and least), fewer exception classes means a smaller memory footprint and less time spent loading classes.
  • Do not reuse Exception, RuntimeException, Throwable, or Error directly. Treat these classes as if they were abstract
Exception Occasion for Use
IllegalArgumentException Non-null parameter value is inappropriate
IllegalStateException Object state is inappropriate for method invocation
NullPointerException Parameter value is null where prohibited
IndexOutOfBoundsException Index parameter value is out of range
ConcurrentModificationException Concurrent modification of an object has been detected where it is prohibited
UnsupportedOperationException Object does not support method
  • Also, feel free to subclass a standard exception if we want to add more detail, but remember that exceptions are serializable. That alone is reason not to write your own exception class without good reason.
  • Consider the case of an object representing a deck of cards, and suppose there were a method to deal a hand from the deck that took as an argument the size of the hand.
    If the caller passed a value larger than the number of cards remaining in the deck, it could be construed as an IllegalArgumentException (the handSize parameter value is too high) or an IllegalStateException (the deck contains too few cards). Under these circumstances, the rule is to throw IllegalStateException if no argument values would have worked, otherwise throw IllegalArgumentException.

5. Throw exceptions appropriate to the abstraction

  • Higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction. This idiom is known as exception translation:
// Exception Translation
try {
       ... // Use lower-level abstraction to do our bidding
    } catch (LowerLevelException e) {
       throw new HigherLevelException(...);
   }
  • A special form of exception translation called exception chaining is called for in cases where the lower-level exception might be helpful to someone debugging the problem that caused the higher-level exception. The lower-level exception (the cause) is passed to the higher-level exception, which provides an accessor method (Throwable’s getCause method) to retrieve the lower-level exception:
// Exception Chaining
try {
      ... // Use lower-level abstraction to do our bidding
    } catch (LowerLevelException cause) {
       throw new HigherLevelException(cause);
   }
  • The higher-level exception’s constructor passes the cause to a chaining-aware superclass constructor, so it is ultimately passed to one of Throwable’s chainingaware constructors, such as Throwable(Throwable):
// Exception with chaining-aware constructor
class HigherLevelException extends Exception {
      HigherLevelException(Throwable cause) {
          super(cause);
       }
  }
  • While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused.
  • If it is impossible to prevent exceptions from lower layers, the next best thing is to have the higher layer silently work around these exceptions, insulating the caller of the higher-level method from lower-level problems.
    Under these circumstances, it may be appropriate to log the exception using some appropriate logging facility such as java.util.logging. This allows programmers to investigate the problem, while insulating client code and the users from it.
  • If it isn’t feasible to prevent or to handle exceptions from lower layers, use exception translation, unless the lower-level method happens to guarantee that all of its exceptions are appropriate to the higher level.
    Chaining provides the best of both worlds:
    • it allows you to throw an appropriate higher-level exception, while capturing the underlying cause for failure analysis

6. Document all exceptions thrown by each method

  • Always declare checked exceptions individually, and document precisely the conditions under which each one is thrown using the Javadoc @throws tag.
  • Don’t declare that a public method throws Exception or, worse, throws Throwable. In addition to denying any guidance to the method’s user concerning the exceptions it is capable of throwing, such a declaration greatly hinders the use of the method because it effectively obscures any other exception that may be thrown in the same context.
  • One exception to this advice is the main method, which can safely be declared to throw Exception because it is called only by VM.
  • It is particularly important that methods in interfaces document the unchecked exceptions they may throw.
  • Use the Javadoc @throws tag to document each exception that a method can throw, but do not use the throws keyword on unchecked exceptions
  • The documentation generated by the Javadoc @throws tag without a corresponding throws clause in the method declaration provides a strong visual cue to the programmer that an exception is unchecked.
  • If an exception is thrown by many methods in a class for the same reason, we can document the exception in the class’s documentation comment rather than documenting it individually for each method.
    If an exception is thrown by many methods in a class for the same reason, we can document the exception in the class’s documentation comment rather than documenting it individually for each method.
  • Document every exception that can be thrown by each method that you write.
    This is true for unchecked as well as checked exceptions, and for abstract as well as concrete methods. This documentation should take the form of @throws tags in doc comments.
    Declare each checked exception individually in a method’s throws clause, but do not declare unchecked exceptions.
    If we fail to document the exceptions that our methods can throw, it will be difficult or impossible for others to make effective use of our classes and interfaces.

7. Include failure-capture information in detail messages

  • If the failure is not easily reproducible, it may be difficult or impossible to get any more information. Therefore, it is critically important that the exception’s toString method return as much information as possible concerning the cause of the failure.
  • To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception. For example, the detail message of an IndexOutOfBoundsException should contain the lower bound, the upper bound, and the index value that failed to lie between the bounds.
    Because stack traces may be seen by many people in the process of diagnosing and fixing software issues, do not include passwords, encryption keys, and the like in detail messages.
  • The detail message of an exception should not be confused with a user-level error message, which must be intelligible to end users. Unlike a user-level error message, the detail message is primarily for the benefit of programmers or site reliability engineers, when analyzing a failure.
    Therefore, information content is far more important than readability.
    User-level error messages are often localized, whereas exception detail messages rarely are.

8. Strive for failure atomicity

  • Generally speaking, a failed method invocation should leave the object in the state that it was in prior to the invocation. A method with this property is said to be failure-atomic.
  • There are several ways to achieve failure-atomicity.
    • The simplest is to design immutable objects. If an object is immutable, failure atomicity is free. For methods that operate on mutable objects, the most common way to achieve failure atomicity is to check parameters for validity before performing the operation
    • A closely related approach to achieving failure atomicity is to order the computation so that any part that may fail takes place before any part that modifies the object. This approach is a natural extension of the previous one when arguments cannot be checked without performing a part of the computation.
      For example, consider the case of TreeMap, whose elements are sorted according to some ordering. In order to add an element to a TreeMap, the element must be of a type that can be compared using the TreeMap’s ordering. Attempting to add an incorrectly typed element will naturally fail with a ClassCastException as a result of searching for the element in the tree, before the tree has been modified in any way.
    • A third approach to achieving failure atomicity is to perform the operation on a temporary copy of the object and to replace the contents of the object with the temporary copy once the operation is complete. This approach occurs naturally when the computation can be performed more quickly once the data has been stored in a temporary data structure.
      For example, some sorting functions copy their input list into an array prior to sorting to reduce the cost of accessing elements in the inner loop of the sort. This is done for performance, but as an added benefit, it ensures that the input list will be untouched if the sort fails.
    • A last and far less common approach to achieving failure atomicity is to write recovery code that intercepts a failure that occurs in the midst of an operation, and causes the object to roll back its state to the point before the operation began. This approach is used mainly for durable (disk-based) data structures.
  • While failure atomicity is generally desirable, it is not always achievable.
    For example, if two threads attempt to modify the same object concurrently without proper synchronization, the object may be left in an inconsistent state. It would therefore be wrong to assume that an object was still usable after catching a ConcurrentModificationException. Errors are unrecoverable, so we need not even attempt to preserve failure atomicity when throwing AssertionError.
  • Even where failure atomicity is possible, it is not always desirable. For some operations, it would significantly increase the cost or complexity.
  • As a rule, any generated exception that is part of a method’s specification should leave the object in the same state it was in prior to the method invocation. Where this rule is violated, the API documentation should clearly indicate what state the object will be left in.

9. Don’t ignore exceptions

  • When the designers of an API declare a method to throw an exception, they are trying to tell you something. Don’t ignore it! It is easy to ignore exceptions by surrounding a method invocation with a try statement whose catch block is empty.
  • An empty catch block defeats the purpose of exceptions, which is to force you to handle exceptional conditions.
  • If you choose to ignore an exception, the catch block should contain a comment explaining why it is appropriate to do so, and the variable should be named ignored:
    Future<Integer> f = exec.submit(planarMap::chromaticNumber);
    int numColors = 4; // Default; guaranteed sufficient for any map

    try {
       numColors = f.get(1L, TimeUnit.SECONDS);
    } catch (TimeoutException | ExecutionException ignored) {
      // Use default: minimal coloring is desirable, not required
     }
  • Whether an exception represents a predictable exceptional condition or a programming error, ignoring it with an empty catch block will result in a program that continues silently in the face of error.
    The program might then fail at an arbitrary time in the future, at a point in the code that bears no apparent relation to the source of the problem.
    Properly handling an exception can avert failure entirely.
    Merely letting an exception propagate outward can at least cause the program to fail swiftly, preserving information to aid in debugging the failure.
⚠️ **GitHub.com Fallback** ⚠️