And finally, for something completely different
Posted on 23 Oct 2014, 1:12 p.m.
Make sure the last thing you do, is not return my stuff
The following is a perfectly reasonable method, whereby we execute a stored procedure in the database, that may well result in some changes and a result being returned.
public ResultSet myTest(Connection conn) throws Exception { ResultSet result = null; Statement stmt = conn.createStatement(); String sql = "CALL my_stored_procedure()"; try { result = stmt.executeQuery(sql); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.close(); } return result; }
If no exception is thrown, we commit the changes, the finally block runs which closes the connection, and then we return the result set. All fine.
If an exception is thrown at any point, we catch it in order to rollback the changes in the database, before re-throwing that exception for handling higher up. But before control is passed back to the caller, the finally block runs to close the connection, since we always want to do that anyhow. All fine.
But what happens when (as has happened in my team previously, with code almost identical to this), a well-wishing, lesser-experienced developer sees an opportunity to make the code look slightly nicer? What happens if the above code gets changed to this ...
public ResultSet myTest(Connection conn) throws Exception { ResultSet result = null; Statement stmt = conn.createStatement(); String sql = "CALL my_stored_procedure()"; try { result = stmt.executeQuery(sql); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.close(); // Moved the return up here :) return result; } }
The return has been moved inside the finally block. What effect will this have, and what does that calling method get back, an Exception or a ResultSet? It's actually an interesting question to ask developers :) I'll deal with each of the 4 possible (or rather, typical) responses in turn, and my answers to them.
Does it even compile?
For sure, it compiles. Next?
I dunno, does it do both?
What would that look like? Can you return a value and throw an exception? If so, what happens in the caller? Does it (where appropriate) assign the result, and then immediately propagate/handle the exception? If so, what good is that?
I guess the exception trumps the return ... so does it ignore the return?
OK, so if that were the case, does it also ignore the conn.close() too, or does it still do that?
Well the finally block always has to run, right? So I guess it returns whatever's in result
So it ignores the re-throw of the exception?If you don't know, the correct answer is the last one. The stupidest answer being the second one. First of all there's no way that a method (at least, in any language that I'm aware of) could both return a value, and raise/throw an exception. It doesn't even make any sense, let alone sound like something useful. If both things happened, what could you possibly do with the return value in the caller that would be meaningful? If you somehow have an answer to that question, then my followup would be, "And how readable/maintainable would that code be?".
The lesson here, is that that the finally block always* executes, and any flow control (return, break, continue, throw/raise) statements trump whatever else you might have tried to do in the try/catch block.
Actually, the real lesson here, is to be extremely careful, or to avoid completely using such statements in a finally block.
And now for some even more fun examples!
function test() { for (var i=0; i<10; i++) { try { return i; } finally { continue; } } return i; } // What get returned? console.log(test());
To save you a few seconds of trying it yourself, it returns 10. Again, the finally block always executes, and it's continue statement overrides the return statement.
What about the same thing in Python?
def test(): for i in xrange(10): try: return i finally: continue SyntaxError: 'continue' not supported inside 'finally' clause
Ahhhhhh, Python ... gotta love Python :)
* 'Always' ... means 'basically always', except for killing processes/threads or pulling the power cable out of the computer ;)