Well, actually; Not all semicolons…

What I have to say here is not just about semicolons. In fact, semicolons are a pretty small part of the message here. But, in fairness, semicolons are what prompted me to write this post.

I like Feross a lot. I have deep respect for many amazing things he’s done. But his deeply flawed argument itself — not the underlying topic of semicolons — deserved to be called out, and I wasn’t the only one who felt so. Sorry for trolling, Feross. Despite being accused of yelling about this at him, at that point the debate was not about semicolons but bad debate about semicolons.

If you (not individually, but as a group) care deeply about/against semicolons — probably you hate them — this is not for you, because I won’t be able to convince you otherwise, anyway. Nor do I want to. You have your reasons. Even if you‘re wrong about the intent of ASI being invented to let you omit them, your beliefs will likely persist.

Note: I just linked a bunch of tweets from @izs. It may seem as if this post is directed at him. It isn’t. His tweets are just convenient illustrations. Beyond that, I have no stake in or concern with whether he likes me or agrees with any of my views. It’s a big web. Plenty of room for differences.

In fact, I won’t even waste much breath re-arguing the case against ASI, which in my mind is already settled. Go read that book excerpt if you want. This post isn’t really about that.

Probably, most of you just think I’m crazy for “giving a shit”, as if merely caring about the topic is offensive.

Clear > Clever

As I said, this post isn’t really about ASI per se, but about the larger category of programming techniques I believe it fits into: being clever.

I’m contrasting clever against clear, so I first need to explain what I mean — or rather, don’t mean — by clear code (as this apparently isn’t universal): not fewer characters, not operators in place of keywords (<ahem> => arrow functions), not not code poetry or other romantic flourishes like this.should.be.clear().

In fact, I find it ironic that some of the same crowd that loves to remove punctuation like ; prefers to insert punctuation in place of keywords, like =>. Come on! Is punctuation good or bad for code!?

Clarity and brevity are often conflated, but I usually don’t find that to be appropriate. The shortest code is not always the clearest. Brief code often favors familiarity and convenience for the developer writing the code (and others already used to it), but at great expense to anyone new who reads the code without such context or predisposition.

These tricks help you write the code quicker, but they actively make the code harder for the next developer to understand. That developer first has to spend time learning your tricks before they can decipher your code.

To me, writing clear or readable code isn’t about whether the code can be understood after you’re already familiar with it, but rather about how understandable it can be the very first time new eyes see it. Most anyone can do the former, but accomplishing the latter is much harder.

By my definition, clear code lets its concepts shine through the syntax, such that a reader can, without undue effort, learn the concepts of the code, and thus recreate code from scratch with those concepts — not just copy-n-paste.

That’s quite different from the normal definitions of readable code that most people seem to use, such as “glance-able” or “terse” or “punctuation-free”. These things are actually awfully subjective, even though most people seem to swing them around as objective sticks.

So, let me list several examples of things I think are similarly clever (as relying on ASI):

  • ts = +new Date;: instead of Date.getTime(), taking advantage both of operator precedence that new binds tighter than +, and also the bizarre legacy quirk that constructors (function calls preceded by new) that don’t have any arguments don’t need the ( )
  • return foo(), bar();: really, any usage of the comma operator to string multiple expression statements together where only one expression is normally expected, and only use the last of the expression results
  • choices = [!1,!0];: instead of [false,true], saving a few characters to rely on the boolean coercion and parity flip of !
  • slots = [,,,,];: instead of declaring affirmative empty values like null or undefined, taking advantage of the terseness of commas as “empty slots” in an array
  • option = choice1 ? choice2 ? choice3 : choice4 : choice5;: instead of an if..else if..else structure, taking advantage of the right-associativity of the ? : operator
  • str = num + "": instead of String(num) or num.toString(), converting a number to a string, taking advantage of both implicit coercion and operator overloading of +
  • foo && foo();: instead of if (foo) foo();, using && as a “guard operator”

You may notice something about most of these tricks — yes I’ve actually seen all of these in real, authored code — they all save characters, and as such many of them are actually used by minifiers like Uglify, etc.

