Skip to main content
2 of 2
added 1542 characters in body
Arseni Mourzenko
  • 139.4k
  • 32
  • 359
  • 544

Use unit testing/TDD

If you really need to access sequences through a for loop, you can avoid the mistakes through unit testing, and especially test driven development.

Imagine you need to implement a method which takes the values which are superior to zero, in reverse order. What test cases could you think of?

  1. A sequence contains one value which is superior to zero.

    Actual: [5]. Expected: [5].

    The most straightforward implementation which satisfies the requirements consists of simply returning the source sequence to the caller.

  2. A sequence contains two values, both superior to zero.

    Actual: [5, 7]. Expected: [7, 5].

    Now, you cannot just return the sequence, but you should reverse it. Would you use a for (;;) loop, another language construct or a library method doesn't matter.

  3. A sequence contains three values, one being zero.

    Actual: [5, 0, 7]. Expected: [7, 5].

    Now you should change the code to filter the values. Again, this could be expressed through an if statement or a call to your favorite framework method.

  4. Depending on your algorithm (since this is white-box testing, the implementation matters), you may need to handle specifically the empty sequence [] → [] case, or maybe not. Or you may ensure that the edge case where all values are negative [-4, 0, -5, 0] → [] is handled correctly, or even that boundary negative values are: [6, 4, -1] → [4, 6]; [-1, 6, 4] → [4, 6]. In many cases, however, you'll only have the three tests described above: any additional test won't make you change your code, and so would be irrelevant.

Work at higher abstraction level

However, in many cases, you can avoid most of those errors by working at a higher abstraction level, using existent libraries/frameworks. Those libraries/frameworks make it possible to revert, sort, split and join the sequences, to insert or remove values in arrays or doubly-linked lists, etc.

Usually, foreach can be used instead of for, making boundary conditions checking irrelevant: the language does it for you. Some languages, such as Python, don't even have the for (;;) construct, but only for ... in ....

In C#, LINQ is particularly convenient when working with sequences.

var result = source.Skip(5).TakeWhile(c => c > 0); 

is much more readable and less error prone compared to its for variant:

for (int i = 5; i < source.Length; i++) { var value = source[i]; if (value <= 0) { break; } yield return value; } 
Arseni Mourzenko
  • 139.4k
  • 32
  • 359
  • 544