I’m discussing this with a work colleague.
Say we want to sum the numbers from 0 to 9 skipping 5.
He prefers this:
int sum = 0;
for(int i = 0; i < 10; ++i)
{
if(i == 5)
{
continue;
}
sum += i;
}
I prefer this:
int sum = 0;
for(int i = 0; i < 10; ++i)
{
if(i != 5)
{
sum += i;
}
}
Any reason to prefer one over the other?
Cyclomatic complexity is the same in both cases, right?
5
As it stands right now (a single condition) I don’t see much reason to prefer one over the other, but I’d probably prefer the second as slightly simpler.
When, however, your set of conditions are more complex, some variant of the first can be preferable, often by a wide margin. Consider something like:
for (...) {
if (i != 5 && (i%2!=0) && i>99 && i<1000 && (i%17 !=3))
sum += i;
}
vs:
for (...) {
if (i==5)
continue;
if (i%2 == 0)
continue;
if (i<100 || i > 999)
continue;
if (i % 17 == 3)
continue;
process(i);
}
In this case, the latter seems (to me) more understandable and maintainable. It lets us group the items that are logically connected (the range, 100…1000) together, but keep those that aren’t so closely related separate so each can be analyzed on its own more easily. It also fits closely with a pattern I use in quite a few functions that basically works out to something like:
function(x, y, z) {
if (!preconditions(x, y, z))
// return an error or (preferably) throw an exception
// now do processing on x, y, z
}
Although in your case it’s a loop body instead of a function per se, you still end up with the same basic pattern: assure that some set of preconditions is met, and then if (and only if) it s, process the data.
1
I personally would go with the first example, given your task. The first example more closely matches your original problem statement. The 2nd requires a little bit more mental parsing to determine if it fulfills your problem statement.
Look at how you worded what is going on: “Sum the numbers from 0 to 9 skipping 5”. That is exactly what the first statement does. The 2nd doesn’t read quite the same way.
Ultimately, what is right is going to depend on the context of the greater problem you are going to solve. The less thinking you have to do to verify it’s doing what it is supposed to, the better.
6
My suggestion would be to extract that logic out into a method so that if you add more conditions or even change your condition it is easier to maintain in one place + it is more testable.
The reason I am suggesting that is because I am assuming this is not the actual problem but it is a more simplified version of your problem that you have narrowed it down to demo it here.
If, however, my assumptions are wrong then it would still be a good idea to extract it out because it makes it a lot more readable and understandable (mostly because of the method name and the variable names) – so that when you come back to it after a while it is still easy to grasp and know what you meant when you wrote it the first time.
private void sumItUp()
{
int sum = 0;
for (int numberToAddToSum = 0, i = 0; i < 10; numberToAddToSum = SkipIfNextOneIsFive(i), i++)
{
sum += numberToAddToSum;
}
}
private int SkipIfNextOneIsFive(int i) { return i + 1 == 5 ? 0 : ++i; }
Both of those code fragments are doing way too much work. You can replace them with int sum = 40;
(in fact, if you gave those code fragments to a compiler, that’s basically what the compiler would do). There’s a famous trick for summing up the first n integers I used to do it in my head, but if you didn’t know the trick and had no calculator handy, you could work it out with pen and paper in about the same amount of time it would take to type up that code. So the problem, as stated, isn’t something you should be writing a program for at all.
Of course, you aren’t asking about only that one example problem. What problem are you actually trying to solve? The question overspecifies a bit, so I’ll just guess that you’re either (a) going for something that sums up all the consecutive integers over a range except for a specified one, or (b) you’re going for summing up all the consecutive integers in a range, except for a range in the middle.
If you want (a), then you can just subtract the specified number from the range. If you want (b), then you can break the large range into a bottom half and a top half and deal with each separately. So, in either case, we want something that can sum up a range of integers.
If you know the famous formula for summing up the first n integers (n*(n+1)/2), and you know how it works, it isn’t hard to come up with a formula for a range ((top-bottom+1)*(top+bottom)/2). If not, there’s the obvious and straightforward way of doing it with for loops.
If we write a function that does that (with either method), we can rewrite the original problem as sum_range(0,9) - 5
or sum_range(0,4) + sum_range(6,9)
. It turns out we didn’t need any ifs or continues, and, depending on how we wrote it, maybe no for loop either.
If your question is really about which keyword to use when nested inside a construct using a specific keyword which is nested inside another construct using another specific keyword, then I don’t think your question really has an answer, and that trying to figure out how to program by looking at which keywords nest inside which other keywords is never going to be useful.
(For those interested, the formula works like this: say we’re trying to add up all the integers from 1 to 100. 1+100 = 101, 2+99 = 101, 3+98 = 101, and so on, so we could just multiply 101 by the number of pairs, and the number of pairs is the number of numbers being added / 2. In this case, top = 100, bottom = 1, top+bottom = 101, and the number of pairs is (top-bottom+1)/2 = n/2 = 50.)
Technically he will have one more line of execution for the continue
statement. Otherwise, the code is identical. It is O(n) in both cases.
2
Any reason to prefer one over the other?
At some places and in some languages, the use of continue
and break
are frowned upon as glorified gotos. They make it harder to read the code since you need to stop and then find the end of the loop you’re in rather than noting the condition and reading along.
The second version will work in pretty much any language too. continue
isn’t always available.
1
A for loops natural tendency is to “continue” and as others have pointed out, it’s an extra line of code.
In 15 years of writing code I have MAYBE used continue (in any language) 3 times; and none of that was recent. It’s simply not a necessary construct as you quickly realize.
3