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

时间:2017-07-21 23:45:07

标签: c++ c design-patterns

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

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函数,不是我自己编写的
  • 我想避免在clean()之外定义第二个函数myfunct()来处理清理+错误
  • 我考虑过使用预处理器宏,但我怀疑这种情况是一种很好的做法

示例:

This code就是这种情况的一个例子:每10行代码实际上= 2行仅用于实际执行某些操作 + 8行错误测试和清理...我们可以做得更好吗?

3 个答案:

答案 0 :(得分:3)

正如贾斯汀所说,答案将因语言而异。

如果您在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,这意味着析构函数是可行的方法。

答案 1 :(得分:2)

这是我喜欢的一个技巧,可以避免转到:

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

答案 2 :(得分: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)
     {
         clean1(a, b, c);
         clean2(d, e);
         log_error();
         return 1;
     }

     clean1(a, b, c);
     clean2(d, e);
     return 0;
}

如果在log_error()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,因此很难确定哪些是真正安全的。如果doSomethingN()函数在C ++中并通过引用获取参数,则doSomething1()可以初始化abc - 但调用{{1}可能会在clean2()d有值之前发生。