Cheap Runtime Asserts: Soft Type Checking
by Guillaume Lathoud, December 2012
Wouldn't you like to catch plain dumb mistakes *early on* during development? Without framework, using the bare minimum that JavaScript offers? And so that you spend more time working on the more interesting parts?
Background (skip)
A «plain dumb mistake» might be to forget some parameter of a function, or to pass the wrong one. This can trigger a whole chain of unintended consequences, which you then have to backtrack.
«Hey, there's a NaN on my screen, can you please look it up?»
Writing each time a test and a throw new Error("...")
could amount to a superfluous explanation of such «plain dumb mistakes»:
function someCoreFunction (text) {
// I must check here, otherwise the consequences will cost too
// much debugging time.
//
// Still... 2 lines for this...
if ("string" !== typeof text)
throw new Error("text must be a string!"); // Yawn!
}
So you might then consider proper encapsulation:
function someCoreFunction (text, duck) {
chkString(text); // extra function call
chkInstance(duck, Duck) // extra function call
// ... Rest of the function
}
// ... somewhere else
function chkString(s) { // extra dependency
if ("string" !== typeof s)
throw new Error("Must be a string!");
}
function chkInstance(x,Ctor) { // extra dependency
if (!(x instanceof Ctor))
throw new Error("Wrong type!");
}
...but this means:
- Extra function calls. Those are non-trivial for JavaScript engines to optimize.
- Extra dependencies to maintain. One more step towards dependency hell [1].
One might debate at length whether these extra costs make sense or not for «core» functions, that is functions that are used in many different places. In any case, since we are only trying to catch plain dumb mistakes, it seems reasonable to consider exploring cheaper alternatives.
What if we just...
...give up on a full check:
function someCoreFunction( domnode, text, duck, /*optional*/func )
{
// "Cheap runtime assert": fails when not a DOM node.
domnode.childNodes.a; // Good enough: quite specific property
// Same idea, for a string.
text.substring.a; // Good enough: quite specific method
// Same idea, for a user-defined object
duck.quackLikeADuck.a; // Good enough: quite specific method
// Same idea, for an optional parameter function
// `func == null` covers both `null` and `undefined` cases
func == null || func.call.a; // `func.call` specific enough
// Rest of `someCoreFunction`
// ...
}
// "Good enough" means: good enough to catch
// most «plain dumb mistakes».
//
// This was a JavaScript example. Cheap runtime asserts
// could likely be used in a few other languages, too.
It helps to catch mistakes early, including during development. I am *not* advocating to litter the code with thousands such statements, but in a few strategic places the benefits can exceed the costs.
Hopefully this can help someone. Constructive feedback is welcome.
Type coercion vs. type checking
Here is a clear feedback I received from Matthias Reuter on the JSMentors list [2]:
My first thought was: Closure Compiler will strip it out. But no, it does not (even in advanced mode) (it issues a warning, though).
My second thought was: I don't want Exceptions in my code. Neither explicit nor implicit. Then I thought, the Exception would occur nonetheless, when I later access the element.
My third thought was: I prefer type coercion over type checking. If text is not a string, then convert it to one. Makes the functions more flexible. But what about dom nodes? How can I convert any other type to a dom node? That's when I started to like the idea.
It helps to catch mistakes early, including during development. I am *not* advocating to litter the code with thousands such statements, but in a few strategical places the benefits can exceed the costs.I agree over the few strategical places. I would always try coercing first, and in the few places that remain think about your idea.
Matt
Development vs. Build
Here is an interesting critic von Stefan Weiss on comp.lang.javascript [3], along with my answer:
I see two problems with this approach:
1) This appears to be intended primarily for the development phase. Most other assert-type runtime checks have a centralized on/off switch, in order to enable or disable debugging aids like this. Not only is there no way to globally deactivate them, there is also no easy way to find them (nothing specific to grep for).
On the other hand, if these statements are intended to remain in the code after development, the potential error messages that the users will see (and submit as bugs) are not going to be very helpful: "TypeError: cannot read property a of undefined" - which of the "asserts" in your example triggered this?
2) This type of statement will cause linting tools and other code processors to emit warnings (and rightly so). If you intend to use something like JSLint or Google's Closure compiler, you'll have to sift through a lot of irrelevant output in order to find the real warnings.
- stefan
Yes, good points. If you decide to only check while developing, either delete the "cheap check" lines after developing a few inter-related components to a stable state, or write full checks that are thrown away during build.
Without disagreeing with you, I feel uneasy with a code that behaves differently during development and on the live website. This is a matter of choosing a methodology. Concerning the second point, I am questioning whether those warnings are "right". Should the tool limit the programmer, or adapt to his needs? Again, this is a matter of choosing a methodology.
Now concerning this:
the potential error messages that the users will see (and submit as bugs) are not going to be very helpful
either:
the "users" are programmers, and will most likely set their browser to break on error, and thus immediately see that the required value is e.g. `null` or that they wrongly swapped parameters in a function call. They can then correct their call themselves. Without check, they'd most likely see a consequence further down, which they are much less likely to link to a function call they made - then they are actually less likely to fix it themselves, thus more likely to submit a bug report. Another bug, which you'll have to backtrack to its origin.
or:
the "users" are not programmers and are anyway NOT going to debug, but rather to submit a bug description focusing on the use case leading to the error, whatever the error is.
References
[1] http://steve-yegge.blogspot.de/2007/12/codes-worst-enemy.html
[2] https://groups.google.com/forum/?fromgroups=#!topic/jsmentors/XT--KpKyHqc
[3] https://groups.google.com/forum/?fromgroups=#!topic/comp.lang.javascript/3m0bjFGlPzA