使用 goto 作为错误处理策略



我最近进行了一次代码审查,并开始了一场辩论。我的大部分代码如下所示:

for (i = 1; i <= 3; i++)
        {
            DoubleValue = tODBCX->getDouble(KeyFieldCount + i, IsNULL, IsSuccess);
            if (IsNULL)
            {
                LoggerDrillHole::LogToDB(LOG_ERROR, L"Survey depth, dip and azimuth values can not be NULL.", __FUNCTIONW__);
                IsSuccess = false;
                goto EXIT;
            }
            else
            {
                if (i == 1)
                    Depth = DoubleValue;
                else if(i == 2)
                    DipDegrees = DoubleValue;
                else if (i == 3)
                    AzimuthDegrees = DoubleValue;
            }
        }

有争议的goto声明引发了争论。此代码包含在一个函数中,该函数通过初始化局部布尔变量 IsSuccess = true 开始生活,该函数最终返回 IsSuccess。EXIT 策略负责整理基本代码;

EXIT:
    tODBCX->Close();
    if (Key != 0) free(Key);
    Key = 0;
    if (PriorKey != 0) free(PriorKey);
    PriorKey = 0;
    return IsSuccess;
有几个

这样的goto EXIT语句与设置IsSuccess = false和记录到数据库等相邻。有人评论说,这会破坏代码的流程。应该改用 do 循环(无限循环)并打破该循环,然后处理所有必需的,而不是使用 goto 语句。

我非常不喜欢无限循环策略,但如果它真的提高了可读性,我可以习惯它。有没有更好的方法?

我想

将其标记为此问题的副本。尽管如此,它并不完全相同,即使解决方案是相同的:

在C++中,最好的解决方案是使用 RAII 和事务代码。在 C 中,最好的解决方案是使用 goto,并遵循一些规则(仅用于返回/清理,不要使用 goto 来模拟循环等)。

请参阅我在上面提到的问题中的回答(基本上,解决方案是使用 RAII 和事务代码);这将完全消除对 goto 清理/错误处理块的需求。

在这里使用goto并没有错。这是为数不多的最干净解决方案的情况之一。(另一个例子是打破内部循环。

使用 do { ... } while (false) 循环是一种人工解决方案,实际上会降低代码的可读性。

在 C 中,考虑将代码分解为两个函数...一个外部函数,它执行常见的初始化并传递内部函数所需的变量,以便内部函数可以简单地返回成功状态,知道外部函数将清理。


在C++通常最好使用作用域防护,以便析构函数确保正确清理。 考虑您的:

 tODBCX->Close();

如果tODBCX需要比函数调用更长的寿命 - 因此析构函数中的Close()没有帮助 - 则创建一个帮助程序:

struct Odbc_Access_Guard 
{
    Odbc_Access_Guard(ODBC& o) : odbc_(o) { }
    ~Odbc_Access_Guard() { odbc_.close(); }
    operator ODBC&() { return odbc_; }
    operator const ODBC&() const { return odbc_; }
    ODBC& odbc_;
};

然后在函数内部:

Odbc_Access_Guard odbc(tODBC);
odbc.xyz();
if (whatever)
   return ...success expression...;

同样的事情也适用于您的指针:它们可能应该是使用上述逻辑的共享指针或守卫。 然后,您可以随时返回,甚至不必考虑去哪里进行清理代码,也不必想知道它是否与当前的变量使用是最新的。

最新更新