遵守愚蠢的规则
函数只能在其最后一行返回!
引言
一些编码标准,包括 MISRA,坚持函数只能在其末尾返回。这通常会导致使用一个标志来绕过每个后续条件,最终到达 return
语句。幸运的是,有一种惯用语——在函数中间也很有用——可以减轻这种糟糕的代码。
Using the Code
你有没有遇到过像这样的代码?
<type> Function(...)
{
<type> result = <default>;
bool done = false;
if(condition1)
{
result = <something>;
done = true;
}
if(!done && condition2)
{
result = <something else>;
done = true;
}
...
if(!done && conditionN)
{
result = <something completely different>;
done = true;
}
return result;
}
或者更糟糕的是,通过深度嵌套条件语句,以至于你必须使用水平滚动条才能阅读右侧的代码?
有些人实际上认为这能显著提高代码质量,因此强制执行它。如果你觉得这像我一样愚蠢,这里有一种改进代码的方法,同时仍然遵守规则。
不,我们不会使用 goto
来到达 return
语句。虽然那会是一种改进,但标准可能也禁止它。
所以,这样做
<type> Function(...)
{
<type> result = <default>;
do
{
if(condition1)
{
result = <something>;
break;
}
if(condition2)
{
result = <something else>;
break;
}
...
if(conditionN)
{
result = <something completely different>;
break;
}
} while(false);
return result;
}
这类似于 goto
解决方案,但我怀疑你的标准不会对此有任何异议。在这种情况下不得不使用这种惯用语仍然很不幸,但这是在糟糕的选择中最好的。
这种惯用语在函数中间也非常有用。例如,我有一个 C++ 解析器,其中以下代码解析类模板实例
bool Parser::ParseClassInst(ClassInst* inst, size_t pos)
{
auto name = inst->ScopedName(true);
Debug::Progress(CRLF + Indent() + name);
// Initialize the parser. If an "object code" file is being produced,
// insert the instance name.
Enter(IsClassInst, name, inst->GetTemplateArgs(), inst->GetCode(), true);
lexer_.Reposition(pos);
Context::Trace(CxxTrace::START_TEMPLATE, inst);
// Push the template instance as the current scope and start to parse it.
// The first thing that could be encountered is a base class declaration.
do
{
BaseDeclPtr base;
Context::PushScope(inst, false);
GetBaseDecl(base);
if(!lexer_.NextCharIs('{')) break;
inst->AddBase(base);
GetMemberDecls(inst);
Context::PopScope();
if(!lexer_.NextCharIs('}')) break;
if(!lexer_.NextCharIs(';')) break;
GetInlines(inst);
}
while(false);
// The parse succeeded if the lexer reached the end of the code. If the
// parse failed, indicate this on the console. If an "object code" file
// is being produced, indicate that parsing of the template is complete.
auto parsed = lexer_.Eof();
Debug::Progress((parsed ? EMPTY_STR : " **FAILED** "));
if(!parsed) Failure(venue_);
Context::Trace(CxxTrace::END_TEMPLATE);
return parsed;
}
当然,这并不是一个很好的例子。一个更好的(但更复杂的)例子涉及解析限定名称中的每个标识符。那个 do
{...}
while(false);
出现在 while
-switch
循环的一个 case
中,并且处理一个命名类模板实例的标识符,因此它确实有助于保持代码的可管理性。但我希望上面的内容能够说明这一点。
祝你的代码审查顺利!
历史
- 2021年5月1日:初始版本