避免"如果清理失败"重复的模式

Pattern to avoid "if failed cleanup" repetition

本文关键字:模式 如果 避免 失败      更新时间:2023-10-16

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

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; }


笔记:

  • 在实际代码中,这些函数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++即使抛出异常,我们也需要处理此清理代码,这会给事情的方案中带来另一个麻烦。但是,在 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()之前还是之后调用log_error()都没有关系,则可以使用:

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正在初始化,因此很难确定什么是真正安全的。 如果doSomethingN()函数C++并通过引用获取参数,那么doSomething1()可以初始化abc——但对clean2()的调用可能发生在de有值之前。

相关文章: