我想要一些关于我碰到的技术的建议。通过查看代码片段可以很容易地理解它,但我在以下段落中对其进行了更多记录。
使用“代码三明治”习语来处理资源管理是司空见惯的。习惯了 C++ 的 RAII 习语后,我切换到 Java 并发现我的异常安全资源管理导致了深度嵌套的代码,其中我很难掌握常规控制流。
显然( Java 数据访问:这种 Java 数据访问代码的风格很好,还是 try finally 太多了?Java io 丑陋的 try-finally 块等等)我并不孤单。
我尝试了不同的解决方案来解决这个问题:
显式维护程序状态:
resource1aquired
,fileopened
...,并有条件地清理:if (resource1acquired) resource1.cleanup()
... 但是我避免在显式变量中复制程序状态 - 运行时知道状态,我不想关心它。将每个嵌套块包装在函数中 - 导致更难遵循控制流,并导致非常尴尬的函数名称:
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();
}
使用辅助构造,您可能会得到一个更线性的构造,其中补偿代码就在发起者旁边。
class Compensation {
public void compensate(){};
}
compensations = new Stack<Compensation>();
嵌套代码变为线性:
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();
}
我很高兴:无论有多少异常路径,控制流都保持线性,并且清理代码在视觉上位于原始代码旁边。最重要的是,它不需要人为限制的closeQuietly
方法,这使得它更加灵活(即不仅是Closeable
对象,还有Disconnectable
,Rollbackable
以及其他任何东西)。
但...
我发现其他地方没有提到这种技术。那么问题来了:
这种技术有效吗?你在其中看到了什么错误?
非常感谢。