在ABAP中,与Java的finally关键字相当的是什么?

4
在Java中,关键字finally用于执行代码(与异常-try..catch语句一起使用),无论是否抛出异常都会执行(来源)。
例如:
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();
}

ABAP是否有相应功能?如果没有,有什么惯用的方法实现相同的功能呢?

我知道ABAP有一个CLEANUP关键字,但似乎只会在抛出异常时执行。

我尝试过以下代码,可能是一种可行的解决方案。不幸的是,我无法想到任何不需要复制代码的解决方案。

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.

lv_error_condition等于abap_false时,执行outer_scope方法的输出为:
code before... Main program... Cleanup...

如果lv_error_condition等于abap_true,输出结果为:
code before... Cleanup... Caught the error!

该解决方案的优点是清理总是运行。它的缺点是需要一些代码重复,因为清理需要写两次。如果将清理打包到一个方法中,那么代码重复就不是非常糟糕了。 :-/


3
对于 finally 的一个重要注意点是:它只有在代码仍在运行时才会触发。如果 try 块中的代码导致虚拟机崩溃,你的 finally 就永远不会生效。最著名的故事是一家银行在面试申请者时,“finally 可能无法生效”,银行坚称它一定会生效,然后申请者问道,“好的,如果我绊倒电源线会发生什么?” 银行沉默了一会儿,然后匆忙召集了它的工程团队。 - Mike 'Pomax' Kamermans
3
谢谢你的夸奖!:) 假设这对于这个特定问题并不重要。 - Jonathan Benn
3个回答

3

ABAP没有Java的finally块的确切等价物。

TRY ... CLEANUP结构,乍一看相似,但实际上工作方式非常不同:

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.

输出结果: 在错误之前的代码... 清理... 捕获到错误!

然而,清理块并不总是被执行。它仅在发生异常且该异常被同一TRY块中的CATCH处理时才会执行。其想法是在外层级别的TRY块处理异常时使用它进行清理。因此,它对于您希望运行无论是否有错误的代码并不实用。它只适用于您希望在try块本身未处理错误的情况下运行的代码。


另一个覆盖Java finally块某些用例的特性是可继续的异常:

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

输出:在错误发生之前的代码...捕获了错误!在错误发生之后的代码...

CATCH块末尾的RESUME关键字会导致在异常被RAISE RESUMABLE后继续执行。因此,当你想要确保即使发生错误也执行您的方法的结尾时,这可能是您正在寻找的句法。

* 是的,在Java中有一些奇特的边缘情况,finally块不会得到执行。但这些情况在此并不相关。


感谢您的出色回答!您实际上比官方文档更好地解释了这些概念。在我的情况下,“可恢复”案例非常接近解决方案。不幸的是,在异常情况下,我只想运行“错误后代码”的子集。我无法想到任何没有代码重复的解决方案。 - Jonathan Benn
1
@JonathanBenn 如果您能提供一个ABAP代码重复的示例,说明您希望消除哪些部分,那么也许我们可以给出一些建议。 - Philipp
我已经更新了我的问题,提供了一个潜在的解决方案,但我并不太喜欢。 - Jonathan Benn

2
一位同事给了我解决方案。避免代码重复的诀窍,同时仍然允许异常向上传播,是根本不使用 CLEANUP 关键字,而是:
  1. 在有风险的代码之后,捕获异常 CX_ROOT (捕获所有可能的异常,而不仅仅是您期望的异常!)
  2. 运行像 finally 那样的清理代码
  3. 使用 IS BOUND 查看是否引发异常,如果是,则重新引发它
以下是一个示例:
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.

如果lv_error_condition等于abap_false,则输出结果为:

code before... Main program... Cleanup...

lv_error_condition等于abap_true时,输出结果为:

code before... Cleanup... Caught the error!

1
“Finally” 可以处理任何异常类型,因此我认为捕获 cx_root 更为合适。 - Jordão

0
据我所知,实际上没有任何真正的等价物。例如,如果ABAP知道finally,我会期望这个。
TRY.
  RETURN.
FINALLY.
  WRITE / 'Finally'.
ENDTRY.

写出单词“Finally”。ABAP程序有很多情况下都会用到这个关键字。其中最常见的是需要对应解锁的锁定操作,但也可能是打开/关闭数据集。

这段代码只输出“try”。

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

因此,如果您想运行清理代码,您必须通过异常离开您的例程。或者您可以编写冗余代码:

  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. 

解决方案往往是以这种形式编写冗余代码。但假设在写入数据到文件时进行了入队和出队操作。那么你可能需要在某些点调用出队并关闭数据集,而在另一个点可能只需要关闭数据集。如果这样的操作发生变化,将其放入单独的方法或表单例程中并不好。

我最初来自帕斯卡语言的一边(从学校时代开始),并最终在那里学到了这个。由于基于帕斯卡的语言不使用垃圾回收器,当你不再需要对象时,你必须确保自己释放它。因此,Free的作用是始终运行(当然,只要程序仍在运行)。你不太需要担心关闭文件之类的事情,因为你会在完成后立即释放它,并且析构函数将在Free时被调用并为你执行关闭操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接