Reputation: 36339
I wonder if the following two swnippest are semantically identical, and, if not, what are the differences (we assume that we want to compute a result of type R, and want to guard against exception X that may be thrown in the course of doing so):
public R tcf(....) {
try {
R some = ...;
... compute the result ....
return some;
}
catch (X exception) {
... exception handling ....
}
finally {
... clean up ....
}
}
and the following:
public R tc(....) {
try {
R some = ...;
... compute the result ....
return some;
}
catch (X exception) {
... exception handling ....
}
}
public R tf(....) {
try {
return tc(....); // wrap the try-catch in tc()
}
finally {
... clean up ....
}
}
As far as I can see, it boils down to if a try-catch block wrapped in a try block with finally is the same, semantically, as a try-catch-finally block, assuming that the code in the finally and catch phrases stays the same and the outer try block just promotes the result of the inner one.
Practical relevance: Given a code base that does not make use of try-catch-finally, when it should, and given that for some reason, one cannot toch that code, one could more or less mechanically generate a layer of wrapper methods that add the finally.
I am fully aware of the fact, that for many reasons, one should use the try ... catch ... finally whenever possible. Specifically, I am not suggesting in any way that one should refactor the first example so that it looks like the second.
Rather, I want to make sure that example 2 can safely be refactored to example 1.
Upvotes: 12
Views: 369
Reputation: 500367
Functionally, they are equivalent.
I think it's good style to perform cleanup in the same method that has allocated the resources in question. In some cases this is the only practical way to go about things (for example, if the cleanup involves variables that are local to tcf
/tc
).
Also, if tc
doesn't clean up after itself, the cleanup becomes part of the function's contract, as a caller's obligation. This makes the design more error-prone, since the cleanup is easy to forget or get wrong. Additionally, if the steps involved in the cleanup in any way change, every single caller would need to be tracked down and updated.
Bottom line: if the cleanup can be performed in tcf
, it should be.
Upvotes: 15
Reputation: 6298
I think your description is pretty accurate in that the tf finally
will execute in both cases and that the exception handling will be dealt with in either tcf
or tf catch
block.
That is, in example (1): if an exception occurs in your try
, flow will execute your catch
block followed by your finally block
. In example (2), in tc
, if an exception occurs in your try
block, flow will continue in your catch
block and then (presuming the exception isn't re-thrown) return to the calling line in tf
. On completion of your tf try
block, flow will continue in your tf finally
block.
I guess the main implication to be aware of, and that you may not be able to correct in the way you suggest, is that:
tc
resources from tf
to be able to clean them up. Upvotes: 2
Reputation: 12797
Both are identical. but finally is used when you are having a risk of breaking the function in between or want to release some resources held by the function. in that case catch is skipped but finally will definitely execute.
try can be used either with catch or finally or both. finally generally can't show the exception occurred but catch can trace the exception.
Upvotes: 1
Reputation: 5663
Not performing the finally in the same method as the try/catch leaves you open to the method doing the try/catch at some point being called without the finally being executed, which is likely a bug.
I'd therefore not advise you to write it like that, though in the scenario you describe, having to add functionality through a finally when the method containing the try/catch can't be modified, it's the only way.
Upvotes: 2
Reputation: 20760
They are the same, however. The try/finally should always be right after the recourse (that finally releases) is claimed. (note that this happens just before the try)
For example:
getLock();
try {
doSomething()
}
finally {
releaseLock();
}
Because it is really easy to forget to clean up, it should always happen at the same location as the taking of the resource. Reason for this is that you should never burden your callers with something they always have to do (and you can do yourself)
Upvotes: 3