Best Practices for Exception Handling

download Best Practices for Exception Handling

of 9

Transcript of Best Practices for Exception Handling

  • 8/14/2019 Best Practices for Exception Handling

    1/9

    Best Practices for Exception Handling

    One of the problems with exception handling is knowing when and how touse it. In this article, I will cover some of the best practices for exception

    handling. I will also summarize the recent debate about the use of checkedexceptions.

    We as programmers want to write quality code that solves problems.Unfortunately, exceptions come as side effects of our code. No one likes sideeffects, so we soon find our own ways to get around them. I have seen somesmart programmers deal with exceptions the following way:

    public void consumeAndForgetAllExceptions(){try {

    ...some code that throws exceptions} catch (Exception ex){

    ex.printStacktrace();}

    }

    What is wrong with the code above?

    Once an exception is thrown, normal program execution is suspended andcontrol is transferred to the catch block. The catch block catches theexception and just suppresses it. Execution of the program continues afterthe catch block, as if nothing had happened.

    How about the following?

    public void someMethod() throws Exception{}

    This method is a blank one; it does not have any code in it. How can a blankmethod throw exceptions? Java does not stop you from doing this. Recently, Icame across similar code where the method was declared to throwexceptions, but there was no code that actually generated that exception.When I asked the programmer, he replied "I know, it is corrupting the API,but I am used to doing it and it works."

    It took the C++ community several years to decide on how to useexceptions. This debate has just started in the Java community. I have seenseveral Java programmers struggle with the use of exceptions. If not usedcorrectly, exceptions can slow down your program, as it takes memory andCPU power to create, throw, and catch exceptions. If overused, they makethe code difficult to read and frustrating for the programmers using the API.We all know frustrations lead to hacks and code smells. The client code may

  • 8/14/2019 Best Practices for Exception Handling

    2/9

    circumvent the issue by just ignoring exceptions or throwing them, as in theprevious two examples.

    The Nature of Exceptions

    Broadly speaking, there are three different situations that cause exceptionsto be thrown:

    Exceptions due to programming errors: In this category,exceptions are generated due to programming errors (e.g.,NullPointerException and IllegalArgumentException). The client code usuallycannot do anything about programming errors.

    Exceptions due to client code errors: Client code attemptssomething not allowed by the API, and thereby violates its contract.The client can take some alternative course of action, if there is usefulinformation provided in the exception. For example: an exception is

    thrown while parsing an XML document that is not well-formed. Theexception contains useful information about the location in the XMLdocument that causes the problem. The client can use this informationto take recovery steps.

    Exceptions due to resource failures: Exceptions that get generatedwhen resources fail. For example: the system runs out of memory or anetwork connection fails. The client's response to resource failures iscontext-driven. The client can retry the operation after some time orjust log the resource failure and bring the application to a halt.

    Types of Exceptions in Java

    Java defines two kinds of exceptions:

    Checked exceptions: Exceptions that inherit from the Exception classare checked exceptions. Client code has to handle the checkedexceptions thrown by the API, either in a catch clause or by forwardingit outward with the throws clause.

    Unchecked exceptions: RuntimeException also extends from Exception.However, all of the exceptions that inherit from RuntimeException getspecial treatment. There is no requirement for the client code to dealwith them, and hence they are called unchecked exceptions.

    By way of example, Figure 1 shows the hierarchy for NullPointerException.

  • 8/14/2019 Best Practices for Exception Handling

    3/9

    Figure 1. Sample exception hierarchy

    In this diagram, NullPointerException extends from RuntimeException and hence is

    an unchecked exception.

    I have seen heavy use of checked exceptions and minimal use of uncheckedexceptions. Recently, there has been a hot debate in the Java communityregarding checked exceptions and their true value. The debate stems fromfact that Java seems to be the first mainstream OO language with checkedexceptions. C++ and C# do not have checked exceptions at all; allexceptions in these languages are unchecked.

    A checked exception thrown by a lower layer is a forced contract on theinvoking layer to catch or throw it. The checked exception contract between

    the API and its client soon changes into an unwanted burden if the clientcode is unable to deal with the exception effectively. Programmers of theclient code may start taking shortcuts by suppressing the exception in anempty catch block or just throwing it and, in effect, placing the burden on theclient's invoker.

    Checked exceptions are also accused of breaking encapsulation. Considerthe following:

    public List getAllAccounts() throwsFileNotFoundException, SQLException{...

    }

    The method getAllAccounts() throws two checked exceptions. The client of thismethod has to explicitly deal with the implementation-specific exceptions,even if it has no idea what file or database call has failed withingetAllAccounts(), or has no business providing filesystem or database logic. Thus, the exception handling forces an inappropriately tight couplingbetween the method and its callers.

  • 8/14/2019 Best Practices for Exception Handling

    4/9

    Best Practices for Designing the API

    Having said all of this, let us now talk about how to design an API that throwsexceptions properly.

    1. When deciding on checked exceptions vs. unchecked exceptions,ask yourself, "What action can the client code take when theexception occurs?"

    If the client can take some alternate action to recover from the exception,make it a checked exception. If the client cannot do anything useful, thenmake the exception unchecked. By useful, I mean taking steps to recoverfrom the exception and not just logging the exception. To summarize:

    Client's reaction when exception happens Exception type

    Client code cannot do anythingMake it an unchecked

    exception

    Client code will take some useful recovery action based on

    information in exception

    Make it a checked

    exception

    Moreover, prefer unchecked exceptions for all programming errors:unchecked exceptions have the benefit of not forcing the client API toexplicitly deal with them. They propagate to where you want to catch them,

    or they go all the way out and get reported. The Java API has manyunchecked exceptions, such as NullPointerException, IllegalArgumentException, andIllegalStateException. I prefer working with standard exceptions provided in Javarather than creating my own. They make my code easy to understand andavoid increasing the memory footprint of code.

    2. Preserve encapsulation.

    Never let implementation-specific checked exceptions escalate to the higherlayers. For example, do not propagate SQLException from data access code tothe business objects layer. Business objects layer do not need to know about

    SQLException. You have two options:

    Convert SQLException into another checked exception, if the client codeis expected to recuperate from the exception.

    Convert SQLException into an unchecked exception, if the client codecannot do anything about it.

  • 8/14/2019 Best Practices for Exception Handling

    5/9

    Most of the time, client code cannot do anything about SQLExceptions. Do nothesitate to convert them into unchecked exceptions. Consider the followingpiece of code:

    public void dataAccessCode(){try{

    ..some code that throws SQLException}catch(SQLException ex){

    ex.printStacktrace();}

    }

    This catch block just suppresses the exception and does nothing. Thejustification is that there is nothing my client could do about an SQLException.How about dealing with it in the following manner?

    public void dataAccessCode(){try{

    ..some code that throws SQLException}catch(SQLException ex){

    throw new RuntimeException(ex);}

    }

    This converts SQLException to RuntimeException. IfSQLException occurs, the catchclause throws a new RuntimeException. The execution thread is suspended andthe exception gets reported. However, I am not corrupting my businessobject layer with unnecessary exception handling, especially since it cannotdo anything about an SQLException. If my catch needs the root exception cause,

    I can make use of thegetCause()

    method available in all exception classes asof JDK1.4.

    If you are confident that the business layer can take some recovery actionwhen SQLException occurs, you can convert it into a more meaningful checkedexception. But I have found that just throwing RuntimeException suffices mostof the time.

    3. Try not to create new custom exceptions if they do not haveuseful information for client code.

    What is wrong with following code?

    public class DuplicateUsernameExceptionextends Exception {}

    It is not giving any useful information to the client code, other than anindicative exception name. Do not forget that Java Exception classes are like

  • 8/14/2019 Best Practices for Exception Handling

    6/9

    other classes, wherein you can add methods that you think the client codewill invoke to get more information.

    We could add useful methods to DuplicateUsernameException , such as:

    public class DuplicateUsernameExceptionextends Exception {public DuplicateUsernameException

    (String username){....}public String requestedUsername(){...}public String[] availableNames(){...}

    }

    The new version provides two useful methods: requestedUsername(), whichreturns the requested name, and availableNames(), which returns an array ofavailable usernames similar to the one requested. The client could use thesemethods to inform that the requested username is not available and that

    other usernames are available. But if you are not going to add extrainformation, then just throw a standard exception:

    throw new Exception("Username already taken");

    Even better, if you think the client code is not going to take any action otherthan logging if the username is already taken, throw a unchecked exception:

    throw new RuntimeException("Username already taken");

    Alternatively, you can even provide a method that checks if the username isalready taken.

    It is worth repeating that checked exceptions are to be used in situationswhere the client API can take some productive action based on theinformation in the exception. Prefer unchecked exceptions for allprogrammatic errors. They make your code more readable.

    4. Document exceptions.

    You can use Javadoc's @throws tag to document both checked and uncheckedexceptions that your API throws. However, I prefer to write unit tests todocument exceptions. Tests allow me to see the exceptions in action andhence serve as documentation that can be executed. Whatever you do, havesome way by which the client code can learn of the exceptions that your APIthrows. Here is a sample unit test that tests for IndexOutOfBoundsException:

    public void testIndexOutOfBoundsException() {ArrayList blankList = new ArrayList();try {

    blankList.get(10);

  • 8/14/2019 Best Practices for Exception Handling

    7/9

    fail("Should raise an IndexOutOfBoundsException");} catch (IndexOutOfBoundsException success) {}

    }

    The code above should throw an IndexOutOfBoundsException whenblankList.get(10) is invoked. If it does not, the fail("Should raise anIndexOutOfBoundsException") statement explicitly fails the test. By writing unittests for exceptions, you not only document how the exceptions work, butalso make your code robust by testing for exceptional scenarios.

    Best Practices for Using Exceptions

    The next set of best practices show how the client code should deal with anAPI that throws checked exceptions.

    1. Always clean up after yourself

    If you are using resources like database connections or network connections,make sure you clean them up. If the API you are invoking uses onlyunchecked exceptions, you should still clean up resources after use, with try -finally blocks.

    public void dataAccessCode(){Connection conn = null;try{

    conn = getConnection();..some code that throws SQLException

    }catch(SQLException ex){ex.printStacktrace();

    } finally{DBUtil.closeConnection(conn);

    }}

    class DBUtil{public static void closeConnection

    (Connection conn){try{

    conn.close();} catch(SQLException ex){

    logger.error("Cannot close connection");throw new RuntimeException(ex);

    }}

    }

    DBUtil is a utility class that closes the Connection. The important point is theuse offinally block, which executes whether or not an exception is caught. Inthis example, the finally closes the connection and throws a RuntimeException ifthere is problem with closing the connection.

  • 8/14/2019 Best Practices for Exception Handling

    8/9

    2. Never use exceptions for flow control

    Generating stack traces is expensive and the value of a stack trace is indebugging. In a flow-control situation, the stack trace would be ignored,since the client just wants to know how to proceed.

    In the code below, a custom exception, MaximumCountReachedException, is usedto control the flow.

    public void useExceptionsForFlowControl() {try {

    while (true) {increaseCount();

    }} catch (MaximumCountReachedException ex) {}//Continue execution

    }

    public void increaseCount()throws MaximumCountReachedException {if (count >= 5000)

    throw new MaximumCountReachedException();}

    The useExceptionsForFlowControl() uses an infinite loop to increase the count untilthe exception is thrown. This not only makes the code difficult to read, butalso makes it slower. Use exception handling only in exceptional situations.

    3. Do not suppress or ignore exceptions

    When a method from an API throws a checked exception, it is trying to tellyou that you should take some counter action. If the checked exception doesnot make sense to you, do not hesitate to convert it into an uncheckedexception and throw it again, but do not ignore it by catching it with {} andthen continue as if nothing had happened.

    4. Do not catch top-level exceptions

    Unchecked exceptions inherit from the RuntimeException class, which in turninherits from Exception. By catching the Exception class, you are also catchingRuntimeException as in the following code:

    try{..}catch(Exception ex){}

    The code above ignores unchecked exceptions, as well.

  • 8/14/2019 Best Practices for Exception Handling

    9/9

    5. Log exceptions just once

    Logging the same exception stack trace more than once can confuse theprogrammer examining the stack trace about the original source ofexception. So just log it once.