模式避免“如果清理失败”重复

我有一些看起来像这样的代码:

int myfunc() { blah a; blah2 b; blah3 c; blah4 d; blah5 e; int iRes = DoSomething1(a, b, c); if (iRes > 0) { clean1(a, b, c); clean2(d, e); log_error(); return 1; } iRes = DoSomething2(a, c, e); if (iRes > 0) { clean1(a, b, c); clean2(d, e); log_error(); return 1; } ... iRes = DoSomething10(c, d, e); if (iRes > 0) { clean1(a, b, c); clean2(d, e); log_error(); return 1; } clean1(a, b, c); clean2(d, e); return 0; } 

如何在C或C ++中避免重复if (iRes > 0) { clean1(a, b, c); clean2(d, e); log_error(); return 1; } if (iRes > 0) { clean1(a, b, c); clean2(d, e); log_error(); return 1; } 每个函数调用后?


笔记:

  • 在实际代码中,这些函数DoSomethingx()cleanx()是API函数,不是我自己编写的
  • 我想避免在myfunct()之外定义第二个函数clean()来处理清理+错误
  • 我想过使用预处理器宏,但我怀疑这对于这种情况是一个好习惯

例:

这段代码是这种情况的一个例子:每10行代码实际上= 2行仅用于实际执行 + 8行错误测试和清理……我们能做得更好吗?

正如贾斯汀所说,答案将根据语言的不同而有所不同。

如果你在C,这是goto在语言中有一个据点的最后一个地方。 例如,如果您有:

 int myfunc() { blah a; blah2 b; blah3 c; blah4 d; blah5 e; int iRes = DoSomething1(a, b, c); if (iRes > 0) { goto error_cleanup; } iRes = DoSomething2(a, c, e); if (iRes > 0) { goto error_cleanup; } /*...*/ iRes = DoSomething10(c, d, e); if (iRes > 0) { goto error_cleanup; } /* SUCCESS */ clean1(a, b, c); clean2(d, e); return 0; /* ERROR EXIT POINT */ error_cleanup: clean1(a, b, c); clean2(d, e); log_error(); return 1; } 

但是,在C ++中,我们需要处理这个清理代码,即使抛出exception,这也会使事情变得更加棘手。 但是,在c ++中我们也有RAII,这意味着析构函数是可行的方法。

这是我喜欢的一个技巧,避免了goto:

 bool success = false; do { r = do_something(); if (r) break; r = do_something_else(); if (r) break; r = do_something_again(); if (r) break; success = true; } while(0); if (! success) { clean_up(); } 

对于给定的代码,您可以使用以下任何一种变体:

 int myfunc() { blah a; blah2 b; blah3 c; blah4 d; blah5 e; int iRes; if ((iRes = DoSomething1(a, b, c)) > 0 || (iRes = DoSomething2(a, c, e)) > 0 || ... (iRes = DoSomething10(c, d, e)) > 0) { clean1(a, b, c); clean2(d, e); log_error(); return 1; } clean1(a, b, c); clean2(d, e); return 0; } 

如果在clean1()clean2()之前或之后clean1() clean2() ,可以使用:

 int myfunc() { blah a; blah2 b; blah3 c; blah4 d; blah5 e; int iRes; if ((iRes = DoSomething1(a, b, c)) > 0 || (iRes = DoSomething2(a, c, e)) > 0 || ... (iRes = DoSomething10(c, d, e)) > 0) { log_error(); } clean1(a, b, c); clean2(d, e); return 0; } 

甚至:

 int myfunc() { blah a; blah2 b; blah3 c; blah4 d; blah5 e; int iRes; if ((iRes = DoSomething1(a, b, c)) > 0 || (iRes = DoSomething2(a, c, e)) > 0 || ... (iRes = DoSomething10(c, d, e)) > 0) { // Don't need to do anything here } clean1(a, b, c); clean2(d, e); if (iRes > 0) log_error(); return 0; } 

if的主体可能是最终的任务:

 int myfunc() { blah a; blah2 b; blah3 c; blah4 d; blah5 e; int iRes; if ((iRes = DoSomething1(a, b, c)) > 0 || (iRes = DoSomething2(a, c, e)) > 0 || ... ) { iRes = DoSomething10(c, d, e); } clean1(a, b, c); clean2(d, e); if (iRes > 0) log_error(); return 0; } 

这种重构取决于清理在所有情况下都是相同的。 通常,它不是那么系统化。 由于您没有显示abcde中的任何a被初始化,因此很难确定什么是真正安全的。 如果doSomethingN()函数在C ++中并通过引用获取参数,则doSomething1()可以初始化abc – 但是在de具有值之前可能会发生对clean2()的调用。