There isn't one (in case you're scratching your head at the title of this post.)
Some would say this is a good thing because "goto is evil." I don't have a problem with the fact that there is no goto statement in Javascript. I do, however, have a problem with people who unconditionally think "goto is evil."
I for one, do not think goto is (always) evil. Oh it can be, don't get me wrong. But it doesn't have to be, and you shouldn't automatically assume that it is.
Donald Knuth's Structured Programming with go to Statements ... analyzes many common programming tasks and finds that in some of them GOTO is the most optimal language construct to use.
Still, as Joey Tribbiani says, "it's a moo point (like a cow's opinion; it doesn't matter)," because there is no goto statement in Javascript.
Why all this talk in a Javscript blog about something that doesn't exist in the language? Because Javascript provides an alternative. One that, if you're me, your coworkers will criticize you for using (even though it made perfect sense and actually enhanced the readability of the code!)
Read on to learn more.
The labeled continue
Labeled continue is a perfectly valid language construct in ECMAScript-262 (Edition 3), and yet, I caught hell and had to fight a bitter battle in order to be able to use it.
The scenario? I was dealing with a nested loop. The outer loop was iterating an array of objects. Those objects happened to be arrays themselves which also needed to be traversed.
To make the example a little more concrete, let's pretend the outer array was an array of "Pastures." Each Pasture contains many "Cows." The idea was to loop through the array of Pastures checking to see if all the Cows in that Pasture were done eating (work with me here). If so, we'd fire a callback function to let Farmer Ned know that all the Cows in Pasture X were done eating.
Simple enough right? All we need to do is loop through the Pastures, check each cow in the given pasture to see if it's done eating, and only if all cows are done eating do we fire the callback function.
For efficiency, I decided to test the cows to see if any one cow was eating. If so, I could abort the remaining checks.
Here was my take:
var i, pastureLen = pastures.length;
pastureLoop:
for (i = 0; i < pastureLen; i++)
{
var pasture = pastures[i];
var cows = pasture.getCows();
var j, numCows = cows.length;
for (j = 0; j < numCows; j++)
{
var cow = cows[j];
if (cow.isEating())
{ continue pastureLoop; }
}
// No cows were eating, so fire the callback for pasture[i]
pasture.executeCallback(); // or whatever
}
Of course, as soon as the code-review process started, I got one developer and two PMs (PMs no less!) criticizing my use of "goto."
What bothered me wasn't so much the criticism of my code (heck, that happens all the time). What bothered me was that they automatically rejected it when they saw the labeled continue (without stopping to think if this might not actually make some sense!)
Now, I'm well aware that you could accomplish the same thing by creating a boolean variable, allDoneEating, which is initially true and which only gets set to false if some cow.isEating() statement returns true. Then you'd have to add an additional check outside of the inner for-loop, but inside the outer for-loop to see if the allDoneEating boolean were true, and if so, you could fire the callback function.
Phew! It was hardly worth typing that out.
Why in the world would I want to go through all that extra effort when a simple little continue pastureLoop; will do just fine?
Furthermore, what could be more readable than continue pastureLoop? It very clearly and very succinctly describes the fact that, if any cow is eating, we want to continue with the pasture loop!
I dunno, maybe it's just my own crazy brain that works this way, but I think the labeled continue is a clean and elegant solution in this particular case, and I don't see any reason why it should be shunned in favor of creating an extra boolean and requiring an extra if-conditional.
The labeled break:
I've done a lot of ranting and very little coding in this blog post, so to end, I'll offer a quick example of a similar construct to labeled continue: labeled break.
For this code snippet, I'll return to the cow example, but with slightly different requirements. This time, we want to know how healthy our cows are. We're going to loop through each pasture counting the number of healthy cows we can find until we find a sick cow, at which time we want to abort the entire process. A labeled break makes this really easy
var healthyCows = 0;
var pastures = getPastures();
var i, pastureLen = pastures.length;
pastureLoop:
for (i = 0; i < pastureLen; i++)
{
var pasture = pastures[i];
var cows = pasture.getCows();
var j, numCows = cows.length;
for (j = 0; j < numCows; j++)
{
var cow = cows[j];
if (cow.isSick())
{ break pastureLoop; }
healthyCows++;
}
}
alert(healthyCows + ' healty cows were found before a sick cow was found');
And again, you could of course accomplish this without a labeled break. Depending on the complexity of your situation, might want to avoid the labeled break, but then again, it might be just the thing!
If there's anything I hope this post conveys, it's that you shouldn't unconditionally rule out any valid language construct just because a few naysayers claim that it's "evil." Analyze your situation, think about what you're trying to accomplish, and make a decision that's appropriate for your situation.
Happy ECMAScripting
16 Responses
Hmm, didn't know JavaScript provided the labeled break or continue. I am too lazy right now to check: in what version of JavaScript is that available?
And why are PMs around during a code review? I know technical PMs exist but…
I totally agree with you. A labelled break makes sense there as it quickly and easily gets you out of a series of iterations that are unnecessary but allows the code to remain very simple and obvious.
I would be happy using it myself.
Ola:
I believe that break and continue are defined in ECMAScript 262 (all editions) but that labeled break and continue are not defined until Edition 3 (someone correct me on that if I'm wrong).
As for the PMs… they just happened to be around in the office where the dev was and felt like offering some "helpful advice" (I guess).
I didn't know about labeled continue, so thanks.
My understanding is that goto is generally considered a bad idea because it will be difficult to read over time: as the length and complexity of the loop grows, and the exit conditions may appear in more places, the goto(s) will get lost. A similar argument is made for having a single return in a method, or even for not using case statements. I tend to think these things arguments are probably generally true, but not universally. When/if the situation grows in complexity, then refactor to a better strategy. If it's not, then the simpler case should win.
I also tend to think this is a side effect of erquired code reviews: generic "best practices" are easier to point out and contribute for the time invested, and politically safer, so that's all the review ends up doing. Rarely is a genuinely better design found or architectural issue discoverd, just StyleCop type stuff.
> Now, I'm well aware that you could accomplish the same thing by
> creating a boolean variable, allDoneEating, which is initially
> true and which only gets set to false if some cow.isEating()
> statement returns true.
No no no. Just re-execute cow.isEating() outside of the inner loops. The cow variable isn't scoped inside of that loop.
Actually you'll need something like if(cow && cow.isEating()) outside of that loop just incase there are zero items to loop over.
RichB,
I think I understand what you're saying, but let me make sure:
Ditch the label, and change the inner loop's "continue pastureLoop" to a plain old "continue;"
Then, outside of that inner loop, do another check: if (cow.isEating()) and if so, continue the outer loop?
In this case, it would also seem that you could check: if (!cow.isEating()) and fire the callback if cow isn't eating.
Note: The cow variable, defined in the for loop is not scoped to the for loop: see my previous blog entry on variable scope.
I think this idea is okay, but what about a more complex case with more than 2 nested loops and you have a condition where you want to break out of a deep inner loop and start over at the outer most loop?
My example may not be ideal to illustrate more complex cases (it's just a silly example I whipped up), but I still think labeled continue/break (when used with caution) can be valuable.
Finally, as programmers, sure, we always know that there is more than one way to "skin a cat" (so to speak), but in my example, I still feel that "continue pastureLoop" describes most clearly and most succinctly exactly what it is I want my program to do (and in no uncertain terms).
Yeah,
continue pastureLoopmakes the most sense. No doubt about it.I say:
continue usingLabelledContinueI think there's a general consesus to ignore some of a language's "more advanced" features because they're commonly used and therefore not always well known. To me, this is just dumbing down of programming; if we can educate others through our code, then so be it.
I agree – I started out with BASIC, so the goto function makes sense in a lot of cases… not *all* cases, but more than enough to not call it evil right off. Now, having started with BASIC, I've only recently gotten into javascript and the lot of interactive languages, so while your example makes sense, how would you go about making it more complex?
i/e:
if(a=="true") goto b
else goto a
if(b=="true") goto c
etc…
No need to post this really, but a reply would be awesome.
I abhor gotos but I understand that at times things that are not best practices can be more efficient to use. In your scenario, I think the continue was absolutely the best way to go because *it is more efficient* than using a boolean and affording the extra overhead for the additonal isEating() calls. I fully agree that should the code need to grow to be more complex, it's possibly the design pattern of the continue could become unwelcome. So, document as such somewhere, construct rudimentary prototype code for that situation, and leave it as being more efficient when it doesn't need to be more complex.
The continue; is listed in my JavaScript 1.2 book. Not sure if it was available before this.
Thanks for the info. I did not know about labeled breaks and continue… thank you. I did a little research and there appears to be a goto statement in JavaScript though.
~David
David,
Do you have a source that documents a supported goto statement in Javascript? At the time this post was written, there was no goto statement in ECMAScipt-262.
Most people that I find that are against 'goto' statements don't understand that the 'goto' is a jump statement in machine language. All the loop structures ('for', 'while', 'switch', etc…) are composed of jump statements. For that matter, function calls are composed jump statements. It is a fundamental tool of procedural programming and is very powerful. Many language features are abused by programmers, but the 'goto' must not be deprecated because of poor usage.
Professor's are probably to blame for this 'evil' concept. It's probably a nightmare reading student's code to begin with. I'm sure 'goto' statements sprinkled throughout their code doesn't help.
I agree with using a label to branch out of code that need not to be executed.
The goto label way back in the Cobol days was being misused and therefore "Spaghetti Code" was invented. These programms were very difficult to trace, maintain and debug.
Using a Goto or label to branch out of loop processing or out of a function is good programming sense when used appropriately.
I realize this is an old post – but its been a year, and now (in FF at least)
we have iterator expressions and "yield" statements inside iterators!
Ive also found myself in a lot of cases where I use var hasSomethingBeenFoundYet = false;
I feel like it actually makes the code more complicated, especially when there are multiple exit conditions or checks.
var hasSomethingElseBeenFoundYet = false;
if(hasSomethingElseBeenFoundYet && hasSomethingBeenFoundYet) return 'found both';