In Java, the finally
keyword is used to execute code (used with exceptions - try..catch
statements) no matter if an exception is thrown or not (source).
For example:
try {
// this code might throw an exception
riskyCall();
// this code will only run if no exception was thrown above
mainProgram();
}
finally {
// this code will always run
cleanUp();
}
Is there an equivalent feature in ABAP? If not, what's an idiomatic way to implement the same functionality?
I know ABAP has a CLEANUP
keyword, but this seems to only execute if an exception was thrown.
I experimented and found the following as a possible solution. Unfortunately, I can't think of any solution without code duplication.
METHOD risky_method.
TRY.
WRITE 'code before...'.
IF lv_error_condition = abap_true.
RAISE EXCEPTION TYPE cx_foo.
ENDIF.
WRITE 'Main program...'.
WRITE 'Cleanup...'.
CLEANUP.
WRITE 'Cleanup...'.
ENDTRY.
ENDMETHOD.
METHOD outer_scope.
TRY.
risky_method( ).
CATCH cx_foo INTO DATA(lx_foo).
WRITE 'Caught the error!'.
ENDTRY.
ENDMETHOD.
For the case where lv_error_condition
equals abap_false
, the output of executing method outer_scope
is:
code before... Main program... Cleanup...
For the case where lv_error_condition
equals abap_true
, the output is:
code before... Cleanup... Caught the error!
This solution has the advantage that the cleanup always runs. It has the disadvantage that some code duplication is required, as the cleanup needs to be written twice. If the cleanup is packaged into a method then it's not a terrible amount of code duplication. :-/
ABAP has no exact equivalent for the Java finally
block.
There is the TRY
... CLEANUP
construct which looks similar at first glance but actually works very differently:
METHOD buggy_method.
TRY.
WRITE 'code before the error...'.
RAISE EXCEPTION TYPE cx_foo.
WRITE 'This line will not get executed.'.
CLEANUP.
WRITE 'Cleanup...'.
ENDTRY.
ENDMETHOD.
" ...on an outer scope...
TRY.
buggy_method( ).
CATCH cx_foo INTO DATA(lx_foo).
WRITE 'Caught the error!'.
ENDTRY.
Output: code before the error... Cleanup... Caught the error!
However the cleanup block does not always* get executed. It only gets executed when there is an exception and that exception is not handled by a CATCH
in the same TRY
-block. The idea is to use it for cleanups which are supposed to happen when an exception is handled by a TRY
-block on an outer level. So it's not useful for code which you want to run regardless of whether you have an error or not. It's only useful for code you want to run in case of an error which the try-block does not handle itself.
Another feature which covers some of the use-cases of the Java finally
block are resumable exceptions:
" In the class definiton
METHODS buggy_method RAISING RESUMABLE(cx_foo).
" In the class implementation:
METHOD buggy_method.
WRITE 'code before the error...'.
RAISE RESUMABLE EXCEPTION TYPE cx_foo.
WRITE 'code after the error....'.
ENDMETHOD.
" Somewhere else:
TRY.
buggy_method( ).
CATCH BEFORE UNWIND cx_foo INTO DATA(lx_foo).
WRITE 'Caught the error!'.
RESUME.
ENDTRY.
Output: code before the error... Caught the error! code after the error....
The RESUME
keyword at the end of the CATCH
block causes the execution to continue right after the exception was RAISE RESUMABLE
d. So when you want to make sure that the end of your method gets executed even in case of an error, then this is perhaps the syntax you are looking for.
* yes, I know there are exotic edge-cases where a finally-block in Java does not get executed. Those are not relevant here.
A colleague gave me the solution. The trick to avoiding code duplication, while still allowing the exception to propagate upward, is to not use the CLEANUP
keyword at all, and instead:
CX_ROOT
(to catch all possible exceptions, not just the ones you expect!)finally
-style cleanup codeIS BOUND
to see if an exception was raised, and if yes, re-raise itHere's an example:
METHOD risky_method.
TRY.
WRITE 'code before...'.
IF lv_error_condition = abap_true.
RAISE EXCEPTION TYPE cx_foo.
ENDIF.
WRITE 'Main program...'.
CATCH cx_root INTO DATA(lx_root).
ENDTRY.
WRITE 'Cleanup...'.
IF lx_root IS BOUND.
RAISE lx_root.
ENDIF.
ENDMETHOD.
METHOD outer_scope.
TRY.
risky_method( ).
CATCH cx_foo INTO DATA(lx_foo).
WRITE 'Caught the error!'.
ENDTRY.
ENDMETHOD.
For the case where lv_error_condition
equals abap_false
, the output is:
code before... Main program... Cleanup...
For the case where lv_error_condition
equals abap_true
, the output is:
code before... Cleanup... Caught the error!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With