Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What is the point of the no-continue rule? #1103

Closed
callumlocke opened this issue Oct 4, 2016 · 47 comments
Closed

What is the point of the no-continue rule? #1103

callumlocke opened this issue Oct 4, 2016 · 47 comments
Labels

Comments

@callumlocke
Copy link

callumlocke commented Oct 4, 2016

The official ESLint docs say "When used incorrectly it makes code less testable, less readable and less maintainable."

Does Airbnb agree with the above reasoning? If so, can you give an example of how continue makes code less testable and less maintainable?

(I'm not arguing against it btw, just want to understand why.)

@ljharb
Copy link
Collaborator

ljharb commented Oct 4, 2016

continue, like break, is GOTO. There are countless articles on the internet about GOTO being harmful.

In addition, this style guide discourages loops, which are the only places you'd need continue or break.

@ljharb ljharb added the question label Oct 4, 2016
@ljharb ljharb closed this as completed Nov 6, 2016
@deckar01
Copy link
Contributor

deckar01 commented Mar 13, 2018

continue is generally not used with a label. I think eslint needs to differentiate GOTO label usage from bare continue usage. When used without a label, continue is no different than a return statement in the body of a forEach loop.

I am going to disable this rule for my project. Would you be interested in added granularity for the no-continue rule if I proposed it?

@deckar01
Copy link
Contributor

'no-labels': ['error', { allowLoop: false, allowSwitch: false }],

no-labels is already enabled.

@ljharb
Copy link
Collaborator

ljharb commented Mar 13, 2018

Loops themselves are banned by this gude; so there’s no use case for continue.

@deckar01
Copy link
Contributor

I am using the latest version of the airbnb base config for eslint and it doesn't warn about for loops.

@deckar01
Copy link
Contributor

Also, how would you generate an array with the numbers A..B without using a for loop? My guess is that it is less readable than

const range = [];
for (let x = A; x <= B; x += 1) {
  range.push(x);
}

@ljharb
Copy link
Collaborator

ljharb commented Mar 13, 2018

The guide is far more expansive than the linter config; rest assured, it will one day warn on for loops, to match the guide text.

The answer is you’d make a “range” abstraction, in which the implementation details are both irrelevant and fine to override linter warnings on, rather than writing a loop in your own code.

@SelaO
Copy link

SelaO commented Apr 24, 2018

Continue is not goto. That's like saying loops are goto.

@ljharb
Copy link
Collaborator

ljharb commented Apr 24, 2018

@SelaO no, it's not like saying that. Loops that use break or continue are goto.

@SelaO
Copy link

SelaO commented Apr 24, 2018

What's the difference between the following?

start:
...
if(x) goto start

and

start:
...
if(y) goto start
...
if(x) goto start

and

start:
...
if(y) goto end
...
if(x) goto start
end:

Now how are these examples (which almost look like nested loops and keep all the jumps inside the scope of the loop) are the same as this?

function foo(){
...
ARBITRARYLABEL1:
....
goto ARBITRARYLABEL2
}

function bar(){
...
goto ARBITRARYLABEL1
...
}

ARBITRARYLABEL2:

@ljharb
Copy link
Collaborator

ljharb commented Apr 24, 2018

@SelaO I’m not sure what you’re asking.

@MegaArman
Copy link

MegaArman commented Jun 27, 2018

I actually don't currently use airbnbs rules, but am considering it after reading the discussion here.
continue should be banned, especially if you don't use loops (you shouldn't in 2018, but that's a whole different discussion).

I was once a continue, break, return everywhere, kind of dude and felt very clever for leveraging those features, but really it doesn't help your team. The less bouncing around you have to do when reading code the better. continue makes code harder to read for other people -you might not see why from your own code, but imagine working on a large codebase with lots of loops and if statements with continues scattered about... Do you really want to maintain code you can't read top to bottom in one shot? Bite the bullet, learn to write easier to read code even if it's initially harder to do.

Just for reference, in Scala the last line of code is automatically the return value if no return is specified and it's what they seem to encourage (disclaimer: I'm not a Scala expert, but just saying that there's things to be learned from other languages).

@SelaO
Copy link

SelaO commented Jun 27, 2018

@MegaArman
Loops are still useful in 2018 in many non trivial cases and will stay useful even in 2118, you can't do everything with map, reduce, filter.

See the example I gave above and tell me how continue is "bouncing around" like goto is.

but imagine working on a large codebase with lots of loops and if statements with continues scattered about

That's an invalid statement, if the code already become such a mess, then not having continue and breaks won't help you, loops shouldn't be huge, just like functions shouldn't be huge.
Banning basic coding concepts isn't a magic tool to make code more readable and maintainable, and oftenly can make the code even less maintainable (like more complex stopping conditions in no continue loops)

@ericblade
Copy link

Yes, there are places where you should use loops. If you're doing it in front-end code, inside your main code-base, though, it might be a sign that there are other things that could be problems. In general, however, your arrays should be things that you want to iterate completely over. You may have a data structure problem, if you find yourself needing to drop out of a render loop early, for example.

@MegaArman
Copy link

MegaArman commented Jun 27, 2018

To clarify I really meant the traditional for ( ; ; ) and while

They are not in every language currently (Ex: Python doesn't have for(; ; ), some languages have always provided proper tail recursion instead of while) and haven't always been in languages. I cannot think of a case where I need them over forEach, map, recursion, etc. I think there's formal proofs somewhere out there that show they are not needed, but you can always find good alternatives on stackoverfow. Iteration can be done in more syntactically clean ways that are more akin to functional programming and less bug prone. There's some extreme cases where they might be more performant still -not sure, but nonetheless I can't imagine JavaScript is meant to concern such low level cases.

@SelaO I'm not going to reply to the codebases discussion. Language considerations should always be worst case -the "well good programmers don't do that" argument is largely what let goto survive for two decades. Definitely suggest seeing some of Douglas Crockford's suggestions about using the most reliable subsets of languages. Brandon Eich who made JS also has suggestions in this regard (I recall him regretting "==" for starters).

@ericblade
Copy link

There are places where you can make a convincing argument for just about any language construct, even the ones that are almost universally panned. Ask a kernel programmer about goto, for example.

Here's an easy example where a for loop is useful. Say you have 5000 records in an array, but you want the first 200 that fit a particular criteria. So you write a loop to search that array, and break once the limit is hit. Perfect use for a for loop. But you should abstract that to a separate function outside of your main logic. If you're doing that in a front-end environment, I'd also seriously consider if the front-end is the place to do that processing -- do you need all that data on the front-end, or should you be sending only the first 200 that fit from the back-end to begin with? That depends on your use case. Either way, it shouldn't be in the main logic.

@ljharb
Copy link
Collaborator

ljharb commented Jun 27, 2018

Or you write an abstraction using .some or .find, no loop required. Totally agree with the rest of the last comment tho.

@ericblade
Copy link

ericblade commented Jun 27, 2018

.... .some or .find return a true/false or a single item . . i could see ways to use them to construct something, then break, but i feel like that would be abusing them in a way that doesn't make sense, for the sake of not using a traditional loop. (feel free to provide an example, in case there's something i'm not considering)

@ljharb
Copy link
Collaborator

ljharb commented Jun 27, 2018

It’s not abusing them, it’s using them - iteration is best expressed with iteration methods, not with loops.

@ericblade
Copy link

I'd like to see an example of using .some or .find to return a limit 200 of a larger array, that doesn't look like abusing them, if you've got a minute to teach me. :-)

@ljharb
Copy link
Collaborator

ljharb commented Jun 27, 2018

function take(arr, predicate, count = Infinity) {
  const taken = [];
  arr.some((item, ...args) => {
    if (predicate(item, ...args)) {
      taken.push(item);
    }
    return taken.length >= count;
  });
  return taken;
}

Obviously you may consider that “abuse” - that’s subjective - i do not; i consider that a perfectly reasonable usage of some when hidden by a presumably well-tested abstraction. It also has the benefit of giving you array iteration method semantics largely for free, which you’d have to do manually when implementing with a loop.

@ericblade
Copy link

ericblade commented Jun 27, 2018

Yeah, that is exactly what I had imagined when I asked. It's unfamiliar, creative usage, and I'll need some time to think on that. :-) I've warmed up to most everything in here over time, though. I really appreciate you taking the time to provide examples for such things, it has definitely improved my creativity and expression of code.

@Zakini
Copy link

Zakini commented Oct 11, 2018

Loops themselves are banned by this gude

I feel like I've seen this repeated far too many times across multiple issues. Would it be worth adding 'ForStatement' and 'WhileStatement' to no-restricted-syntax? That way rules related to loop-only syntax (are there any other than no-continue?) could be safely removed. If anyone decides to disable no-restricted-syntax in their project, they can use loops in any way they want without having to disable extra rules; they're off the style guide by that point anyway.

That said, discussion around banning loops always revolves around for, for-of, etc. and while never gets mentioned. Is while actually allowed by this guide? I can't think what the functional alternative to it would be...

@ljharb
Copy link
Collaborator

ljharb commented Oct 11, 2018

Eventually we’ll be adding those rules, but we’d ever want to remove no-continue or anything similar; even when using loops, using continue is GOTO.

@Venryx
Copy link

Venryx commented Nov 7, 2018

SelaO: Continue is not goto. That's like saying loops are goto.

ljharb: no, it's not like saying that. Loops that use break or continue are goto.

Lets look at an example.

Using continue:

for (let item of array) {
  if (item == 0) {
    continue;
  }
  console.log("Found non-zero item");
}

Using if:

for (let item of array) {
  if (item != 0) {
    console.log("Found non-zero item");
  }
}

I'm guessing you'd say that the continue example is like GOTO because: it snaps the point of execution (from line-3 in example 1 to line-2, for the next iteration)

The thing is, "snapping the point of execution" is also happening in example 2. You're "snapping the point of execution" from line 2 to line 4.

"Okay, but in the second example, your always snap in the same direction -- you always snap downward, to directly after the block's braces."

The thing is, you can make a similar statement about continue: "Okay, but you always snap in the same direction -- you snap upward, to the first line of the loop."

In other words, both continue statements and if statements snap execution, and they do so in a consistent way: one always snaps up, the other always snaps down.

Conclusion: I don't see how the continue statement is significantly more "like a GOTO" than an if statement.
= = = = = = = = = =
The main argument I can see in response is this: "Both statements act consistently, however continue statements obscure readability more because sometimes the start of the loop is on a much higher nesting level than the continue statement, requiring more traversal to find the next executed line."

Okay, however:

  1. Many (if not most) times, usages of continue are done in simple if statements directly under the for loop, in which case you don't have to traverse through nesting levels -- so it's equivalent to an if-statement in how difficult it is to track the point of execution.
  2. Even in the nested cases where continue statements may yield worse readability (depends on how you'd have to restructure to avoid the continue), it's still an exaggeration to say "continue statements are GOTO" whereas "if statements are nothing like GOTO".

Both of them:

  • Snap the point of execution.
  • But they do so consistently.
  • With the programmer knowing unambiguously where the next line of execution will be. (without having to search for labels like with GOTO)
  • And with no chance of jumping to areas of code which have code prior to them that (misleadingly) have not yet been run. (which is one of the main problems with GOTO)

I'd like to see a list of properties of continue statements which make them equivalent to GOTO statements, which are not shared by loops or if statements. I know there are some, but I don't believe them to be substantial enough to say "continue is GOTO" but "if statements are nothing like GOTO".

@ljharb
Copy link
Collaborator

ljharb commented Nov 7, 2018

@Venryx conditional branching isn't the same as goto, conceptually - even if under the hook it's all jumping to different CPU instructions.

continue is GOTO because it breaks the top-down flow of code execution - with a conditional, you the human read the condition and know if you enter the block, or skip over it. With continue, you exit the block prematurely, no longer evaluating further statements, and then proceed with the next iteration.

@Venryx
Copy link

Venryx commented Nov 7, 2018

continue is GOTO because it breaks the top-down flow of code execution

But a regular loop fits that description as well. When you reach the end of a for loop block, it breaks the top-down flow of code-execution -- you have to scan back up to the top of the loop to find the next execution point.

We don't consider loops (or forEach) to be as bad as GOTO though, and that's because:

  1. It's easy to find where the next line of execution is when you get to the end of a loop.
  2. Loops still ensure that any code above the current line has already been run, and has been run in order -- that is, you don't have to worry about having jumped forward with misleading code above it that hasn't initialized.

And continue has those same positive properties. With continue:

  1. It's easy to find where the next line of execution is: just return to the start of the loop.
  2. Any code above the current line has already been run; continue never causes you to jump forward "over" code. It works like a loop and merely lets you go back and re-run a block of code (with the next iteration of data), which is easier to reason about.

In other words, if continue is criticized as being GOTO merely because it breaks the top-down flow and transports you to an earlier line, then you have to also criticize for loops, forEach calls, and anything else that makes you re-run a block of code, because those either are or result in statements that jump to earlier lines of code. (such as return calls inside a function passed to forEach, which work almost exactly the same as continue calls)

If there is a different reason for calling continue an instance of GOTO, then it can be brought up, but so far I haven't seen a substantial one.

= = = = = = = = = =

You do add:

With continue, you exit the block prematurely, no longer evaluating further statements, and then proceed with the next iteration.

The concern here could be that, continue limits your ability to ignore earlier (seemingly unrelated) if-statement blocks, because you don't know whether that if-statement block contains a "continue" statement which will end up short-circuiting the for-loop block and keeping the code you're interested in from even being reached.

If that's the core reason that continue is criticized, then I can understand that. However, if that's the reason, then you also have to criticize having multiple return statements in the same function: just like when you add a continue statement, adding a 2nd or 3rd return statement makes it so you can't ignore those other if-statement blocks anymore. You have to make sure they don't contain a return statement that's going to short-circuit the function block before it gets to the code you're interested in.

Yet, the airbnb style guide indicates that it's fine to have multiple return statements in the same function. There are a few examples on the homepage, such as: https://github.com/airbnb/javascript#variables--define-where-used

// good
function checkName(hasName) {
  if (hasName === 'test') {
    return false;
  }

  const name = getName();

  if (name === 'test') {
    this.setName('');
    return false;
  }

  return name;
}

= = = = = = = = = =

In summary, I think there were two criticisms of continue statements that were brought up (as grounds for saying continue is GOTO):

  1. Continue statements break the top-down flow of code execution [like GOTO].
  2. Continue statements reduce your ability to skim over if-statement blocks, because those blocks might contain continue statements that short-circuit the loop's code [like GOTO].

My response was that:

  1. For loops, forEach calls, return statements in forEach functions, etc. all do the same thing and yet aren't criticized as being (or resulting in) GOTO statements because of it, so the criticism is inconsistent with our acceptance of this aspect for other constructs.
  2. Having multiple return statements in the same function does the same thing, and yet it's commonly used -- including in the airbnb guide itself. Furthermore, people don't call those extra return statements GOTOs based on it.

The reason #2 above I think is a fair criticism of (deeply nested) continue statements. But that doesn't mean it's the same as a GOTO, because there are several negative properties of GOTOs that aren't shared by continue statements. (a couple of these are listed at the start)

@ericblade
Copy link

For me, I ignore the rules that encourage multiple return statements, and I stick to a rule that I've held much longer: There should be at most (with some potential exceptions) two points of exit to a function -- throw at the top, return at the bottom. Of course, we can't always throw when we need to exit early, but in general: early exit as quick as possible at the top, otherwise flow should reach the bottom.

Your opinions may differ, and of course, this is the AirBnB style guide, not ericblade's style guide. but if i were to write one, it would be quite similar to this one :-)

@Venryx
Copy link

Venryx commented Nov 8, 2018

Yep, and I can understand that line of thinking. My intention's not to change the airbnb style, and I normally don't comment so long on these things. The main reason I responded was because of the insistence that kept being made that "continue is GOTO".

In my opinion, that designation is lazy and misleading, because several of the negative properties of GOTOs are not in fact shared by continues. And in fact, the properties that continue does share with GOTO are all shared by other accepted constructs.

So basically, the "continue is GOTO" rhetoric is what got me riled up (like a false assertion at a political rally), and if that one point were retracted I'd be content.

@mikkqu
Copy link

mikkqu commented Nov 17, 2018

Totally agree. Advocating against continue in functions is pretty much the same as advocating against return guard clauses in normal functions.

@hcbh96
Copy link

hcbh96 commented May 18, 2019

Let say I am trying to stick to the rule of no GOTO as suggested and I have a piece of code as follows what can I use to make the code more readable?

  • the code must print the Lowest number of matching objects in each array i.e for the two supplied it should print 1 X 1s & 2 X 2s
const array1 = [1, 2, 2, 2, 2, 2, 'h', 'h', 'h', '', 'b', 0, 'r', 10, 'e', 'a', 12, 'k', '', 99, 'i', 's', 100, '', 'g', 101, 'r', 'e', 'a', 't'];
const array2 = [1, 2, 5, 1, 2, 'h', 'h', 'h', 'h', '', 't', 'a', 1003, 'e', 'r', 'g', 1012, '', 15, 's', 'i', '', 999, 'k', 'a', 200, 'e', 'r', 1001, 'b'];
let log = 1;
for (let i = 0 - 1; i < array1.length; i += 1) {
  const _1 = array1[i];
  for (let j = array2.length - 1; j >= 0; j -= 1) {
    const _2 = array2[j];
    if (_2 === _1) {
      console.log(_2);
      log += 1;
      array2.splice(j, 1);
      break;
    }
  }
}

@ghost
Copy link

ghost commented May 23, 2019

Deep down inside, all the control flow statements are goto's. Saying that break and continue are goto's, is not enough to justify the rule, because we can say the same for ifs and loops. Even function calls are just fancy gotos. break and continue are limited enough to not to cause any harm, since we can jump only inside a portion of a function (classic goto, like jmp/j* in x86, allows jumping to anywhere you want in the whole program).

@ljharb
Copy link
Collaborator

ljharb commented May 23, 2019

What things are “deep down” isn’t relevant; deep down, a gasoline-based car is a bunch of explosions, but a car isn’t an explosion.

@ghost
Copy link

ghost commented May 23, 2019

That's absolutely true. Therefore saying that we shouldn't use cars because they are moving by exploding - is not a valid statement.

I mean all the criticism of goto's goes from the times of BASIC/assembly/etc, where goto is a source of disaster indeed. in JS, and especially for break and continue, this isnt a thing, because they are limited. We can't "continue" into a different function, as we can do with the "goto".

Anyway I get that general direction for airbnb is to move to immutable functional code, and for that loops dont fit well. Thanks for the quick response.

@SelaO

This comment has been minimized.

@ljharb
Copy link
Collaborator

ljharb commented May 23, 2019

@dcbrwn sure. the general rule would be "don't use explosions", just like it's "don't use goto" - but things that are "basically goto" but aren't actually goto aren't included in that prohibition.

@Venryx
Copy link

Venryx commented May 24, 2019

but things that are "basically goto" but aren't actually goto aren't included in that prohibition.

Exactly. Which is why break and continue should be allowed, just as if statements, return statements, loops, and function calls are.

In other words, it has yet to be shown how break and continue are "more like goto" than those other elements above. The reasons so far provided have been discussed at length above, and shown to be present in those other allowed elements, so they are not valid as distinguishers between break/continue and those other elements.

To format that more explicitly, it's being claimed that:

  1. break and continue "are goto"
  2. if statements, return statements, loops, and function calls are not goto

But when challenged to say how break and continue are "more like goto", the reasons provided do not stand up to scrutiny because those reasons/properties are present in if statements, return statements, loops, and function calls as well.

Again, it's fine if AirBnb wants to prohibit an element in their style guide, that's their prerogative. The core issue most of us have is with the false claim that "break/continue is goto". It's not! (anymore so than those other elements)

If you want to claim that it is, then show us what property break/continue has that makes it like goto, that is not shared by any of those other elements. (for example, continue's property that it "breaks the top-down flow of code execution" is also shared by loops, return statements in functions, and function calls)

@onderaltintas
Copy link

Still there is no evidence that using "continue" is making code less testable and less maintainable for JavaScript. Saying that there are thousands of articles on the internet which say "continue is bad" is like saying that "snakes have legs" cause you have seen it on the internet from the newspaper named "Daily Testicle". I am re-closing it by applying the information there were no valid arguments on why "continue" should not be used.

@OmarOmeiri
Copy link

Ohh, so many uses for for-loops that cannot be done with these functional loops.
Check this out.

I use the AirBnb rules, but this first thig I turn off is no-continue

@ricardodnb
Copy link

Sorry to continue commenting on this thread and this is opinionated but come on @ljharb, do you really prefer a bunch of possibly nested if-elses instead of simple continue statement? how can that be more readable ?

for(const item of data) {
  if (x) {
    // ...
  } else {
    if (y) {
      // ...
    } else {
      // ...
    }
  }
}

vs

for(const item of data) {
  if (x) {
    // ...
    continue
  }

  if (y) {
    // ...
    continue
  }

  // ...
}

@ljharb
Copy link
Collaborator

ljharb commented Jan 31, 2022

@ricardodnb yes. If you provide a non-contrived case, I’ll show you what i find most readable - and it’s not going to be a loop at all.

@icetbr
Copy link

icetbr commented Jun 6, 2022

@ricardodnb a concrete example would allow for better explanations, but but your code could look something along these lines.

const itemsX = items.filter(isX).map(toItemX);
const itemsY = items.filter(isY).map(toItemY);

Avoiding loops means breaking down your operations into smaller steps. It makes your code more declarative than imperative.

@radarsu
Copy link

radarsu commented Aug 19, 2022

I'll add few points here for people that look for better justification of this rule:

  1. Using continue, break as well as nested if statements or other complex logic inside loops suggests, that maybe we iterate over badly filtered list and we should split it into few lists, then run them through few different functions. This encourages writing smaller functions and smaller datasets (smaller pieces increase reusability) plus increases readability.
  2. Unstructured control flow statements in general increase cognitive load when somebody reads the code. It's same for break, continue as well as multiple return statements from a single function, BUT pure functions are usually a chunk that is smaller, separated, testable and usually can be understood in reasonable amount of time. Also, when you understand separated function logic you can "compress" what it is doing in your brain and treat it as black box, allowing you to understand and fit into limited brain's capacity bigger picture of program works faster.
  3. There are statements that are error-prone in programming languages. Goto, break, continue, labels, even very popular switch statement. They are error-prone for various reasons. Even if you can use them perfectly well and only in specific cases where they actually improve readability, there are majority of cases where you or somebody else could use them wrong and cause bugs. As programming is usually cooperative work, it's better if you enforce a rule to not use them at all.
  4. Avoiding continue/break statement's sometimes enforces you to write code in more functional and immutable manner.

@bmind7
Copy link

bmind7 commented Sep 28, 2022

All control flows are masqueraded GOTOs, even IF statement. Should we get rid of IFs?

There are cases when we have to use IF, CONTINUE and so on to have better performance. And it doesn't mean you necessarily end up with less readable code. Running FITLER on the same array many times just to avoid IF and CONTINUE will kill performance and increase the noise to signal ratio of the codebase.

Still, I believe there are many cases when we can use FILTER, MAP, REDUCE and have cleaner code, but there are no silver bullets.

@ljharb
Copy link
Collaborator

ljharb commented Sep 28, 2022

yes, it does mean that - performance overrides clarity here, and you’d use an eslint override comment to explain why you used a worse pattern.

@ericblade
Copy link

I try to keep all of the things that require eslint overrides close together in the lower level functionality area of the code. Anything high level especially at UI level, I want to be following this guide as close as possible.

@ljharb
Copy link
Collaborator

ljharb commented Sep 29, 2022

If you're considering performance at a given location, you're already thinking at a very, very low level.

vientoeste added a commit to vientoeste/BLOKUS-expressjs that referenced this issue Jan 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests