Steve McConnell的核对表项目之一是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();
我一直受到代码审查问题的挑战,并发现我对为什么这种不良做法的表达并不像我所希望的那么强烈。我说很难理解循环的流程,因此在代码的生命周期内更难维护和调试,并且最终会更昂贵。
是否更好地阐明了为什么这是不好的做法?
答案 0 :(得分:23)
嗯,这会增加混乱的目的 - 你可以轻易地写一下:
while(MyControl.TabPages.Count > 0)
{
MyControl.TabPages.Remove(MyControl.TabPages[0]);
}
或(更简单)
while(MyControl.TabPages.Count > 0)
{
MyControl.TabPages.RemoveAt(0);
}
或(最简单)
MyControl.TabPages.Clear();
在上述所有情况中,我不必眯眼并考虑任何边缘情况;很清楚会发生什么事情。如果你正在修改循环索引,你很快就会很难理解它。
答案 1 :(得分:20)
我认为你的表达很棒。也许它可以这样措辞:
由于逻辑可以表达很多 它应该更清楚。
答案 2 :(得分:16)
这都是关于期望的。
当使用循环计数器时,您希望循环的每次迭代以相同的量递增(递减)。
如果你使用循环计数器弄乱(或者你喜欢猴子),你的循环就不会像预期的那样。这意味着它更难理解并且增加了代码被误解的可能性,并且这引入了错误。 或者(错误地)引用一个明智但虚构的角色:
complexity leads to misunderstanding
misunderstanding leads to bugs
bugs leads to the dark side.
答案 3 :(得分:3)
我同意你的挑战。如果他们想要保持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] );
}
但 while 循环或 Clear()方法(如果存在)显然更为可取。
答案 4 :(得分:2)
我认为你可以通过调用Knuth的literate programming概念来构建一个更强大的论点,程序应该不为计算机编写,而是将概念传递给其他程序员,因此更简单的循环:
while (this.MyControl.TabPages.Count>0)
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}
更清楚地说明了意图 - 删除第一个标签页,直到没有剩下。我想大多数人会比原来的例子快得多。
答案 5 :(得分:1)
这可能更清楚:
while (this.MyControl.TabPages.Count > 0)
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[0] );
}
答案 6 :(得分:1)
可以使用的一个参数是调试这样的代码要困难得多,其中索引被更改两次。
答案 7 :(得分:1)
原始代码是高度冗余的,可以将for循环的动作弯曲到必要的位置。增量是不必要的,并且通过减量来平衡。这些应该是PRE增量,而不是POST增量,因为从概念上讲,后增量是错误的。与tabpages计数的比较是半冗余的,因为这是检查容器是空的一种hackish方式。
简而言之,这是不必要的聪明,它增加而不是消除冗余。因为它既明显更简单又明显更短,这是错误的。
答案 8 :(得分:1)
根本无法使用索引的唯一理由是,如果有人选择性地擦除事物。即使在这种情况下,我认为最好还是说:
i=0; while(i < MyControl.Tabpages.Count) if (wantToDelete(MyControl.Tabpages(i)) MyControl.Tabpages.RemoveAt(i); else i++;而不是在每次删除后使用循环索引。或者,更好的是,将索引向下计数,以便在删除项目时,它不会影响将来需要删除的项目的索引。如果删除了许多项目,这也可能有助于最大限度地减少每次删除后移动项目所花费的时间。
答案 9 :(得分:0)
我不确定我是否完全理解你所追求的是什么,但也许是这样的?
for ( int i=this.MyControl.TabPages.Count - 1 ; i >= 0 ; i-- )
{
this.MyControl.TabPages.Remove ( this.MyControl.TabPages[i] );
}
或仅仅是TabPages集合的实现IList:
this.MyControl.TabPages.Clear();
答案 10 :(得分:0)
我认为指出循环迭代不是像任何人所期望的那样由“i ++”控制的事实,而是疯狂的“i--”设置应该已经足够了。
我还认为通过评估计数然后改变循环中的计数来改变“i”的状态也可能导致潜在的问题。我希望for循环通常具有“固定”迭代次数,并且for循环条件的唯一部分变为循环变量“i”。