简体   繁体   中英

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

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

  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!

By my knowledge there is no real equivalent at all. For example, if ABAP knew a finally, I would expect this

TRY.
  RETURN.
FINALLY.
  WRITE / 'Finally'.
ENDTRY.

to write out the word "Finally". And ABAP programs have so many situations where this would definitely be useful. One of the most common being enqueues which need the corresponding dequeue, but it could also be an open/close dataset pair.

This code only outputs 'try'.

TRY. WRITE /'try'. RETURN. WRITE /'after return'. CLEANUP. write /'cleanup'. ENDTRY. write /'after try'.

So you are forced to leave your routines by exception if you want to run that cleanup code. Or you write redundant code:

  OPEN DATASET iv_file IN TEXT MODE ENCODING UTF-8.
  TRY.
    " ... write something to file
    
    IF do_more IS INITIAL.
       CLOSE DATASET iv_file. 
      RETURN.
    ENDIF.
    
    " ... write more to file ...
    
  CLEANUP.
    CLOSE DATASET iv_file.
  ENDTRY.
  CLOSE DATASET iv_file. 

The solution is more than often to write redundant code in the form like that. But let's say this enqueues and dequeues things while writing data to the file. Then you may have to call the dequeue and close dataset at some points any maybe only the close dataset at another point. Not nice to put this into a separate method or form routine if it changes.

I'm originally from the pascal side (from school days) and learned finally there. It's very often used since pascal based languages don't use garbage collector and you always have to make sure to free the object yourself when you son't need it anymore. So the free there is meant to always run (as long as the program still runs of course). You rather won't bother about things like closing files there because <ou free it as soon as you're done and the destructor will be called on Free and will do it for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM