关于嵌套Java try/finally代码三明治的建议

xto*_*ofl 21 java try-finally

我想对我遇到的技术提出一些建议.通过查看代码片段可以很容易地理解它,但我在以下段落中对其进行了更多的记录.


使用"Code Sandwich"习惯用法是处理资源管理的常用方法.用于C++的RAII习语,我切换到Java并发现我的异常安全的资源管理导致了深层嵌套的代码,我很难掌握常规控制流.

显然(java数据访问:这是java数据访问代码的好风格,还是最终尝试太多了?,Java io丑陋的try-finally阻塞等等)我并不孤单.

我尝试了不同的解决方案来应对这个问题:

  1. 明确地维护程序状态:resource1aquired,fileopened...,并有条件地清理:if (resource1acquired) resource1.cleanup()...但我避免在显式变量中复制程序状态 - 运行时知道状态,我不想关心它.

  2. 包装在每个功能块嵌套-结果更难遵循控制流程,并为真正尴尬的函数名称:runResource1Acquired( r1 ),runFileOpened( r1, file ),...

最后,我在一些关于代码三明治的研究论文的支持下(概念上)得到了一个成语:


而不是这个:

// (pseudocode)
try {
   connection = DBusConnection.SessionBus(); // may throw, needs cleanup
   try {
        exported = false;
        connection.export("/MyObject", myObject ); // may throw, needs cleanup
        exported = true;
            //... more try{}finally{} nested blocks
    } finally {
        if( exported ) connection.unExport( "/MyObject" );
    }   
} finally {
   if (connection != null ) connection.disconnect();
}
Run Code Online (Sandbox Code Playgroud)

使用辅助构造,您可以得到一个更线性的构造,其中补偿代码就在发起者旁边.

class Compensation { 
    public void compensate(){};
}
compensations = new Stack<Compensation>();
Run Code Online (Sandbox Code Playgroud)

嵌套代码变为线性的:

try {
    connection = DBusConnection.SessionBus(); // may throw, needs cleanup
    compensations.push( new Compensation(){ public void compensate() {
        connection.disconnect();
    });

    connection.export("/MyObject", myObject ); // may throw, needs cleanup
    compensations.push( new Compensation(){ public void compensate() {
        connection.unExport( "/MyObject" );
    });   

    // unfolded try{}finally{} code

} finally {
    while( !compensations.empty() )
        compensations.pop().compensate();
}
Run Code Online (Sandbox Code Playgroud)

我很高兴:无论有多少异常路径,控制流都保持线性,清理代码在视觉上紧挨着原始代码.最重要的是,它并不需要人为地控制closeQuietly方法,这使得它更灵活(即不仅Closeable对象,而且Disconnectable,Rollbackable和让别人).

但...

我发现其他地方没有提到这种技术.所以这就是问题:


这种技术有效吗?你看到了什么错误?

非常感谢.

Thi*_*ilo 1

很好。

没有什么大的抱怨,我脑子里只有一些小事情:

  • 一点性能负担
  • 你需要final为补偿做一些事情才能看到它们。也许这会阻止一些用例
  • 你应该在补偿期间捕获异常并继续运行补偿,无论如何
  • (有点牵强)由于编程错误,您可能会在运行时意外清空补偿队列。OTOH 编程错误无论如何都可能发生。通过补偿队列,您将获得“条件最终块”。
  • 不要把这件事推得太远。在单个方法中,这似乎没问题(但无论如何,您可能不需要太多的 try/finally 块),但不要在调用堆栈中上下传递补偿队列。
  • 它延迟了对“最后最外层”的补偿,这对于需要尽早清理的东西来说可能是一个问题
  • 仅当您仅需要为finally块使用try块时,它才有意义。如果你有一个 catch 块,你可以在那里添加finally。