Principles for safe and clean JavaScript

Perhaps you like writing JavaScript. Perhaps you also like poking your eyes out with sticks. The rest of us like type-safety, a clean object model, and being able to assert against our own stupidity.

Yet no matter how much we loathe it, no one can avoid writing JavaScript. Any half serious product will eventually need a website, and that website is going to need JavaScript. Here are some principles that, when applied, may reduce the probability of insanity.

No vars

Don’t spray your scope everywhere. If you write var, you’re doing something wrong. There is no circumstance under which you should need var over const or let. If you need a variable in a higher scope, then put it there explicitly, don’t imply scope by hammering it with var.

// Bad
function bloop(pigeons) {

   if (pigeons > 42) {
      var output = pigeons + '!';
      console.log(output);
   }

   // `output` has no business being
   // in scope here. Use let instead!

}

By restricting scope, you restrict the shit your brain needs to reason about. Your brain is a pile of mush that has trouble thinking about more than six thing at once. Throw it a bone.

And before you say ‘but not all users have ECMA XXXX’, let me stop you. You’re not Google. Settle down. Whatever tiny proportion of your users don’t have access to modern syntax are not material to your business.

In fact, allowing dinosaur devices to use your service is to do them a disservice. They’re a security risk to themselves and to you. Let the ancient devices go. Just let them go.

Prefer const

You’re already going to be suffering enough pain trying to untangle your types. At least you can protect yourself against accidental mutation. const everything unless you are absolutely sure you need to mutate. Then, ask yourself if you can redesign your code to avoid mutation.

const AWESOME_SUFFIX = ' is awesome!';

function preach(truth) {
   const output = truth + AWESOME_SUFFIX;
   console.log(output);
   // We can't accidentally mutate
   // output.
}

preach('Immutability');

Enforcing immutability allows you to spend less time reasoning about state, and more time reasoning about the problem you are trying to solve.

Build your own types

The days of something.prototype.what.was.the.syntax are gone. Let them be gone, and lean on an object-oriented approach.

const DESCRIPTION_RAGE = 'induces rage!';
const DESCRIPTION_CALM = 'is quite pleasant.';

class Language {

   constructor(name, induces_rage) {
      this._name = name;
      this._induces_rate = induces_rage;
      return;
   }

   description() {
      if (this._induces_rage) {
         return this._name + DESCRIPTION_RAGE;
      }
      return this._name + DESCRIPTION_CALM;
   }

}

const SWIFT = new Language(
   'Swift',
   false
);
const JAVASCRIPT = new Language(
   'Javascript',
   true
);
const PYTHON = new Language(
   'Python',
   false
);

If you ever find yourself wrangling low-level types outside a method, you’ve probably got yourself a good case for defining a custom type. Modern JavaScript syntax makes it super easy and there is no excuse to avoid it.

Avoid 3rd party abstraction layers

Hey there, welcome to 2018. The days of needing jQuery are long, long gone. Browsers are generally highly standards compliant. Yes, document.getElementById('bleep') will just work.

There is no point in abstracting the core DOM API anymore. You’re not supporting IE6 and if you are, just get out, don’t @ me. The only thing that DOM API abstractions are good for are:

  • Bloating page weight
  • Excessive allocations
  • Making ~2009 school JavaScript devs who never moved with the times feel good about themselves by validating their desire to start every line of code with a dollar sign despite the fact that there are no material benefits and they just end up splintering community knowledge into needless silos while wasting precious cycles

Check yourself while you wreck yourself

It’s not easy to practice safe JavaScript. My go-to condom in Python is assert, and in Swift it’s guard. Meanwhile, running JavaScript is like rolling around naked and blindfolded on a Thai beach during a full-moon party.  You can’t be sure what’s going to happen, but you can bet it’s not all going to be enjoyable.

You can take some action to protect yourself. throw liberally where you make assumptions. That way, your code will at least hard crash in the off-nominal case, rather than merrily trucking on with an undefined just waiting to ruin your day.

const INVALID_INPUT = 'oink oink';

class InputField {
   
   constructor(element_id) {
      
      this._element = document.getElementById(
          element_id
      );
      
      if (!this._element) { throw 'Abort!' }
      return;

   )

   is_valid() {
      
      const value = this._element.value;
      if (value === INVALID_INPUT) {
         return false;
      }
      return true;

   }

}

In the above case, the throw catches a case in which we supplied a non-existent DOM element id. Without the throw, execution would proceed and we would have no indication that shit was sideways until we called is_valid().

Lean into the wind

JavaScript is here to stay. Websites are important parts of product development. No matter how much you dislike it, it is important to develop JavaScript skills. Do it with a bit of modern, clean syntax and it can be less painful that you might expect.