Are these tricks bad/buggy/wrong JavaScript? No. But when used in your authored code (instead of being produced by a tool), they favor being clever (writing shorter code; doing more with less) over writing clearer code.

Here’s some other clever tricks that may be less common but are still valid:

  • Early-block-exits: conditionally skipping over code without an else
    do {
      if (skipStuff) continue; // or `break`
      // ..
    } while (false);
    
    try {
      if (skipStuff) throw;
      // ..
    } catch(e) {}
    
    (function(){
      if (skipStufff) return;
      // ..
    })();
    
  • The reverse, inverting the switch (switching on true with conditions in the cases) and using fall-through case statements to string multiple independent blocks together without nested ifs
    switch (true) {
      case abc:
        // .. (no break here)
      case wqr:
        // ..
        break;
      case xyz:
        // .. (no break here)
      default:
        // ..
    }
    
  • Extracting the “first” key (for some definition of ordering) from an object
    var firstKey;
    for (var key in obj) {
      firstKey = key;
      break;
    }  
    

Again, are these techniques wrong? Ehhh… No. But do they favor cleverness over clarity? I think many would agree with me: yes.

Clever: Bad vs Good?

So, I guess this post is all about how clever code is actually bad and you should be ashamed for it? I’m just a cranky old man raining on your parade, right?

I hope you stick with me here.

I rarely endorse clever code, unless the alternative isn’t actually clear but is polluted by code overhead/boilerplate/cruft. If you can make code both clearer and clever, great! I think those cases are rarer than others do, but they do exist, and when possible, you should use them!

What are some cases where the clever code is also clearer?

Coercion: Clear & Clever

One case that I like which actually most developers believe you should avoid is coercion, even implicit coercion. I wrote an entire book about why coercion is unfairly shunned and thus overlooked for its benefits to our code. I won’t repeat that book’s argument in detail, but just summarize it here.

Coercion, whether implicit or explicit, takes details of a program that the developer might not need to care about and hides them under the surface of a language mechanism/syntax that handles it for them. The net result is that, used properly, coercion gets the job done, but with less complicated overhead in the code.

For example:

// option 1:
if (x === 3 || x === "3") {
  // ..
}

// option 2:
if (x == 3) {
  // ..
}

Let’s ask this question about that code: does it help the clarity of code to see option 1, where both possible representations for three are listed? Perhaps. But I’d say usually, not.

By contrast, option 2 hides that detail, and favors expressing only that there’s a comparison to a 3 value. In a sense, by using ==, we’re communicating that matching three is what’s important (regardless of type). The former option forces every developer who reads this code to actively think about the two types (string and number) that three can be represented.

Both clarity and brevity align well in this case. Thumbs up from me.

What I’ve found over my years of experience is that more often than not, these sorts of details are safer and better to hide away — what we call abstraction. You could have abstracted the conditional as:

if ( isThree(x) ) {
  // ..
}

Does that help the readability of the code? I think so. And if you care about the details of what makes something three, you can go find the isThree(..) function and inspect it.

Essentially, coercion provides this abstraction as built-in mechanism instead of needing a isThree(..). It can similarly be further investigated as learning necessitates — you won’t find a function, but you’ll look up the simple Abstract Equality Comparison Algorithm in the spec, and your answer will be clear.

If a future developer doesn’t understand coercion, do they have to learn? Yes, yes they do. So why is that any different from them needing to learn the tricks I called out earlier!?

This is the most important take-away from this post, so don’t miss it! Learning a core mechanism like coercion will keep paying off throughout the current and all future code, so the effort is more than worth it. When a future developer on your team has to learn a specific trick that you’ve always liked, that learning (perhaps nothing more than just memorization) often has far less widespread applicability.

Used responsibly, once you’ve learned how it works, == is clever as well as clearer. The alternative to this clever trick is code with more noise that distracts a developer unnecessarily. Learning and using the cleverness and clarity of == will pay off in a far greater scope than just that one line in your code. Using == is not just about rote memorization, but can expose its concepts if you take the time to learn and internalize them.

So, on the whole, the cleverness of == pays for itself with brevity and clarity, even well beyond itself.

Other Clever Clarity

