【问题标题】:Style question about existing piece of code (C/C++)关于现有代码的样式问题 (C/C++)
【发布时间】:2009-05-25 20:07:32
【问题描述】:

我只是希望以下内容在您看来不像是多余的胡言乱语 :)
反正就是这样:

for (p = fmt; *p; p++) {
    if (*p != '%') {
        putchar(*p);
        continue;
    }
    switch (*++p) {
        /* Some cases here */
        ...
    }
 }

我想知道为什么作者(Kernighan / Ritchie)在if 声明中使用continue
我认为这仅仅是因为他认为这比在else 语句下缩进整个switch 更优雅,你怎么看?

【问题讨论】:

    标签: c++ c coding-style


    【解决方案1】:

    大概吧。人脑的堆栈空间有限,难以处理深度嵌套的结构。任何将我们期望解析的信息扁平化的东西都更容易理解。

    同样,我通常更喜欢这个:

    bool foo(int arg)
    {
        if(!arg) {
            /* arg can't be 0 */
            return false; 
        }
    
        /* Do some work */
        return true;
     }
    

    到这里:

     bool foo(int arg) 
     { 
         if(!arg) {
             /* arg can't be 0 */ 
             return false; 
         } else {
             /* Do some work */ 
             return true;
         } 
     }
    

    或者更糟的是:

    bool foo(int arg) 
    { 
        if(arg) {
            /* Do some work */ 
            return true;
        } else {
            /* arg can't be 0 */ 
            return false; 
        } 
    }
    

    在最后一个例子中,完成这项工作的部分可能很长。当读者到达 else 子句时,他可能不记得他是如何到达那里的。

    将救助条件放在接近开始的位置有助于确保尝试调用您的函数的人能够很好地了解函数期望的输入。

    此外,正如其他人指出的那样,continue 清楚地表明,无需进一步阅读循环内的代码,以确定在此情况下是否在该点之后进行了更多处理,从而使代码更易于理解。同样,你强迫读者跟踪的东西越少越好。

    【讨论】:

      【解决方案2】:

      因为使用 continue 很明显代码是为这个循环迭代完成的。如果会使用 else,您还必须检查 else 之后是否没有代码。

      我认为尽快退出上下文通常是一个好习惯,因为这会导致代码更清晰。


      例如:

      if(arg1 == NULL)
        return;
      
      if(arg2 == NULL)
        return;
      
      //Do some stuff
      

      对比

      if(arg1 != null)
      {
        if(arg2 != null)
        {
          //Do some stuff
        }
      }
      

      【讨论】:

      • +1 清除代码。除此之外,当稍后修改代码时,您不必考虑所有错误情况,因为它们已经在顶部处理过,您知道您只需要处理有效情况。我觉得以这种方式编写代码非常优雅——有使用“其他”版本的经验,并且它是我另一家公司的标准——例如只有一个返回的东西......
      【解决方案3】:

      这样写就更容易阅读了。

      我们在此循环中的迭代完成了吗? 是吗?所以让我们继续进行下一次迭代。

      【讨论】:

      • 这是有争议的——这取决于这个习语在你的代码中是否常见。如果不是这样,它的缺点是控制循环的方式有点不寻常,并且是潜在的维护问题/错误来源。
      • 是的,完全正确。不习惯这个成语的人在维护它时可能会感到困惑。这只是我习惯的,启发式教给我的更好。我更容易迷失在嵌套的 if-then-else 块中,然后注意到中断;或继续;
      【解决方案4】:

      我认为他有足够的理由缩进 switch 下的代码,缩进整个函数的肉是相当浪费水平空间的。在编写代码时,我想 80 字符宽度仍然很流行。

      我认为这并不难理解,但我确实认为最好先提及你不做的事情,然后是 GTFO。

      【讨论】:

        【解决方案5】:

        这样写代码的方式总是有很多——

        将整个 switch 放在 else 语句中是完全有效的。我想他们这样做的原因〜可能〜只是他们当时的想法:

        "如果p处的值不等于'%',放然后继续。"

        如果您在 else 下进行了 switch,那么对于作者来说,在该特定情况下您跳到下一个迭代可能并不那么明显。

        不过,这完全是个人风格选择。我不会太担心 - 只需以对您和您的团队最有意义的方式编写即可。

        【讨论】:

          【解决方案6】:

          我同意。 但是您不能将其视为“单纯的理由”,这实际上是一个很好的理由,因为它降低了代码的整体复杂性。使其更短,更易于阅读和理解。

          【讨论】:

            【解决方案7】:

            如果您使用else,那么else 中的所有内容都需要缩进:

            if ()
            {
              doA();
            }
            else
            {
              doB();
              if ()
              {
                doC();
              }
              else
              {
                doD()
              }
            }
            

            如果你使用continue,那么你不需要缩进:

            if ()
            {
              doA()
              continue;
            }
            doB();
            if ()
            {
              doC();
              continue;
            }
            doD();
            

            另外,continue 意味着我可以停止考虑这种情况:例如,如果我看到else,那么可能会在循环的后面(即最后)对'%' 进行更多处理else 声明的;而在看到continue 时,我立即知道循环中'%' 案例的处理已完全完成。

            【讨论】:

              【解决方案8】:

              最可能的原因是后面的switch 相当长——这看起来像printf 格式解析。

              【讨论】:

                【解决方案9】:

                继续/中断循环的原因可能不止一个。所以接下来会是这样:

                loop
                {
                   if (cond1)
                   {
                      if (cond2)
                      {
                         if (cond2)
                         {
                            more conditions...
                         }
                      }
                   }
                   else
                   {
                      the loop action 
                   }
                }
                

                恕我直言,它不像您的示例中的循环那么优雅和可读,例如:

                loop
                {
                   if (cond1)
                      continue;
                   if (cond2)
                      continue;
                   if (cond2)
                      continue;   
                   if( more conditions...)
                      continue;
                
                  the loop action 
                }
                

                而且您甚至不需要了解所有“if”的所有结构(它可能要复杂得多)来理解循环逻辑。

                附:只是为了案例:我认为作者没有考虑如何编写这个循环,他们只是写了它:)

                【讨论】:

                  【解决方案10】:

                  我坚持 Dijkstra 的教导:goto 是有害的。而 continue/break 是 goto 的小兄弟。

                  如果问题是代码缩进过多,解决方案不是在循环中放置 continue,而是通过将代码分隔在不同的函数中或考虑更好的组织方式来降低复杂性。

                  例如,@Kamarey sn-p 会更清晰,如下所示:

                  loop
                  {
                     if (!(cond1 ||
                           cond2 ||
                           cond2 || 
                           ...))
                     {
                        the loop actions;
                     }
                  }
                  

                  或@Ori Pessach 示例可以表示为:

                  bool foo(int arg)
                  {
                      if(arg) {
                          /*do some work*/
                      }
                  
                      return arg != 0;
                  }
                  

                  简而言之,通常我找不到足够好的理由来使用非结构化编程结构(可能是在非常有限的情况下进行一些清理代码......)

                  【讨论】:

                  • 我尊重你所说的。我知道 Dijkstra 表达了他对使用 GOTO 的反对意见,但他有没有说过继续/中断?我发现这些语句简化了代码。
                  • 据我所知,E.D.反对任意 GOTO。 continue/break 的目标是相当有限的;另一方面,GOTO 可能会去任何地方
                  【解决方案11】:

                  嗯,我写了大约 11 年的 C 程序,我必须阅读 5 次你的代码才能理解它!

                  Kernighan 和 Ritchie 在 60 年代很活跃。在那个时候,能够理解一段代码并不重要。能够编写适合 16 Ko 的代码。

                  所以我并不感到惊讶。当你的老师是 K&R 时,C 是一门糟糕的语言。看看 realloc:今天谁会知道这样的代码?在 60 年代,它风靡一时,但现在至少令人震惊:o)

                  【讨论】:

                  • K&R 出现于 1978 年,而不是 1960 年代,通常被认为是一篇非常好的文章,简洁且写得很好。代码的清晰性与此高度相关:在十年前的 1968 年北约软件工程会议上,与会者一致认为软件变得如此复杂以至于出现了“软件危机”。正在讨论的代码示例使用非常常见的 C(和 C++)习语。
                  • 好吧,看看 realloc。以今天的标准来看,真是一团糟。如果你喜欢 K 和 R,很酷,你只落后了 2 年!万事如意!
                  猜你喜欢
                  • 2023-03-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多