This is a perfect example of just how bad that suggestion is, and just how disastrous it can be. It's totally worth typing a few extra characters here and there to basically avoid these kind of situations completely.
This also applies to the "JS without ;" crowd.
You may think you're too good to know all the rules of ; or you can just don't think about it, and worry about other things instead, like your code.
I certainly see far more bugs caused by an improperly inserted semicolon than an improperly omitted semicolon, but then I'm looking at jshinted code most of the time.
IIFEs are the most common source of semicolon problems:
for(i = 0; i < 10; i++){
x = arr[i]
(function(x) {
setTimeout(function(){console.log(x)})
})()
}
A less common sort of pattern that I still use pretty often as a DRY measure: init()
[x, y, z].forEach(function(a){
if(a.length > 5) {
process(a)
}
})
This is clean and readable, and without semicolons it's completely wrong.Now sure, you can prepend semicolons to these specific cases, and that will work. Here's why I dislike that:
1. Those leading semicolons look weird. To the uninitiated, it looks like a typo, and that's a significant hazard if anyone else is going to touch your code.
2. While there are only a handful of special rules like this to remember, there's only one rule to remember if you don't rely on ASI.
Your examples will crash immediately and at the right spot though. The problems I see caused by excessive use of semicolons are often far weirder.
That said, inadvertent errors caused by semicolon insertion are still more common and baffling (especially by people addicted to jslint who use a variable declaration idiom particularly easy to screw up with errant semicolons).
A relatively rare scenario, but a brutal one to debug.
As for errors caused by extra semicolons, they can be weird, but I don't think I've ever actually hit one in practice. They'd also be a little easier to spot, since you tend to develop a reasonable instinct for where semicolons belong.
Hence these are the cases where more time and resources will be wasted because of it.
"I certainly see far more bugs caused by an improperly inserted semicolon"
What would be an example of this? Because I've seen exactly zero bugs of this type (not counting typos, of course)
(usually this will be across multiple lines)
...just overwrote c in a different scope. This kind of bug is common, idiomatic, baffling, and actually more likely among coders subscribing to javascript "best practices".
This doesn't justify playing a guessing game and skipping semicolons just because you think you know all the rules about not using them.
if (something) do_something();
Instead of: if (something)
do_something();
So it's much less likely that I'll confuse indentation with a block scope.That's fine until you write
if (something) do_something(); do_something();
or worse: if (something); do_something();
I've actually seen something very similar to that one.You haven't really changed the dimensions of the problem by putting it all one one line. Only the whitespace is different. Sure it looks wrong to you; but so does the incorrect code from apple.
I don't understand the need to omit braces; it doesn't make the compiled program any smaller. And denser source code is not always more readable, or we'd prefer "? :" to "if" every time.
if (something); { do_something(); }
(Personally, I configure my editor to highlight if (...); in bright red.)The first line of defence is consistent code layout; e.g. always use "{ }" after an if, and only one statement per line. This aims to make simple typos less likely to compile, make the code more readable and so make errors stand out.
Then there is using and paying attention to the linter / compiler warnings / jshint + 'use strict' / static analysis or whatever it’s called in your language of choice.
Then there are unit tests or integration tests.
Any of these might have caught the apple error, but there are no absolute guarantees.
IMHO it's a professionalism issue – gifted amateurs are great, and they can succeed... most of the time. But a professional engineer would put aside ego "I won’t make mistake like that" and laziness "I don’t need to type braces this time" and do the careful thing every time. Because each step raises the bar, and makes it harder to ship code with bugs like this. Because sometimes it matters.
if (something); { do_something(); }
So..
if(something) {do_something();}
Many code editors will easily insert curly brace pairs, so it's really just a single extra key stroke you're saving.
Two gotos immediately after one another at the same indentation level is obviously wrong by visual inspection.
Proper indentation might have led the casual viewer to think that the code was actually ok!
This is what happens if you try the same thing in it http://imgur.com/syt6LuU.
I have emacs setup to auto run clang-format on each save of cc-mode files. I can't NOT notice this stuff now.
Outside of python, for obvious reasons, it should never be allowed. The slightly nuts conditionals in Erlang are justifiable for avoiding exactly this kind of problem.
They exist because of the long, bloody history of mistakes by very smart people.
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
{
goto fail;
}
{
goto fail;
} if (condition)
{
doSomething();
}
into if (condition)
{
doSomething();
}
{
doAnotherThing();
}
instead of if (condition)
{
doSomething();
doAnotherThing();
}
then that person needs some serious mentoring right away. 'Cuz. . . just wow.As far as the original example goes, if it's an error it's most likely a copy/paste error. Curly braces help there, too. With three times as many lines in the block, the odds of a paste error resulting in code that even compiles is greatly reduced, and a compiler error should call attention to the issue.
Even assuming the bug was malicious, the curly braces would increase the odds of it being caught by another developer. This is particularly the case now that offside rule languages are common. A large chunk of younger devs cut their teeth on languages where
if (condition)
doSomething();
doAnotherThing();
doesn't look odd in the slightest. But I think that the 2nd example above would still look immediately bizarre to nearly everyone.Right, and this demonstrates the major problem with verbosity in languages and APIs and design patterns. When you have to repeat yourself many times, it's very easy to make a mistake in one of the near-copies, and you or a code reviewer can miss it because you'll tend to quickly skim over the boilerplate.
For cases like this, using exceptions rather than manual error value checking would make your code shorter, less redundant, and not susceptible to this particular bug. (Not possible in C, I know).
But I wonder if there's still room to tighten up the code. Perhaps something like
if ( (err = SSLHashSHA1.update(&hashCtx, &serverRandom) != 0)
|| (err = SSLHashSHA1.update(&hashCtx, &signedParams) != 0)
|| (err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0))
{
goto fail;
}If there were a language feature available to do this more elegantly, whether exceptions or something else such as a Haskell-style monad with fail semantics, I'd almost certainly agree with using it in preference to a series of manual checks, though.
If not, `|| (err = check())` is equivalent to separate checks which also also `goto fail` immediately.
Left-to-right, short-circuit evaluation of the logical operators is so deeply rooted in C idiom that a mainstream compiler that doesn't respect it would be practically unusable. But perhaps it wouldn't work in a hobbyist compiler, or a non-standard dialect of C.
While valid according to the language definition, assignments within conditional expressions do have a reputation for being error-prone and not always the easiest code to read. Sometimes they're still neater than the available alternatives.
However, IMHO in this case, it feels a bit too easy to break the code during later maintenance edits. For example, someone might simplify the code by removing the explicit comparisons with 0 that are unnecessary here, and then later someone else might "fix" an "obvious bug" by replacing the "(err = x) || (err = y)" construction with the common construction "(err == x) || (err == y)" that they assumed was intended, perhaps prompted by a compiler warning. Obviously they shouldn't if they didn't properly understand the code, but when has that ever been a reliable guarantee? :-)
But yes, mandatory braces on the same line is the correct choice.
if ((err = DSomething()) != 0)
goto fail;
is to wrap it in a macroWhile Go has goto for tricky situations like this, because it has defer you don't have to use it often, assuming the free calls were needed (and the vars were not going to be GC'd):
defer SSLFreeBuffer(&hashCtx)
defer SSLFreeBuffer(&signedHashes)
if err = SSLHashSHA1.update(&hashCtx, &serverRandom); err != nil {
return err;
} else if err = SSLHashSHA1.update(&hashCtx, &signedParams); err != nil {
return err;
} else if err = SSLHashSHA1.final(&hashCtx, &hashOut); err != nil {
return err;
}
return nil;
}err = SSLHashSHA1.update(&hashCtx, &serverRandom); if (err != nil) { return err; }
Is there objective evidence for this? As a Go programmer a semicolon in an if statement screams to me. I can see it possibly being in issue for new Go programmers- but I don't remember it being one for me.
I rarely use the one-line `if err := ...; err !=nil ` idiom because its quite a mouthful. However when I do, I try to make sure it's not too much to grasp at once. Here the extra `else` goes against that.
Alright, I know this is just a quick snippet on HN and all, I just thought I'd mention it anyways. Maybe next time you actually write that in code you'll think about my point. ;)
This is not spaghetti code or what Dijkstra talked against.
It's one of the two things one hears novices: that gotos are to always be avoided (because they heard that they are bad), and that we should write stuff in assembly (cause they heard that it's fast).