代码之家  ›  专栏  ›  技术社区  ›  Ed Guiness

为什么“玩弄循环索引”是不好的?

  •  18
  • Ed Guiness  · 技术社区  · 17 年前

    史蒂夫·麦康奈尔的清单项目之一是 you should not monkey with the loop index (第16章第25页, 循环索引 ,PDF格式)。

    这从直觉上讲是有道理的,这是我一直遵循的一种做法,除非我在白天学会了如何编程。

    在最近的一次代码审查中,我发现了这个尴尬的循环,并立即将其标记为可疑。

            for ( int i=0 ; i < this.MyControl.TabPages.Count ; i++ )
            {
                this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
                i--;
            }
    

    这几乎很有趣,因为它通过将索引保持在零直到删除所有TabPages来工作。

    这个循环可以写成

            while(MyControl.TabPages.Count > 0)
                MyControl.TabPages.RemoveAt(0);
    

    由于控件实际上与循环几乎同时编写,因此它甚至可以写成

            MyControl.TabPages.Clear();
    

    从那以后,我一直受到关于代码审查问题的质疑,发现我对 为什么? 这是一个糟糕的做法,没有我想要的那么强烈。我说,理解循环的流程更难,因此更难维护和调试,最终在代码的生命周期内成本更高。

    有没有更好地阐明为什么这是一种不良做法?

    10 回复  |  直到 7 年前
        1
  •  24
  •   PEZ    17 年前

    我觉得你的发音很好。也许可以这样措辞:

    因为逻辑可以表达得很多 更清楚,它应该。

        2
  •  23
  •   Marc Gravell    17 年前

    好吧,这毫无意义地增加了混乱——你也可以很容易地写:

    while(MyControl.TabPages.Count > 0)
    {
        MyControl.TabPages.Remove(MyControl.TabPages[0]);
    }
    

    或(更简单)

    while(MyControl.TabPages.Count > 0)
    {
        MyControl.TabPages.RemoveAt(0);
    }
    

    或(最简单)

    MyControl.TabPages.Clear();
    

    在上述所有情况下,我不必眯着眼睛思考任何边缘情况;很清楚什么时候会发生。如果你正在修改循环索引,你很快就会让它一眼就很难理解。

        3
  •  16
  •   Toon Krijthe    17 年前

    这完全取决于期望。

    当使用循环计数器时,您希望它在循环的每次迭代中都以相同的量递增(递减)。

    如果你弄乱了循环计数器(如果你喜欢,也可以弄乱),你的循环就不会像预期的那样运行。这意味着它更难理解,增加了你的代码被误解的机会,这会引入错误。 或者(错误地)引用一个聪明但虚构的人物:

    complexity leads to misunderstanding
    
    misunderstanding leads to bugs
    
    bugs leads to the dark side.
    
        4
  •  3
  •   Sam Meldrum    17 年前

    我同意你的挑战。如果他们想保持for循环,代码:

    for ( int i=0 ; i < this.MyControl.TabPages.Count ; i++ ) {
        this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
        i--;
    }
    

    减少如下:

    for ( int i=0 ; i < this.MyControl.TabPages.Count ; ) {
        this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
    }
    

    然后执行以下操作:

    for ( ; 0 < this.MyControl.TabPages.Count ; ) {
        this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
    }
    

    但是a 与…同时 循环或a 清除() 如果存在这种方法,显然更可取。

        5
  •  2
  •   Paul Dixon    17 年前

    我认为你可以通过引用高德纳的概念来建立一个更有力的论点 literate programming ,程序应该 是为计算机编写的,但用于向计算机传达概念 其他程序员 因此,循环更简单:

     while (this.MyControl.TabPages.Count>0)
     {
                this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
     }
    

    更清楚地说明了意图——删除第一个标签页,直到没有标签页为止。我认为大多数人会比最初的例子更快地摸索。

        6
  •  1
  •   foxy    17 年前

    这可能更清楚:

    while (this.MyControl.TabPages.Count > 0)
    {
      this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
    }
    
        7
  •  1
  •   Rad    17 年前

    一个可以使用的论点是,调试这样的代码要困难得多,因为索引被更改了两次。

        8
  •  1
  •   Joris Timmermans    17 年前

    原始代码非常冗余,无法将for循环的动作弯曲到必要的程度。增量是不必要的,由减量来平衡。这些应该是PRE增量,而不是POST增量,因为从概念上讲,POST增量是错误的。与标签页计数的比较是半冗余的,因为这是一种检查容器是否为空的黑客方法。

    简而言之,这是不必要的聪明,它增加而不是消除冗余。因为它既可以明显更简单,也可以明显更短,所以这是错误的。

        9
  •  1
  •   supercat    16 年前

    使用索引的唯一原因是有选择地擦除内容。即使在这种情况下,我也认为最好说:

      i=0;
      while(i < MyControl.Tabpages.Count)
        if (wantToDelete(MyControl.Tabpages(i))
          MyControl.Tabpages.RemoveAt(i);
        else
          i++;
    而不是每次删除后的循环索引。或者,更好的是,将索引计数向下,这样当一个项目被删除时,它就不会影响未来需要删除的项目的索引。如果删除了许多项目,这也有助于减少每次删除后移动项目所花费的时间。
        10
  •  0
  •   Matthew Pelser    17 年前

    我认为指出循环迭代不是由任何人所期望的“I++”控制,而是由疯狂的“I-”设置控制的事实就足够了。

    我还认为,通过评估计数然后改变循环中的计数来改变“I”的状态也可能导致潜在的问题。我希望for循环通常具有“固定”的迭代次数,并且for循环条件中唯一改变为循环变量“I”的部分。