你认为这种技术“不好”吗?

时间:2008-10-28 16:34:32

标签: c++ coding-style

有时您需要在某些非严重错误条件下跳过执行部分方法。您可以使用例外,但通常不建议在正常的应用程序逻辑中使用异常,仅用于异常情况。

所以我做了这样的伎俩:

do
{
   bool isGood = true;

   .... some code

   if(!isGood)
       break;

   .... some more code

   if(!isGood)
       break;

   .... some more code

 } while(false);

 ..... some other code, which has to be executed.

我使用一个“假”循环,它将运行一次,我可以通过 break continue 中止它。

我的一些同事并不喜欢这样,他们称之为“不良做法”。我个人觉得这个方法很漂亮。但你怎么看?

28 个答案:

答案 0 :(得分:45)

糟糕的做法,取决于。

我在这段代码中看到的是一种非常有创意的方式,可以用更少硫磺味的关键词来写“goto”。

此代码有多种替代方案,根据具体情况可以或不可能更好。

您的行动/解决方案

如果您有很多代码,那么您的解决方案很有意思,但会在某些限制点评估此处理的“退出”:

do
{
   bool isError = false ;

   /* some code, perhaps setting isError to true */
   if(isError) break ;
   /* some code, perhaps setting isError to true */
   if(isError) break ;
   /* some code, perhaps setting isError to true */
}
while(false) ;

// some other code   

问题是你不能轻易使用“if(isError)break;”是一个循环,因为它只会退出内循环,而不是你的do / while块。

当然,如果失败在另一个函数内部,函数必须返回某种错误代码,并且您的代码不能忘记正确解释错误代码。

我不会讨论使用ifs甚至嵌套ifs的替代方案,因为经过一番思考后,我发现它们比你自己的解决方案更差。

调用goto a ... goto

也许你应该清楚地说明你正在使用goto的事实,并记录你选择这个解决方案的原因而不是另一个。

至少,它会显示代码可能出错,并提示审阅者验证或使您的解决方案无效。

你仍然必须打开一个块,而不是破坏,使用goto。

{
   // etc.
   if(/*some failure condition*/) goto MY_EXIT ;
   // etc.

   while(/* etc.*/)
   {
      // etc.
      for(/* etc.*/)
      {
         // etc.
         if(/*some failure condition*/) goto MY_EXIT ;
         // etc.
      }
      // etc.
      if(/*some failure condition*/) goto MY_EXIT ;
      // etc.
   }

   // etc.
}

MY_EXIT:

// some other code   

这样,当您通过goto退出块时,您无法绕过goto(C ++禁止)的某个对象构造函数。

这个问题解决了从嵌套循环问题退出的过程(并且使用goto退出嵌套循环是B. Stroustrup给出的一个例子,作为goto的有效用法),但是它不能解决一些函数调用可能失败的事实并被忽略(因为有人未能正确测试他们的返回代码,如果有的话)。

当然,现在,您可以从多个循环嵌套深度退出多个点,因此如果这是一个问题......

尝试/捕获

如果代码不应该失败(因此,失败是例外),或者即使代码结构可能失败,但退出过于复杂,那么以下方法可能更清楚:

try
{
   // All your code
   // You can throw the moment something fails
   // Note that you can call functions, use reccursion,
   // have multiple loops, etc. it won't change
   // anything: If you want to exit the process,
   // then throw a MyExitProcessException exception.

   if(/* etc. */)
   {
      // etc.
      while(/* etc.*/)
      {
         // etc.
         for(/* etc.*/)
         {
            // etc.
            if(/*some failure condition*/) throw MyExitProcessException() ;
            // etc.
         }
         // etc.

         callSomeFunction() ;
         // the function will throw if the condition is met
         // so no need to test a return code

         // etc.
      }
      // etc.
   }

   // etc.
}
catch(const MyExitProcessException & e)
{
   // To avoid catching other exceptions, you should
   // define a "MyExitProcessException" exception
}

// some other code

如果上面代码中的某些条件或上面代码调用的某些函数内部不满足,则抛出异常。

这比你的do / while解决方案有点重要,但具有相同的优点,甚至可以从内部循环或内部调用函数中止处理。

讨论

您的需求似乎来自于您可以执行复杂的过程(代码,函数调用,循环等),但您希望在某些条件下中断它(可能是失败,或者因为它很快就成功了)比例外)。如果你能以不同的方式重写它,你应该这样做。但也许,没有其他办法。

