48

Sometimes I need for loops which needs a break like this:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

I feel uncomfortable with writing

if(condition){
    break;
}

because it consumes 3 lines of code. And I found the loop can be rewritten as:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

So my question is, is it good practice to move the condition into the condition field to reduce lines of codes if possible?

doppelgreener
  • 1,274
  • 17
  • 26
ocomfd
  • 5,652
  • 8
  • 29
  • 37
  • 7
    Depends on what the `condition` in particular is. – Bergi Feb 27 '18 at 13:44
  • 4
    There is a reason why misra forbid use of "break" and "continue" within loops. See also this: https://stackoverflow.com/q/3922599/476681 – BЈовић Feb 27 '18 at 14:26
  • 35
    I think the number of lines itself is not the real issue. It could be written on one line. if(condition) break; The issue in my opinion is readability. I personally dislike breaking a loop other than using the loop condition. – Joe Feb 27 '18 at 15:59
  • 1
    The only time I break out is if a lot of processing in the loop can be skipped and I dont want all that processing contained in an if block as it could get hard to read. For example if (!Condition)... do rest of loop code. – Joe Feb 27 '18 at 16:02
  • "*If it makes you happy, it can't be that bad....*" - Sheryl Crow –  Feb 27 '18 at 16:14
  • 97
    *because it consumes 3 lines of code* A very, very, very bad reason to dislike any style. IMO "consum[ing] lines of code" is somewhere between irrelevant and a good thing. Trying to stuff too much logic into too few lines results in unreadable code and hard-to-find bugs. – Andrew Henle Feb 27 '18 at 16:40
  • 2
    If you are iterating over an array or collection, you _also_ have the option of filtering the elements and simply using a `for_each` construct. This puts the smarts in your iterator rather than in your loop. It's basically how C# LINQ can limit breaks as well as Java Stream API. – Berin Loritsch Feb 27 '18 at 17:45
  • 2
    Heck if you want to reduce space you can put it all in the iterator expression (separated by commas): `for (int i=0; i – John Wu Feb 28 '18 at 02:45
  • "Should I do X if possible"? The answer is NO whatever X is. You should do X if it makes your code better. – gnasher729 Feb 28 '18 at 12:30
  • 2
    Ask yourself this: would you care about the break if your language had a `for` construct that let you write `for element in array { ...}` instead of `for (i=0; i < array.length; i++) { element = array[i]; ... }`? I would consider separating the iteration logic from the early-stopping logic a plus, not something to avoid. – chepner Feb 28 '18 at 17:53
  • 3
    Possibly unpopular opinion: `for(int i=0;i – Jules Feb 28 '18 at 22:57
  • 1
    If we still used teletypes and line editors, then caring about the number of lines *might* be relevant. – vsz Mar 01 '18 at 05:34
  • 3
    @BЈовић: I [wouldn't use MISRA as a positive example](https://en.wikipedia.org/wiki/MISRA_C#Criticism) – TMN Mar 01 '18 at 14:29
  • 1
    @BЈовић, I wish I could +5 that. Found a great study at that link that shows it to be nearly useless and possibly harmful; something I have learned the hard way after being forced to use it for the last few years. – psusi Mar 01 '18 at 21:40
  • @BЈовић - If you read the answers on that question, the top rated ones all discredit it. – Paddy Mar 02 '18 at 11:03
  • I suspect that this is a very situational question, and the answer may be different for any given loop. Generally, my advice is that the condition should be where it can be evaluated. If it depends on data determined during each iteration (e.g., a "Should I process this one?" user prompt), keep the condition in the loop. If it can be evaluated between iterations (e.g., a "successfully processed this many" counter, or a "Continue?" user prompt), then putting it in the condition can indicate that it's part of the loop logic (but may make it harder for others to notice). And so on... – Justin Time - Reinstate Monica Mar 04 '18 at 13:54
  • @Paddy Yes, lots of stupid people decided to vote on the most stupid answer. No wonder we experience some critical bugs so often. Good that you point it out, so I can -1 it – BЈовић Mar 05 '18 at 10:23

16 Answers16

153

Those two examples you gave are not functionally equivalent. In the original, the condition test is done after the "some other code" section, whereas in the modified version, it is done first, at the start of the loop body.

Code should never be rewritten with the sole purpose of reducing number of lines. Obviously it's a nice bonus when it works out that way, but it should never be done at the expense of readability or correctness.

Pete
  • 3,181
  • 1
  • 12
  • 18
  • 5
    I understand your sentiment, but I've maintained enough legacy code where the loop is several hundred lines long and there are multiple conditional breaks. It makes debugging very difficult when you expect code to get to the conditional break you expect but prior code exercised it's conditional break first. How do you handle that code? In short examples like the above, it's easy to forget about that kind of all too common scenario. – Berin Loritsch Feb 27 '18 at 13:49
  • 84
    @BerinLoritsch: The correct way to deal with that is to not have loops that are several hundred lines long! – Chris Feb 27 '18 at 13:58
  • 1
    @Chris but since it's legacy code it's likely not his and rewritten the whole loop might not be an option. – Walfrat Feb 27 '18 at 14:27
  • 27
    @Walfrat: If rewriting it isn't an option then there isn't really a question any more. Having said that you can usually pretty safely use an "Extract Method" type refactor to move blocks of code around which should make your main method loop shorter and hopefully make the logic flow more obvious. Its really a case by case thing. I'll stand by my general rule of keeping methods short and definitely keep nexted loop logic short but I don't want to try to say anything more than that in a general sense though... – Chris Feb 27 '18 at 14:35
  • If I need a condition to be done after the loop I use a do while. – Joe Feb 27 '18 at 16:01
  • `Code should never be rewritten with the sole purpose of reducing number of lines.` Print this out and hang it over your monitor if you have to, but this is so important. We have good practices to write good code. We don't write code so we can use good practices. – corsiKa Feb 27 '18 at 16:08
  • 1
    I think that this answer would be better if it explored 1. situations where the change does not change the functionality, and 2. reasons aside from reducing LoC for possibly making such a change. This answer is absolutely right, and both points are well worth pointing out in case the querent missed or misunderstood either, but fundamentally there is *still a question here* that isn’t being addressed. – KRyan Feb 27 '18 at 16:34
  • 1
    *Obviously it's a nice bonus* I don't think it's a bonus in any way. What's inherently *better* about doing something in three lines of code instead of ten? Readability, maintainability and *maybe* performance are all that matter. If the more readable/maintainable and perhaps performant code is shorter, it's shorter. If it's longer, it's longer. – Andrew Henle Feb 27 '18 at 16:45
  • 1
    Note that the difference you describe only matters if the condition can be false *before* the loop is entered the first time. That is probably the case, though, if someone wrote the code this way already. – jpmc26 Feb 27 '18 at 16:48
  • 1
    @Chris, in principle I agree with you, but if you've never had to maintain legacy code with enough complexity to make you question every change, it falls apart in practice. For new code, I agree. However there _are_ things you _can_ do to help with readability and debugging even if you can't do a full rewrite of the code block. – Berin Loritsch Feb 27 '18 at 17:34
  • 2
    @BerinLoritsch: I have been there, don't worry. Horrible legacy code is just immune from general rules for the most part. Anything you can say such as "shorter methods" or "only return/break in one location" may or may not be able to be implemented on this legacy code. More often than not I find that this sort of code is just not touched at all if it is critical and if its not it gets rewritten to be better with the understanding that the rewrite may introduce new bugs that were not there before but at least they will be easier to debug. – Chris Feb 27 '18 at 17:47
  • 4
    @AndrewHenle brevity is readability, at least in the sense that increasing verbosity *always* reduces readability and maintainability. Reducing LOC is achieved by increasing density and raising the abstraction level, and is very tightly correlated with maintainability as attested in other Q/A pairs on this very site. – Alex Celeste Feb 28 '18 at 09:08
  • The first thing maintaining a several hundred line loop is finding all the break statements in it. My editor has a "search" command to do this. You should check yours. – gnasher729 Feb 28 '18 at 10:05
  • 5
    @Joe a Do-While construct is so rare that it is prone to trip developers that need to maintain that code later on. A few devs never actually saw them! I'm not sure if a do-while would improve readability at all - if anything, it would _increase_ the difficulty to understand the code. Just as an example - my current codebase is around 15 years old and has around 2 million LOC. Around fifty developers touched it already. It has exactly zero do-while loops! – T. Sar Feb 28 '18 at 12:18
  • 2
    When I write programs, I always prefer brevity to correctness. That's why all my source files are empty. No checkstyle warnings! – Michael Feb 28 '18 at 16:04
  • 3
    @T.Sar I've been in this industry for over 20 years since uni, and I've never found any experienced engineer who didn't know what a do-while loop was. If I find during reviewing/mentoring that they don't know, they're going on a standard beginner's coding course and be treated like any other novice coder, with more-closely supervised access to production code. If they claim to be more senior and don't know, they don't make it past interview with me or anyone I've ever worked with, and if they did then I'd ensure they were fired before the end of their probation period. – Graham Mar 01 '18 at 14:52
  • 1
    @Graham I'm not saying that People _don't know_ what do-while loops do. I'm saying that they _trip people over_. They are one of those code constructs that increase the chance of you messing something up, like magic strings, for-loops with side effects or static globals. Knowing what something is doesn't automatically protect you against messing it up. It isn't because I know what a goto is that I _should_ use it when a more usual, clear way of coding is possible. – T. Sar Mar 01 '18 at 15:25
  • 1
    @T.Sar I'll have to agree to disagree then, because I don't believe they should trip up any competent coder; and by definition, anyone who does get tripped up by them needs to be supervised and mentored as any novice would be, and not be put in positions where their competence is assumed. Of course there are code constructs which are poorly understood, but I would not agree that this is one of them. Coding standards such as MISRA don't restrict its use, suggesting they don't think it's a problem either. – Graham Mar 02 '18 at 12:19
  • @Graham You never were tripped by any code that could have written in a more clear way? Do you really think that being tripped over by an _unccomon coding construct_ (which do-while is, without any doubt), reason to supervise them as novices? Disagree with me all you want - the frequency of use of the do-while construct speaks for itself. Unless you _never ever gets a bug on your code_, you can't really judge competence that way. Good developers aren't the ones that never get tripped by weird code, they are those that don't write weird code in the first place. – T. Sar Mar 02 '18 at 13:19
  • 1
    @T.Sar It seems we disagree about whether "do-while" is *unclear* and *weird*. I don't think so, and MISRA doesn't think so either which speaks for itself. I'd agree that it's less common, sure, but not *uncommon*. If your code needs to run at least once and maybe more times, "do-while" is simply the right construct for the job and others are sub-optimal choices, and there is nothing unclear about it. Of course I've been tripped up by unclear code, but never by "do-while" instead of "while", and I'd be concerned about the competence of someone who was *and thought it was the code's fault*. – Graham Mar 02 '18 at 16:22
  • @Graham I'm pretty sure if I went with the same type of reasoning you used back there, some of your "unclear code" would look like bread and butter to me and I would then be allowed to put you in the "novice" bucket. See the issue there? While do-while may be common in C or some older languages, they are a rarity nowadays. You rarely see them in a C# or Java codebase, for example, and it isn't uncommon to find developers that never found one of those in the wild. Personally, I never saw it used correctly outside college, and it always ended up refectored to a classical while. – T. Sar Mar 02 '18 at 16:52
  • 1
    @Graham "In my experience, the do-statement is a source of errors and confusion. The reason is that its body is always executed once before the condition is evaluated. However, for the body to work correctly, something very much like the condition must hold even the first time through. More often than I would have guessed, I have found that condition not to hold as expected either when the program was first written and tested, or later after the code preceding it has been modified. I also prefer the condition "up front where I can see it." Consequently, I tend to avoid do-statements." – T. Sar Mar 02 '18 at 16:58
  • @Graham That's a quote from Bjarne Stroustrup (C++ creator). I'll leave it at that. – T. Sar Mar 02 '18 at 16:58
51

I don't buy the argument that "it consumes 3 lines of code" and thus is bad. After all, you could just write it as:

if (condition) break;

and just consume one line.

If the if appears half way through the loop, it of course has to exist as a separate test. However, if this test appears at the end of the block, you have created a loop that has a continue test at both ends of the loop, possibly adding to the code's complexity. Be aware though, that sometimes the if (condition) test may only make sense after the block has executed.

But assuming that's not the case, by adopting the other approach:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

the exit conditions are kept together, which can simplify things (especially if "some other code" is long).

There's no right answer here of course. So be aware of the idioms of the language, what your team's coding conventions are etc. But on balance, I'd adopt the single test approach when it makes functional sense to do so.

David Arno
  • 38,972
  • 9
  • 88
  • 121
  • 14
    @jpmc26, Better? No. Valid alternative style? Of course. – David Arno Feb 27 '18 at 16:48
  • 6
    Better as in harder to mess up when the code is edited later, and not suffering from any notable disadvantages. – jpmc26 Feb 27 '18 at 16:50
  • 4
    Keeping them together because of "loop contents might be long" seems to be a weak argument. When your for content is hard to read then you have other problems than saving the two lines of code. – BlueWizard Feb 27 '18 at 18:37
  • @BlueWizard, absolutely agree, which is why I added it as an “especially if...” addendum. – David Arno Feb 27 '18 at 18:49
  • 1
    @jpmc26 If you're going for harder to mess up when later edited, maybe you should be using `if(condition){break;}else{}`? That way it won't break `if(...) else ...` blocks – phflack Feb 27 '18 at 19:11
  • 12
    @jpmc26 Disagree. Braces are superfluous clutter in the shortest, one-liner conditionals as per this answer. Without is more elegant and easier on the eye. They're like a ternary conditional operator. I have never forgotten to add the braces while breaking them out into bigger statement blocks; I'm already adding newlines after all. – benxyzzy Feb 27 '18 at 19:35
  • 3
    It's all a style preference, but if one makes the argument that it's safer, I would disagree with that reasoning. I myself prefer without braces and on the next line, but that doesn't mean it's any more or less valid than other styles – phflack Feb 27 '18 at 19:59
  • 3
    safer = using the style your team uses. If your team writes everything as base64 encoded haiku then thats the safest way to go. – BlueWizard Feb 28 '18 at 07:03
  • ( in reality though, one might consider the whole developer community of that language/framework as your "team". Simply because people come and go. ) – BlueWizard Feb 28 '18 at 07:05
  • 1
    @BlueWizard if the team used base 64 encoded haiku, the way I’d go is out the door, as fast as possible! :) – David Arno Feb 28 '18 at 07:07
  • The three line vs. one line argument would be avoided if you had statements "break if ;" and "continue if ";. After all you write break and continue on one line. – gnasher729 Feb 28 '18 at 12:32
  • 1
    Always including braces for if is "safer" because it prevents a future mistake if you decide to later add an additional statement into the if code block. The braces are already there, so you won't accidentally leave them out. You won't accidentally wind up with `if (condition) line1(); line2();`. – trlkly Feb 28 '18 at 14:50
  • *I have never forgotten to add the braces while breaking them out into bigger statement blocks* - Michael Bolton, the day before [his error exposed his "rounding fractions of a penny" theft](https://youtu.be/qLk81XnkGUM) – radarbob Mar 02 '18 at 15:44
49

This sort of question has sparked debate almost as long as programming as been going. To throw my hat into the ring, I'd go for the version with the break condition and here's why (for this specific case):

The for loop is just there to specify the iterating values in the loop. Coding the breaking condition within that just muddies the logic IMO.

Having a separate break makes it crystal clear that during normal processing, the values are as specified in the for loop and processing should continue except where the condition comes in.

As a bit of background, I started off coding a break, then put the condition in the for loop for years (under the direction of an exceptional programmer) and then went back to the break style because it just felt far cleaner (and was the prevailing style in the various code tomes I was consuming).

It is another of those holy wars at the end of the day - some say you should never break out of a loop artificially, otherwise it isn't a real loop. Do whatever you feel is readable (both for yourself and others). If reducing keystrokes is really your thing, go code golfing at the weekend - beer optional.

Robbie Dee
  • 9,717
  • 2
  • 23
  • 53
  • 3
    If the condition has a good name, such as "found" (e.g. you are looking through the array for something), then it is a fine idea to put it in the head of the for loop. – user949300 Feb 27 '18 at 20:52
  • 1
    I was told that `for` loops are used when the number of iterations are known (e.g when there's no breaking condition but the end of the array/list) and `while` when are not. This seems to be the case for a `while` loop. If readability is the concern a `idx+=1` inside the loop is much cleaner to read than a `if/else` block – Laiv Feb 28 '18 at 06:34
  • You can't just put the break condition in the loop if there is code following it, so at least you would have to write "if (condition) { found = true; continue; } – gnasher729 Feb 28 '18 at 10:07
  • We are so used to `for(int i=0; i – Ralf Kleberhoff Mar 01 '18 at 08:51
  • @RalfKleberhoff Indeed. I've seen many a new developer express surprise that the tripart statement expressions are all optional. I don't even recall the last time I saw `for(;;)`... – Robbie Dee Mar 01 '18 at 13:03
43

for loops are for iteration over something1 - they aren't just lol-let's-pull-some-random-stuff-from-the-body-and-put-them-in-the-loop-header - the three expressions have very specific meanings:

  • The first one is for initializing the iterator. It can be an index, a pointer, an iteration object or whatever - as long as it is used for iteration.
  • The second is for checking if we reached the end.
  • The third is for advancing the iterator.

The idea is to separate the iteration handling (how to iterate) from the logic inside the loop (what to do in each iteration). Many languages usually have a for-each loop that relieves you from the details of the iteration, but even if your language doesn't have that construct, or if it can't be used in your scenario - you should still limit the loop header to iteration handling.

So, you need to ask yourself - is your condition about the iteration handling or about the logic inside the loop? Chances are, it's about the logic inside the loop - so it should be checked inside the loop rather than in the header.

1As opposed to other loops, that are "waiting" for something to happen. A for/foreach loop should have the concept of a "list" of items to iterate on - even if that list is lazy or infinite or abstract.

Idan Arye
  • 12,032
  • 31
  • 40
  • 5
    This was my first thought when I read the question. Disappointed I had to scroll through half a dozen answers to see someone say it – Nemo Feb 27 '18 at 17:46
  • 4
    Umm... *all* loops are for iteration - that's what the word "iteration" means :-). I assume you mean something like "iterating over a collection", or "iterating using an iterator"? – psmears Feb 27 '18 at 17:57
  • 3
    @psmears OK, maybe I chose the wrong word - English is not my native tongue, and when I think of iteration I think of "iteration over something". I'll fix the answer. – Idan Arye Feb 27 '18 at 20:15
  • An intermediate case is where the condition is something like `someFunction(array[i])`. Now both parts of the condition are about the thing you're iterating over, and it would make sense to combine them with `&&` in the loop header. – Barmar Feb 28 '18 at 23:01
  • I respect your position, but I disagree. I think `for` loops at heart are counters, not loops over a list, and this is supported by the syntax of `for` in earlier languages (these languages often had a `for i = 1 to 10 step 2` syntax, and the `step` command - even allowing an initial value that is not the index of the first element - hints at a numerical context, not a list context). The _only_ use case that I think supports `for` loops as only iterating over a list is parallelising, and that requires explicit syntax now anyway in order to not break code doing, for example, a sort. – Logan Pickup Mar 01 '18 at 14:54
  • @LoganPickup That's why I wrote "even if that list is lazy or infinite or abstract". The loop `for i = 1 to 10 step 2` is iterating over the list of odd numbers between 1 and 10 - it doesn't have to allocate an actual data structure in memory that will contain that list. – Idan Arye Mar 01 '18 at 15:47
  • @LoganPickup In a large number of cases, that `i` is an index, so it's still conceptually iterating over the collection being indexed. This has become subsumed by `foreach` loops in many modern languages, though, but it's not uncommon to use the older style. – Barmar Mar 01 '18 at 21:56
  • The "older style" is legacy - more expressive languages sometimes replace it with a for-each style even when `i` is not an index. In Python, for example, you'll use `for i in range(1, 10, 2):`, treating the numbers you'll be iteration over as a lazy list (generator actually, but that's the concept of a lazy list) – Idan Arye Mar 02 '18 at 01:03
  • @IdanArye My point is that as soon as you step outside the concept of iterating over every element in a predefined collection, your loop is creating an arbitrary list of indexes, and the only difference between the lists (1, 2, 3, 4), (2, 4, 6, 8) and (1, 2, maybe 3, 7) is that some are easier to precompute. I understand that some sets of indexes may be abstract, but as soon as you allow that, you allow any arbitrary condition in the for loop's test. If we only permit the most pure form of `for` loop, then it should only allow a single parameter - the number of items. – Logan Pickup Mar 02 '18 at 01:40
  • It's not about whether or not the collection is predefined or easy to precompute. It's about whether or not you want to separate the iteration from what you do with these items. – Idan Arye Mar 02 '18 at 13:53
8

I recommend using the version with if (condition). It's more readable and easier to debug. Writing 3 extra lines of code won't break your fingers, but will make it easier for the next person to understand your code.

Robbie Dee
  • 9,717
  • 2
  • 23
  • 53
Aleksander
  • 368
  • 1
  • 8
  • 1
    Only if (like has been commented) the loop block doesn't has hundreds lines of code and the logic within is not rocket science. I think your argument is ok, but I miss something like `naming things` properly in order to understand what's going on and when the breaking condition is met. – Laiv Feb 28 '18 at 06:47
  • 2
    @Laiv If the loop block has hundreds lines of code within it, then the are bigger problems than the question where to write the break condition. – Aleksander Feb 28 '18 at 09:33
  • That's why I suggested contextualise the answer, because is not always true. – Laiv Feb 28 '18 at 09:36
8

If you are iterating over a collection where certain elements should be skipped, then I recommend a new option:

  • Filter your collection before iterating

Both Java and C# make this relatively trivial to do. I wouldn't be surprised if C++ had a way of making a fancy iterator to skip certain elements that don't apply. But this is only really an option if your language of choice supports it, and the reason your are using break is because there are conditions on whether you process an element or not.

Otherwise there is a very good reason to make your condition a part of the for loop--assuming it's initial evaluation is correct.

  • It's easier to see when a loop ends early

I've worked on code where your for loop takes a few hundred lines with several conditionals and some complex math going on in there. The problems you run into with this are:

  • break commands burried in the logic are hard to find and sometimes surprising
  • Debugging requires stepping through each iteration of the loop to truly understand what's going on
  • A lot of the code does not have easily reversable refactorings available or unit tests to help with functional equivalence after a rewrite

In that situation I recommend the following guidelines in order of preference:

  • Filter the collection to eliminate the need for a break if possible
  • Make the conditional part of the for loop conditions
  • Place the conditionals with the break at the top of the loop if at all possible
  • Add a multi-line comment drawing attention to the break, and why it is there

These guidelines are always subject to the correctness of your code. Choose the option that can best represent the intent and improve the clarity of your code.

Berin Loritsch
  • 45,784
  • 7
  • 87
  • 160
  • 1
    Most of this answer is fine. But... I can see how filtering a collection could eliminate the need for `continue` statements, but I don't see how they help much with `break`. – user949300 Feb 27 '18 at 20:55
  • 1
    @user949300 A `takeWhile` filter can replace `break` statements. If you have one - Java for example only gets them at Jave 9 (not that it's _that_ hard to implement it yourself) – Idan Arye Feb 28 '18 at 01:35
  • 1
    But in Java you still can filter before the iteration. Using streams (1.8 or later) or using Apache Collections (1.7 or earlier). – Laiv Feb 28 '18 at 06:44
  • @IdanArye - there are add-on libraries that add such facilities to the Java streams API (e.g. [JOO-lambda](https://www.jooq.org/products/jOO%CE%BB/javadoc/0.9.9/org/jooq/lambda/Seq.html#limitUntil-java.util.function.Predicate-)) – Jules Feb 28 '18 at 17:41
  • @IdanArye never heard of a takeWhile before your comment. Explicitly linking the filter to an ordering strikes me as unusual and hacky, since in a "normal" filter every element gets to return true or false, independent of what comes before or after. In this way you can run things in parallel. – user949300 Mar 02 '18 at 19:55
  • @user949300 I'd argue that if your logic dictates that you need to **only** process the elements that appear before an element that fulfills a certain condition, ordering **does** matter to begin with. – Idan Arye Mar 02 '18 at 21:03
  • Clearly the ordering does matter, agree there. I'm just quibbling on whether a "filter" is the correct term and technique. – user949300 Mar 02 '18 at 23:17
8

I strongly recommend to take the approach of least surprise unless you gain a significant benefit doing otherwise.

People don't read every letter when reading a word, and they don't read every word when reading a book - if they're adept at reading, they look at the outlines of words and sentences and let their brain fill in the rest.

So chances are the occasional developer will assume this is just a standard for loop and not even look at it:

for(int i=0;i<array.length&&!condition;i++)

If you want to use that style regardless, I recommend changing the parts for(int i=0;i< and ;i++) that tell the reader's brain that this is a standard for loop.


Another reason to go with if-break is that you cannot always use your approach with the for-condition. You have to use if-break when the break condition is too complex to hide within a for statement, or relies on variables that are only accessible inside the for loop.

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

So if you decide to go with if-break, you're covered. But if you decide to go with for-conditions, you have to use a mix of if-break and for-conditions. To be consistent, you will have to move the conditions back and forth between the for-conditions and the if-break whenever the conditions change.

Peter
  • 3,718
  • 1
  • 12
  • 20
4

Your transformation is assuming that whatever condition is evaluates to true as you enter the loop. We can't comment on the correctness of that in this instance because it isn't defined.

Having succeed in "reducing lines of code" here, you may then go on to look at

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

and apply the same transformation, arriving at

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

Which is an invalid transformation, as you now unconditionally do further code where you previously didn't.

A much safer transformation scheme is to extract some other code and evaluating condition to a separate function

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}
Caleth
  • 10,519
  • 2
  • 23
  • 35
4

TL;DR Do whatever reads best in your particular circumstance

Let's take this example:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

In this case we don't want any of the code in the for loop to execute if something is true, so moving the test of something into the condition field is reasonable.

for ( int i = 1; i < array.length && !something; i++ )

Then again, depending on if something can be set to true before the loop, but not within it, this might could offer more clarity:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

Now imagine this:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

We're sending a very clear message there. If something becomes true while processing the array then abandon the whole operation at this point. In order to move the check into the condition field you need to do:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

Arguably, the "message" has become muddied, and we are checking the failure condition in two places - what if the failure condition changes and we forget one of those places?

There are of course many more different shapes a for loop and condition could be, all with their own nuances.

Write the code so that it reads well (this is obviously somewhat subjective) and concisely. Outside of a draconian set of coding guidelines all "rules" have exceptions. Write what you feel expresses the decision making best, and minimize the chances for future mistakes.

2

While it may be appealing because you see all conditions in the loop header and break may be confusing inside large blocks, a for loop is a pattern where most programmers expect looping over some range.

Especially since you do so, adding an additional break condition can cause confusion and it is harder to see if it is functionally equivalent.

Often a good alternative is to use a while or do {} while loop which checks both conditions, assuming your array has at least one element.

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

Using a loop which is only checking a condition and not running code from the loop header you make very clear when the condition is checked and what's the actual condition and what's code with side effects.

allo
  • 164
  • 7
  • Despite this question having some good answers already, I wanted to specifically upvote this because it makes an important point: to me, having anything other than a simple bound checking in the for-loop (`for(...; i <= n; ...)`) actually makes the code _harder_ to read because I have to stop and think about what is happening here and might miss the condition altogether on the first pass because I don't expect it to be there. For me, a `for` loop implies that you are looping over some range, if there is a special exit condition it should be made explicit with an `if`, or you can use a `while`. – CompuChip Feb 28 '18 at 09:09
1

Few reasons in my opinion that says, you should not.

  1. This reduces the readability.
  2. The output may not be same. However, it can be done with a do...while loop (not while) as the condition is checked after some code execution.

But on top of that, consider this,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

Now, can you really achieve it by adding these conditions into that for condition?

1

This is bad, as code is meant to be read by humans - non-idiomatic for loops are often confusing to read, which means the bugs are more likely to be hiding here, but the original code may have been possible to improve while keeping it both short and readable.

If what you want is to find a value in an array (the provided code sample is somewhat generic, but it may as well be this pattern), you can just explicitly try to use a function provided by a programming language specifically for that purpose. For example (pseudo-code, as a programming language was not specified, the solutions vary depending on a particular language).

array.find(item -> item.valid)

This should noticeably shorten the solution while simplifying it as your code says specifically what you need.

Konrad Borowski
  • 308
  • 3
  • 10
0

Yes, but your syntax is unconventional and hence bad(tm)

Hopefully your language supports something like

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}
Ewan
  • 70,664
  • 5
  • 76
  • 161
  • Shouldn't that be **while (!condition)**? But anyhoo, yes - this is preferable to mixing the for and exit condition. – Robbie Dee Feb 27 '18 at 11:15
  • 2
    maybe I should have used a different word for 'condition' I didn't mean it literally to mean the exact same condition in the example – Ewan Feb 27 '18 at 11:18
  • 1
    Not only is the for loop not even remotely unconventional, there is absolutely no reason a while loop of this form is better than an equivalent for loop. In fact most people would say it is worse, such as the authors of the [CPP Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es72-prefer-a-for-statement-to-a-while-statement-when-there-is-an-obvious-loop-variable) – Sean Burton Feb 27 '18 at 17:34
  • 2
    Some people just hate creative alternatives. Or looking at a problem in a different way than the one set in their heads. Or more generally, smart asses. I feel your pain bro! – Martin Maat Feb 27 '18 at 18:33
  • 1
    @sean sorry dude, I know people hate to be told they are wrong, but I refer you to es.77 – Ewan Feb 27 '18 at 22:35
  • 1
    @martin bros4life! *gang sign* I knew there were others out there who could see the **TRUTH** – Ewan Feb 27 '18 at 22:37
  • A for loop takes very nicely care of initialisation, loop condition, switching to the next iterations. Which is _very_ useful when things are not trivial, like break and continue in interesting places. – gnasher729 Feb 28 '18 at 10:12
  • @gnasher729 yes, its a neat solution, but hard to read if you start loading up conditions. Even the OP made a mistake with the execution order in the example. – Ewan Feb 28 '18 at 10:44
  • 1
    @Ewan Read the “note” at the end: ES.77 doesn’t supersede ES.72 (but see ES.73). In general it’s pretty widely accepted that an unbounded loop is worse than a bounded loop (such as a `for` loop) since it has fewer explicit invariants and makes it harder to prove that it’s finite. That said, C-style `for` loops don’t make this guarantee obvious anyway, since they’re essentially glorified unbounded loops. – Konrad Rudolph Mar 01 '18 at 13:15
  • 1
    @KonradRudolph I dont think the rules confict at all. remember we are not talking about replacing a simple for loop with a while, but while as alternative to a for loop with multiple conditions ie for(i=0;i<10 && cond1 || cond2 && !cond3;i++) in such a case you no longer have the obviousness of looping over an array that es.72 is recommending. – Ewan Mar 01 '18 at 13:26
  • 1
    @Ewan You’re still replacing an obviously bounded loop with a potentially unbounded loop, which should be a huge warning sign. Adding further `break`s in a loop body isn’t ideal but does’t change its boundedness guarantees. Converting it to a `while` loop, however, does. tl;dr Don’t underestimate boundedness guarantees, they’re important for correctness verifiability. – Konrad Rudolph Mar 01 '18 at 13:28
  • @KonradRudolph the breaks don't affect the upper bound true, but they are not good! (es.77) you can get around the unboundedness of the while with a reader.next() style of incrementation instead of i++ to make it even better – Ewan Mar 01 '18 at 13:33
  • 2
    This `while` emphasizes the monster in the code - the exception otherwise hidden in routine/mundane code. The business logic is front and center, the looping mechanics is subsumed. It avoids the "wait a minute..." reaction to the `for` loop alternative. This answer *fixes the problem.* absolutely up vote. – radarbob Mar 02 '18 at 15:28
0

I think removing the break can be a good idea, but not to save lines of code.

The advantage of writing it without break is that the post-condition of the loop is obvious and explicit. When you finish the loop and execute the next line of the program, your loop condition i < array.length && !condition must have been false. Therefore, either i was greater than or equal to your array length, or condition is true.

In the version with break, there’s a hidden gotcha. The loop condition says that the loop terminates when you’ve run some other code for each valid array index, but there is in fact a break statement that could terminate the loop before that, and you won’t see it unless you review the loop code very carefully. And automated static analyzers, including optimizing compilers, could have trouble deducing what the post-conditions of the loop are, too.

This was the original reason Edgar Dijkstra wrote “Goto Considered Harmful.” It wasn’t a matter of style: breaking out of a loop makes it hard to formally reason about the state of the program. I have written plenty of break; statements in my life, and once as an adult, I even wrote a goto (to break out of multiple levels of a performance-critical inner loop and then continue with another branch of the search tree). However, I am far more likely to write a conditional return statement from a loop (which never runs the code after the loop and therefore can’t muck up its post-conditions) than a break.

Postscript

In fact, refactoring the code to give the same behavior might actually need more lines of code. Your refactoring could be valid for particular some other code or if we can prove that condition will start off false. But your example is equivalent in the general case to:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

If some other code isn’t a one-liner, you might then move it to a function so as not to repeat yourself, and then you’ve very nearly refactored your loop into a tail-recursive function.

I recognize this was not the main point of your question, though, and you were keeping it simple.

Davislor
  • 1,513
  • 10
  • 13
  • The problem is not that the break makes it hard to argue about the code, the problem is that break at random places makes the code to complicated things. If that is actually needed, then it's not hard to argue because of the break, but because the code needs to do complicated things. – gnasher729 Feb 28 '18 at 10:10
  • Here’s why I disagree: if you know there’s no `break` statement, then you know what the loop condition is and that the loop stops when that condition is false. If there’s a `break`? Then there are multiple ways the loop could terminate, and in the general case, figuring out when one of them could halt the loop is literally solving the Halting Problem. In practice, my bigger worry is that the loop *looks* like it does one thing, but actually, whoever wrote the code after the loop overlooked an edge case buried in its complex logic. Stuff in the loop condition is hard to miss. – Davislor Feb 28 '18 at 17:03
0

If you're concerned about an overly complex for statement being difficult to read, that is definitely a valid concern. Wasting vertical space is also an issue (albeit a less critical one).

(Personally I dislike the rule that if statements must always have braces, which is largely the cause of the wasted vertical space here. Note that the problem is wasting vertical space. Using vertical space with meaningful lines should not be a problem.)

One option is to split it to multiple lines. This is definitely what you should do if you're using really complex for statements, with multiple variables and conditions. (This is to me a better use of vertical space, giving as many lines as possible something meaningful.)

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

A better approach might be to try to use a more declarative and less imperative form. This will vary depending on the language, or you might need to write your own functions to enable this sort of thing. It also depends what condition actually is. Presumably it must be able to change somehow, depending on what is in the array.

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void
Dave Cousineau
  • 313
  • 3
  • 10
  • Splitting a complex or unusual `for` statement into multiple lines is the best idea in this whole discussion – user949300 Mar 02 '18 at 19:59
0

One thing that was forgotten: When I break from a loop (not do all the possible iterations, no matter how implemented), it's usually the case that not only do I want to exit the loop, I will also want to know why this happened. For example, if I look for an item X, not only do I break from the loop, I also want to know that X was found and where it is.

In that situation, having a condition outside the loop is quite natural. So I start with

bool found = false;
size_t foundIndex;

and in the loop I will write

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

with the for loop

for (i = 0; i < something && ! found; ++i)

Now checking the condition in the loop is quite natural. Whether there is a "break" statement depends on whether there is further processing in the loop that you want to avoid. If some processing is needed and other processing is not, you might have something like

if (! found) { ... }

somewhere in your loop.

gnasher729
  • 42,090
  • 4
  • 59
  • 119