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:
- 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
andstmt
have been set. We don’t need to worry about whether a variable has been set properly before we operate on it. - 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 thegetMyDataFinallyWithConnection()
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 thefinal
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.