I’ve been reading the new version of Michael Nygard‘s book, Release It, 2nd Ed. I read the first version of the book shortly after I first became an engineer and the book impacted my work greatly. Thus far, the quality of the second edition is on par with the first edition. One of the first things that Nygard discusses is “The Exception that grounded an airline” – the name of chapter 2. In this chapter, Nygard discusses a software bug that caused immense trouble for the airline – an issue with the reservation system that caused several flights to be grounded. Nygard shows the buggy code, what was done to fix the issue, and what patterns could have been employed to prevent the issue in the first place. Funnily enough, though, none of the large scale patterns of “Release It” needed to have been employed to prevent the airline from being grounded. Believe it or not, all that needed to be done to prevent the issue was for the Java developers who wrote the system to have properly used Java’s final keyword.

At the crux of the issue, was the following code:

Connection conn = null;
Statement stmt = null;
try {
    conn = connectionPool.getConnection();
    stmt = conn.createStatement();

    // Do the lookup logic
    // return the list of results
} finally {
    if (stmt != null) {
        stmt.close();
    }
    if (conn != null) {
        conn.close();
    }
}

Nygard goes on to discuss how this code can be fixed by paying attention to the exceptions thrown by close() methods. But, as stated above, the code could also have been trivially fixed by using the final keyword judiciously.

All too often, I see code like this:


void queryDb(final String queryParam) {
    Connection conn = null;
    Statement stmt = null;

    // do stuff with `conn`, `stmt`, and `queryParam`, and conditionally set
    // `conn` and `stmt` to something non-null.
}

The only variables that should have been final in the above method were conn and stmt. The variable queryParam was pointless to make final. By making conn and stmt final, we make it easy to reason about the state of the variable. This is because we know that it’s never null when it’s final (unless its initial state is null); we know that its value is the initial value, nothing else is possible. By making queryParam final, all that is really happening is noise pollution. We know that queryParam is always going to have that initial value passed into the method, its a trivial parameter that never changes. So, in my opinion, using the final keyword on such variables just adds noise. Really, we wanted conn and stmt to be final because, ideally, those variables should only get set once. As such, consider the following code:

public void getMyDataFinally() throws SQLException {
    final Connection conn = this.connectionFactory.newConnection();
    try {
        getMyDataFinallyWithConnection(conn);
    } finally {
        conn.close();
    }
}

private void getMyDataFinallyWithConnection(Connection conn) throws SQLException {
    final Statement statement = conn.createStatement();
    try {
        // Do the lookup logic
        // return the list of results
    } finally {
        statement.close();
    }
}

The above code fixes the issue present in Nygard’s version of the code, without any null checks, and most importantly, without paying attention to what exceptions are thrown by the close() method of objects, which was the cause of the issue that crashed the airline. All that needed to happen was to make conn and stmt final, and to refactor the code in such a way that allowed the compiler to guarantee that conn and stmt could only be set once.

Note that the code is now more verbose – we have two methods, not just one. However, there are two benefits to using this approach:

  1. The code is easier to follow. There are no null-checks. We don’t need to reason about the state and whether or not conn and stmt have been set. We don’t need to worry about whether a variable has been set properly before we operate on it.
  2. The new methods are more cohesive. That is to say, they operate at different levels of abstraction. The getMyDataFinally() method operates at the level of abstraction pertaining to the connection, whereas the getMyDataFinallyWithConnection() method operates at the abstraction level dealing with the statement. Granted, it’s a pretty trivial difference. Nonetheless, this example demonstrates how making appropriate use of the final keyword forces one to factor their code in such a way that methods are more cohesive than they might otherwise be.

All in all, I really enjoy Nygard’s work. It’s an updated version of an industry standard with tons of wisdom. But, from my point of view, it’s interesting to think about how the final keyword could have saved millions of dollars for this airline. If you are reading this and don’t use the final keyword in your Java code well, it could probably save you from coding some bugs as well.

Leave a reply

required