Death by a thousand inconsistencies ð
I think consistency is one of the most important design guidelines.
When things are consistent, we use our hard-earned knowledge about how things have worked in the past to understand how new things work. Consistency helps us avoid superfluous and distracting choices. You don’t need to think about whether X is different from Y for a good reason, or just because the developer was bored that day. You can focus your energy where it really matters.
I’ve been learning a lot about JavaScript and TypeScript lately. (It is an intentional choice, so this is not a rant by a disgruntled developer forced to use an unfamiliar language.) Doing so has given me ample opportunity to reflect on consistency.
Example: The JavaScript event loop #
Let’s talk about the event loop. A single thread runs non-blocking code off of a single queue. Any other work is put on the queue for later processing. No-one will interrupt your code during your execution. You don’t have to deal with concurrent data access. Good resource allocation. Such a nice and simple abstraction. Everybody’s happy!
Except it’s not true. (If it had been true, this would have been a very different blog post.) To begin with, there is not just one queue. In addition to the ordinary task queue, promises adds a microtasks queue which has higher priority. If you call requestAnimationFrame
, the request is put on yet another queue. And if you work with Node.js it adds a nextTick
task queue as well.
“Ok, so there might be multiple nested task queues, but at least you don’t have to think about concurrent access.” Well… except for when you resize the browser window or the user puts focus on another form input element. When the browser processes these events (and a few more), it just hijacks your stack as it is and appends a new frame to execute the new event handler. When the original task gets the stack back, it has no idea it was ever interrupted and no idea that the state may have changed.
The value of consistency #
This is just one example. Unfortunately it is easy to find more.1 2 3 4 5 6 7 8 9 10 11 12 13 Together they turn what could have a been a nice, simple and consistent language into a quagmire of surprises. (To be fair, it should be noted that the language is moving in the right direction. And thanks to strict mode, linters, and TypeScript, you can get work done with most of your sanity intact.)
However, the real purpose of this post is not to point out flaws in JavaScript. It is to highlight the importance of consistency. It is easy to find faults with JavaScript just because it is so inconsistent. Together these inconsistencies make learning JavaScript harder, and the experience of using it worse. JavaScript also highlights how much effort goes into correcting or mitigating those inconsistencies once they’re released.
So if you want what you build to be easy to understand and use â be consistent! Remember that whenever you make an exception from what was previously a rule, you make the whole thing harder to learn. You remove a little bit of the trustworthiness of the system. You make it worse.
Be consistent!
-
The
class
syntax is just syntactic sugar on top of the prototype-based inheritance mechanism. Except it also adds support for private fields which are not available withoutclass
syntax. ↩︎ -
JavaScript’s type coercion behavior is famously weird:
[] == []
is false but[] == ![]
is true. ↩︎ -
When using
var
, you can reference variables before they have been defined, but not when usinglet
orconst
. ↩︎ -
Objects of
RegExp
are stateless, unless the global or sticky flag is set. Then they store match iteration state. ↩︎ -
Calling
Array(10)
creates an array of length 10, whileArray(10, 20)
creates an array of length two, with elements10
and20
. ↩︎ -
The
Array
type useslength
to denote the number of elements, whileSet
usessize
. ↩︎ -
Higher-order collection functions
map
andfilter
are present in an array’s prototype. ThegroupBy
function is defined onObject
. ↩︎ -
While
null
is a keyword,undefined
is a property on the global object. (And it used to be assignable!) ↩︎ -
The
obj.__proto__
property as a way to interact with an object’s prototype is considered non-standard and discouraged. The{ __proto__: p, ... }
syntax to set an object’s prototype is standard. ↩︎ -
Calling
new Date()
gives you aDate
, but callingDate()
gives you a string. ↩︎ -
The value
undefined
is assigned to (formal) arguments when no value is provided, butNumber()
is0
whileNumber(undefined)
isNaN
. ↩︎ -
The expression
[,0]
produces an array of length two, but[0,]
produces an array of length one. ↩︎ -
I could keep on going all day… ð ↩︎