There are other cases of this convergence, too.

I assert (again to the strong disagreement of many) that the ~ operator used to conform -1 based sentinel values to boolean sanity is a lesser of two evils. I fully admit it’s an opaque trick, one you virtually just have to memorize, sans concepts. That’s a high cost.

But the alternative it replaces is similarly memorization-only opaqueness in the form of == -1 or > -1 checks.

// opaque, leaky-abstraction
if (str.indexOf(searchStr) > -1) {
  // ..
}

// opaque, clever trick
if (~str.indexOf(searchStr)) {
  // ..
}

You may hate this trick. I understand. I’m not deeply passionate that you should use it. But I think it illustrates my point of the fine distinctions of subjectivity that are at play when we consider the notions of code clarity. I don’t use this trick for the sake of being clever — I use it because I believe it’s a (mild) improvement over the other option, without any relative loss of learnability.

Want another example? How about this:

switch (test) {
  case 10:
    // ..
    break;
  case 20:
    // ..
    break;
  case 30:
    // ..
    break;
}

Instead of the potential pitfalls of e.g., forgetting a break in a case, you can set up functions to handle each case as properties of an object, and evaluate the test condition once to find the appropriate function:

({
  10: function() { .. },
  20: function() { .. },
  30: function() { .. }
})[test]();

I’d say the latter option is undeniably definitely more clever, obviously more brief, but roughly equal in clarity to the former. A lot of people find it a better choice than the switch statement, if for no other reason than avoiding the missing-break mistake.

Why Clarity Matters

Why does it matter that you spend effort making your code more clear? Isn’t shorter (and/or faster) code the best version of itself? After all, we can always explain things with code comments, right?

Shouldn’t we just expect everyone who reads or works on our code to level up their learning to the highest level or our cleverness? I absolutely believe all developers should be learning and leveling up. But I definitely distinguish between learning code that was meant to be clear and teachable and learning (aka, deciphering) code that was meant to show off its cleverness.

Let me draw this post down with a few observations that I teach every time I’m working with classes of JavaScript learners:

  1. Your source code is only tangentially relevant to the computer.
  2. There’s only one program on your computer that cares about your code: the JS engine. The rest of the computer only cares about 1’s and 0’s.
  3. Modern JS engines with scores of sophisticated tricks and optimizations up its sleeve only use your code as a rough suggestion of what to do. You write a loop, the engine says, “nah, I’ll do it this way instead, that’s better.”
  4. Whatever final sequence of 1’s and 0’s are produced, there are an infinite number of programs that can produce the same sequence. That means the code you choose doesn’t even really matter to the computer.
  5. If that’s true (it is!), why does it matter what kind of code we write or what choices we make?
  6. Because code is, first and foremost, a means of communication, by and for developers. We communicate to coworkers, and even our future selves, with code. Code is for humans, not for computers.
  7. So, we have to care that we’re writing code which helps others know what we mean and what our intentions are. We aren’t going to get any credit or praise from the computer for merely being clever. But we will harm the clarity if we choose brevity and cleverness at the expense of other humans.

ASI: Clear || Brief || Clever?

To come full circle and conclude, I started this post saying this wasn’t really about semicolons. And it’s not. It hasn’t been. It’s about something much bigger and more important.

In light of all these observations, I’ll leave you to ask those same questions of your omission of semicolons. What clarity does ASI bring?

Consider this broken example of ES6 destructuring (courtesy of Adam Rackis). Can you spot why it doesn’t work? (hint: ASI). I leave semicolons in my code because I don’t think omitting them increases clarity, but rather favors brevity on the back of cleverness. It also sets accidental landmines like the previous link illustrates.

I’ve heard all the pro-ASI arguments before, and they don’t really amount to adding clarity. I think they’re nothing but subjective assessments along the lines of shorter is clearer. I hope you can now see why I doubt that claim.

Whatever you decide about semicolons, I encourage and challenge you to approach every line (and line-ending!) thinking closely about the tensions between clarity, brevity, and cleverness.

Whether you realize it or not, every line of code you write is teaching someone else. So, what exactly are you teaching them? And how hard do they have to work to learn it?

;