我最近进行了一次代码审查,并开始了一场辩论。我的大部分代码如下所示:
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...;
同样的事情也适用于您的指针:它们可能应该是使用上述逻辑的共享指针或守卫。 然后,您可以随时返回,甚至不必考虑去哪里进行清理代码,也不必想知道它是否与当前的变量使用是最新的。