Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In ABAP, what is the equivalent of Java's finally keyword?

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. :-/

like image 652
Jonathan Benn Avatar asked Sep 06 '25 02:09

Jonathan Benn


2 Answers

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 RESUMABLEd. 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.

like image 71
Philipp Avatar answered Sep 07 '25 19:09

Philipp


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:

  1. After the risky code, catch exception CX_ROOT (to catch all possible exceptions, not just the ones you expect!)
  2. Run the finally-style cleanup code
  3. Use IS BOUND to see if an exception was raised, and if yes, re-raise it

Here'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!
like image 36
Jonathan Benn Avatar answered Sep 07 '25 19:09

Jonathan Benn