循环中的最后一个元素是否应该单独处理?

20

在审核时,有时会遇到这种循环:

i = begin
while ( i != end ) {    
   // ... do stuff
   if ( i == end-1 (the one-but-last element) ) {
      ... do other stuff
   }
   increment i
}

接下来我要问一个问题:你会写这个吗?

i = begin
mid = ( end - begin ) / 2 // (the middle element)
while ( i != end ) {    
   // ... do stuff
   if ( i > mid ) {
      ... do other stuff
   }
   increment i
}

在我看来,这违背了编写循环的意图:你循环是因为每个元素都有共同的操作。使用这种结构,对于某些元素,你会做一些不同的事情。因此,我的结论是,你需要为这些元素单独编写一个循环:

i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {    
   // ... do stuff
   increment i
}

while ( i != end ) {
   // ... do stuff
   // ... do other stuff
   increment i
}

现在我甚至在SO上看到了一个关于如何以好的方式编写if子句的question... 我感到很难过:这里有些事情不对劲。
我错了吗?如果是,那么在编码时提前意识到的特殊情况为什么要使循环体充满混乱呢?

这一切都很棒。我可以通过它改善我的个人工作风格。谢谢。 - peter_the_oak
你循环是因为每个元素都需要做一些共同的事情。这取决于这个“共同点”有多普遍。它可能只是所有元素都是汽车这个共同点。有些是蓝色的,有些是红色的。如果是蓝色的,则选择蓝色进行涂漆,如果是红色的,则选择红色进行涂漆。因此,在某个时候,循环中的决策是有意义的。但在某个时候,显然应该拆分循环。 - peter_the_oak
@peter_the_oak:我同意你根据循环中遇到的值做出决策,但并不是根据索引。 - xtofl
13个回答

23

我认为这个问题不应该通过一个原则来回答(例如,“在循环中,平等对待每个元素”)。相反,您可以考虑两个因素来评估实现的优劣:

  1. 运行效率 - 编译后的代码是否运行快速,或者用其他方式更快?
  2. 代码可维护性 - 别的开发人员能否轻松理解这里发生了什么?

如果通过在一个循环中完成所有操作可以使代码更快且更易读,请使用这种方法。如果这样做会更慢并且不易读,请尝试其他方式。

如果通过在一个循环中完成所有操作可以使代码更快但不易读,或者更慢但更易读,请找出在您特定情况下哪个因素更重要,然后决定如何循环(或不循环)。


11

我知道当人们尝试将数组元素连接成逗号分隔的字符串时,他们会看到这个:

for(i=0;i<elements.size;i++) {
   if (i>0) {
     string += ','
   }
   string += elements[i]
}

要么在那里加入if条件语句,要么你就必须在最后再次复制字符串+=行。

在这种情况下,显而易见的解决方案是

string = elements.join(',')

但是join方法在内部执行相同的循环。而且并不总是有一个能够实现你想要的功能的方法。


7

@xtofl,

我同意你的担忧。

我遇到过类似的问题很多次。

通常情况下,开发人员要么为第一个元素添加特殊处理,要么为最后一个元素添加特殊处理。

在大多数情况下,值得从 startIdx + 1endIdx - 1 元素开始循环,甚至将一个长循环拆分成多个较短的循环。

在极少数情况下,无法拆分循环。

在我看来,不常见的事情应尽可能在循环外处理。


6

我意识到,当我在for循环中添加特殊情况时,通常是过于聪明而不利于自己。


6
在您最后发布的代码片段中,您重复了“// .... do stuff.”的代码。
当您在不同的索引集上执行完全不同的操作时,保留2个循环是有意义的。
i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {    
   // ... do stuff
   increment i
}

while ( i != end ) {
   // ... do other stuff
   increment i
}

如果不是这种情况,你仍然需要保持一个循环。但事实仍然是您可以节省 ( end - begin ) / 2 的比较次数。因此,问题在于您想让代码看起来整洁还是想要节省一些 CPU 周期。决策权在你手中。


5

我认为你完全掌握了这个问题。大多数人会陷入在循环中包含条件分支的陷阱中,而他们可以将它们放在循环外面:这样做简单而且更快

例如:

if(items == null)
    return null;

StringBuilder result = new StringBuilder();
if(items.Length != 0)
{
    result.Append(items[0]); // Special case outside loop.
    for(int i = 1; i < items.Length; i++) // Note: we start at element one.
    {
        result.Append(";");
        result.Append(items[i]);
    }
}
return result.ToString();

你所描述的中间情况非常糟糕。想象一下,如果这段代码不断增长,并需要重构成多个方法。

除非你正在解析XML(微笑),否则循环应该尽可能简单明了。


3

我认为你对循环处理所有元素的意图是正确的。不幸的是,有时候会有特殊情况,这些情况应该通过if语句在循环结构内部处理。

如果有许多特殊情况,你可能应该考虑想出一些方法来将两个不同的元素集分别处理。


3

我更倾向于在循环中排除元素,并在循环外进行单独处理。

例如:让我们考虑 EOF 的情况

i = begin
while ( i != end -1 ) {    
   // ... do stuff for element from begn to second last element
   increment i
}

if(given_array(end -1) != ''){
   // do stuff for the EOF element in the array
}

2

当然,在循环中特别处理可以分离出来的内容是愚蠢的。我也不会复制 do_stuff;我要么将其放入函数中,要么放入宏中,这样就不需要复制粘贴代码。


2
哪个更好?
如果项目数量非常大,那么我会始终循环一次,特别是如果您要对每个项目执行某些操作。评估条件的成本可能小于循环两次。
哎呀,当然你不会循环两次...在这种情况下,两个循环更可取。然而,我认为主要考虑应该是性能。如果您可以通过对循环边界进行简单操作(一次)来分区工作,就没有必要在循环中(N次)遇到条件。

我从未提到循环两次 - 我暗示将循环分成两个部分。 - xtofl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接