65.9K
CodeProject 正在变化。 阅读更多。
Home

遵守愚蠢的规则

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.80/5 (6投票s)

2021 年 5 月 1 日

CPOL

1分钟阅读

viewsIcon

9887

函数只能在其最后一行返回!

引言

一些编码标准,包括 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日:初始版本
© . All rights reserved.