guards
Version:
Data type & structure checking, runtime analog of types
69 lines (53 loc) • 2.12 kB
JavaScript
/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint asi: ture newcap: true undef: true es5: true node: true devel: true
forin: true */
/*global define: true */
// Sometimes you want to validate an incoming value to determine whether to let
// it pass, coerce it, or reject it. Here is an example:
function Point(x, y) {
return { x: x, y: y }
}
// It's clear that `Point` function expects `x` and `y` to be a numbers, but
// what if it's called with other values ?
var p1 = Point(1, 'Boom!')
// `Point` will just return invalid value, causing further misbehavior of the
// program. To avoid further propagation of an error, we can employ runtime
// value validation:
function Point(x, y) {
if (typeof x !== 'number')
throw new TypeError('Expected number not a: `' + x + '`')
if (typeof y !== 'number')
throw new TypeError('Expected number not a: `' + y + '`')
return { x: x, y: y }
}
// Unfortunately, we ended up with a lot of code that is not relevant to a
// `Point`. All we actually need is a guard of `x` and `y` properties that
// either accepts values or throws. Let's refine `Point` by refactoring value
// guarding logic into separate function:
function number(value) {
// If not a number reject!
if (typeof value !== 'number')
throw new TypeError('Expected number not a: `' + value + '`')
// Otherwise pass!
return value
}
function Point(x, y) {
return { x: number(x), y: number(y) }
}
// Ok this looks much better but would be nice if our error message was less
// generic so we could give a more descriptive error message, and preferably
// we should not make `number` function too specific so we can reuse it in other
// places.
function Numbers(message) {
var message = message || 'Expected number not a: `{{value}}`'
return function number(value) {
if (typeof value !== 'number')
throw new TypeError(message.replace('{{value}}', value))
return value
}
}
var number = Numbers('Point only takes a number arguments not a: `{{value}}')
function Point(x, y) {
return { x: number(x), y: number(y) }
}
// Maybe we can also improve our