我们假设。

如果您可以使用try / catch对其进行编码,请执行此操作:要中断复杂的代码,抛出异常是正确的解决方案(事实上您可以在其中添加失败/成功信息你的异常对象不应该被低估)。之后你会有一个更清晰的代码。

现在,如果你处于速度瓶颈,解决抛出异常的问题作为退出并不是最快的方法。

没有人可以否认你的解决方案是一个美化的goto。不会有goto-spaghetti代码,因为do / while不会让你这样做,但它仍然是一个语义goto。这可能是一些人可能会发现此代码“不好”的原因:他们闻到了goto而没有清楚地找到它的关键字。

在这种情况下(在此性能,经过分析验证的情况下),您的解决方案似乎没问题,并且比使用if的替代方案更好,但质量较差(恕我直言)比goto解决方案至少,没有' t隐藏在一个错误的循环背后。

结论

就我而言,我发现你的解决方案很有创意,但我会坚持抛出异常解决方案。

因此,按优先顺序排列:

  1. 使用try / catch
  2. 使用转到
  3. 使用do / while循环
  4. 使用ifs / nested ifs

答案 1 :(得分:31)

你几乎只是将“goto”伪装成假循环。无论你喜欢和不喜欢,你都会使用一个真正毫无掩饰的goto。[/ p>

就个人而言,我只是把它写成

bool isGood = true;

   .... some code

   if(isGood)
   {
   .... some more code
   }

   if(isGood)
   {
   .... some more code
   }

答案 2 :(得分:24)

为什么要使用假循环?你可以用一种方法做同样的事情,它可能不会被认为是一种“坏习惯”,因为它更受期待。

someMethod()
{
   .... some code

   if(!isGood)
       return;

   .... some more code

   if(!isGood)
       return;

   .... some more code

 }

答案 3 :(得分:12)

在难以识别的习语中,你有非复杂的非线性控制流。所以,是的,我认为这种技术很糟糕。

花一些时间试图弄清楚这是否可以写得更好一点可能是值得的。

答案 4 :(得分:11)

这是令人费解和困惑的,我会立即废弃它。

考虑这个选择:

private void DoSomething()
{
   // some code
   if (some condition)
   {
      return;
   }
   // some more code
   if (some other condition)
   {
      return;
   }
   // yet more code
}

还要考虑将上面的代码分解为多个方法。

答案 5 :(得分:8)

public bool Method1(){ ... }
public bool Method2(){ ... }

public void DoStuff(){
    bool everythingWorked = Method1() && Method2();
    ... stuff you want executed always ...
}

这种作用的原因是由于所谓的短路逻辑。如果Method1返回false,则不会调用Method2。

这还有一个额外的好处,你可以将你的方法分解成更小的块,这将更容易进行单元测试。

答案 6 :(得分:5)

您要做的是非本地故障恢复。这是goto的用途。用它。 (实际上,这是异常处理的目的 - 但如果你不能使用它,'goto'或'setjmp / longjmp'是下一个最好的东西)。

这种模式,if(succeeded(..))模式和'goto cleanup',都是3在语义上和结构上相同。使用代码项目中最常见的一个。一致性很有价值。

我会谨慎地反对if(failed(..)) break;,因为如果你试图嵌套循环,你会产生一个令人惊讶的结果:

do{
   bool isGood = true;
   .... some code
   if(!isGood)
       break;
   .... some more code
   for(....){
      if(!isGood)
          break; // <-- OOPS, this will exit the 'for' loop, which 
                 //  probably isn't what the author intended
      .... some more code
   }
} while(false);
..... some other code, which has to be executed.

goto cleanupif(succeeded(..))都没有这个惊喜,所以我鼓励使用这两个中的一个。

答案 7 :(得分:5)

基本上你刚刚描述了goto。我一直在C中使用goto。我不认为它不好,除非你用它来模拟一个循环(从来没有这样做过!)。我在C中使用goto的典型用法是模拟异常(C没有例外):

// Code

if (bad_thing_happened) goto catch;

// More code

if (bad_thing_happened) goto catch;

// Even more code

finally:

// This code is executed in any case
// whether we have an exception or not,
// just like finally statement in other
// languages

return whatever;

catch:

// Code to handle bad error condition

// Make sure code tagged for finally
// is executed in any case
goto finally;

除了catch和最后有相反顺序的事实,我不明白为什么这个代码应该是BAD只是因为它使用goto,如果真正的try / catch / finally代码工作完全像这只是不使用转到。这是没有意义的。因此,我无法确定为什么您的代码应被标记为BAD。

答案 8 :(得分:2)

只要代码可读,我就没问题了。

答案 9 :(得分:2)

重构。在大多数情况下,干净的解决方案是将此代码拆分为一个较小的辅助函数,您可以从该函数返回,而不是打破您的非实际循环。

然后你可以用你的break代替return,现在人们可以在阅读时立即理解你的代码,而不必停下来想知道为什么你做了这个实际上没有循环的循环。

是的,我只是因为它不像读者所期望的那样说,这是一种不好的做法。最不惊讶的原则,等等。当我看到一个循环时,我希望它循环,如果没有,我必须停下来想知道为什么

答案 10 :(得分:2)

无论好坏,我在一些地方使用了这个结构。但是,它的开头有明确的记录:

    /* This is a one-cycle loop that simplifies error handling */
    do
    {
        ...modestly complex code, including a nested loop...
    } while (0);

这是在C而不是C ++中 - 因此异常不是一种选择。如果我使用的是C ++,我会认真考虑使用异常来处理异常。杰里米建议的重复测试成语也是合理的;我更频繁地使用它。 RAII也会对我有很大帮助;可悲的是,C并不容易支持。使用更多功能会有所帮助。从嵌套循环内部处理断点是通过重复测试完成的。

我不会把它归类为一种伟大的风格;我不会自动将其归类为“坏”。

答案 11 :(得分:2)

我多年来一直使用这个习语。我发现它更类似于类似的嵌套或串行ifs,“goto onError”,或者有多个返回。上面的嵌套循环示例值得注意。我总是在“做”上添加一条评论,以便向任何对这个成语的新手说清楚。

答案 12 :(得分:1)

如果你的代码所做的事情不是现有结构的简单含义,那么你就可以冒险进入“可爱”的领域。

在这种情况下,你有一个只能运行一次的“循环”。任何代码的读者都需要进行双重操作才能弄清楚发生了什么。

如果它不是“好”的情况确实是例外,那么抛出异常将是更好的选择。

答案 13 :(得分:1)

将您的代码拆分为更小的功能元素块 - 因此您可以将上述内容拆分为一个返回而不是中断的函数。

我不知道上述是否是不好的做法,但是它的可读性有点偏差,可能会让那些可能需要维护源头的人感到困惑。

答案 14 :(得分:1)

  

您可以使用异常,但通常不建议在正常应用程序逻辑中使用异常,仅适用于异常情况。

使用异常没有错。例外是应用程序逻辑的一部分。关于例外的指导原则是它们在运行时应该相对较少。

答案 15 :(得分:1)

对我而言,你所做的事情在很多方面都很糟糕。可以通过将该代码放在方法中来替换循环。

我个人认为,如果你必须放一个!在你的条件面前,你正在寻找错误的东西。为了便于阅读,请将布尔匹配设置为您要检查的内容。你真的在检查是否有错误或一些不好的情况,所以我宁愿:

If (isError)
{
   //Do whatever you need to do for the error and
   return;
}
过度
If (!isGood)
{
  //Do something
}

因此,请检查您确实要检查的内容,并将异常检查保持在最低限度。你的目标应该是可读性而不是棘手。想想将要出现并维护你的代码的可怜的灵魂。

28年前我工作的第一件事是Fortran程序,它总是需要检查是否有可用的图形设备。有人决定为这个LNOGRAF调用布尔值,所以如果有可用的图形设备,这将是错误的。我相信它是以这种方式设置的,因为效率错误,如果有图形设备,设备的检查返回零。在整个代码中,所有检查都是为了查看图形设备是否可用。它充满了:

如果(.NOT。LNOGRAF)

我认为没有一个:

如果(LNOGRAF)

在该计划中。这被用于B-52和巡航导弹的任务规划软件。它肯定教会我为我正在检查的内容命名变量。

答案 16 :(得分:1)

我会考虑这种不好的做法。我认为这会更加惯用,如果你把它变成一个函数并且将断点更改为返回值,那么通常会更清楚。

答案 17 :(得分:1)

这是一个非常奇怪的习语。它使用循环来实现其无意的东西,并可能引起混淆。我想这将跨越多个页面,对于大多数人来说,这绝不会超过一次,这将是一个惊喜。

如何使用更常用的语言功能,如功能?

bool success = someSensibleFunctionName();

if(success)
{
   ...
}

someCommonCodeInAnotherFunction();

答案 18 :(得分:1)

功能方法怎么样?


        void method1()
        {
            ... some code
            if( condition )
                method2();
        }

        void method2()
        {
            ... some more code
            if( condition )
                method3();
        }

        void method3()
        {
            ... yet more code
            if( condition )
                method4();
        }

答案 19 :(得分:1)

这取决于替代品的含义。 你必须承认你发布的代码有点难看。我不会说它很清楚。这是一种黑客行为。因此,如果使用其他一些编码解决方案会更糟,那么好吧。但如果你有更好的选择,不要让借口“它足够好”安慰你。

答案 20 :(得分:1)

我想说你的解决方案可能是正确的解决方案,但这取决于。 Paul Tomblin发布了一个更好的答案(一系列if管)......如果可以使用的话。

当循环中存在昂贵的对象初始化时,无法使用Paul的解决方案。如果在后面的步骤中使用创建的对象,则do while(0)解决方案更好。

也就是说,应该改进变量命名。另外,为什么要重复使用“escape”变量呢?而是明确地捕获每个单独的错误条件并打破循环,以便明显是什么导致每个点发生突破。

其他人建议使用函数调用。同样,如果由于在任何给定步骤可能传递的args数量而给函数调用增加了不必要的复杂性,这可能不是理想的分解。

其他人则认为这是一个难以理解的习语。好吧,首先你可以根据循环顶部的建议发表评论。其次,do-while(0)是宏中常见的常用习惯,所有C程序员都应该立即识别,所以我就是不买。

答案 21 :(得分:1)

首先,如果你只是想要回答这个代码结构或成语是否“坏”,我会认为是。但是,我认为这是分解不良的症状,而不是你所拥有的代码是“好”还是“坏”。

我认为必须做更好的分析和重构才能进一步解决问题的根源,而不是只看代码。如果你可以这样做:

if (condition1(...) && condition2(...) && condition3(...) && ... && conditionN(...)) {
    // code that ought to run after all conditions
};
// code that ought to run whether all conditions are met or not

然后我认为它会更“可读”,更具惯用性。这样,您可以创建以下函数:

bool conditionN(...) {
    if (!real_condition) return false;
    // code that ought to run
    return true;
};

您可以从编译器获得更好的分解和帮助,从而产生必要的短路,这将带来&amp;&amp;'s带来。编译器甚至可以在函数中嵌入代码,以产生比手动编码循环更好的代码。

答案 22 :(得分:0)

如果将 if(!isGood)break; 之间的代码分割成单独的函数,最终可能会有几十个只包含几行的函数,因此doers不会简化任何操作。我无法使用返回,因为我还没准备好离开这个功能,还有我想做的事情。 我接受可能我应该为我想要执行的每个代码部分解决单独的 if(isGood){...} 条件,但有时会导致大量的花括号。但是我想我接受人们并不喜欢那种类型的建筑,所以所有的条件都有风!所以感谢您的回答。

答案 23 :(得分:0)

元评论: 在编码时,您的目标应该是清晰,可维护的代码。你不应该放弃在效率祭坛上的易读性,除非你描述并证明它是必要的并且它实际上改善了事物。应该避免绝地心灵的伎俩。总是认为下一个维护你的代码的人是你的家庭住址的一个重要的精神病患者。我,例如。

答案 24 :(得分:0)

我认为该技术基本上没有错。我认为我会确保bool被命名为比goodGood更具描述性的东西。虽然,现在我看一下,为什么将循环中的所有代码放入单个if(!isGood)块中不起作用?循环只执行一次。

答案 25 :(得分:-1)

我认为我必须同意你的同事只是因为可读性,乍一看你用这段代码想要完成的事情并不清楚。

为什么不使用

if(isGood)
{
...Execute more code
}

答案 26 :(得分:-1)

我认为人们在这里并不诚实。

如果我作为你的团队负责人,会看到这样的代码,你会在一对一的情况下加入,并被标记为团队的潜在问题,因为这段代码特别可怕。

我说你应该听取你的同事的意见,并按照这里发布的任何建议重写。

答案 27 :(得分:-1)

这是例外情况。出了问题,你无法继续逻辑。即使恢复是微不足道的,它仍然是一个例外。如果您有充分的理由不使用异常,例如当您使用不支持异常的语言编程时,则使用条件块,而不是循环。