3
\$\begingroup\$

I am writing a JavaScript application and would like to validate function arguments in many places. Most of these checks will be for correct argument types, or numeric values within specific ranges.

Here is a simple function I came up with to check any number of preconditions:

function requires(conditions)
{
    for (var cond in arguments)
    {
        if (typeof cond !== "boolean") throw "Preconditions must be boolean expressions {" + cond + "}";
        if (cond !== true) throw "Precondition failed {" + cond + "}";
    }
}

It would be used like this:

function alertChar(message, index)
{
    requires(
        typeof message === "string",
        typeof index === "number",
        index >= 0,
        index < message.length);

    alert(message.charAt(index));
}

Is there anything conceptually wrong with this simple implementation? Is there a way I can make it lazily evaluate each precondition expression? Is accessing the arguments array inefficient?

\$\endgroup\$
3
\$\begingroup\$
function requires(conditions)
{
    for (var cond in arguments)

Don't use for-in. The problem with for-in is that it runs over prototype properties as well. You might loop through more properties than you expect. Either use a regular loop or convert your iterable into an actual array to access array method.

If you can write in ES6, you can use the rest operator. What it does is put the rest of the arguments into an array. Much like arguments but it's a real array. You have access to all array methods.

function requires(...args){
  args.forEach(...)

if (typeof cond !== "boolean") throw "Preconditions must be boolean expressions {" + cond + "}";
if (cond !== true) throw "Precondition failed {" + cond + "}";

The second condition is overly verbose. The first check weeds conds that are not boolean. Therefore, if it passes that part, cond is a boolean. That means you can simplify if (cond !== true) as if (!cond).


requires(
    typeof message === "string",
    typeof index === "number",
    index >= 0,
    index < message.length);

If you go on with this route of manual type checks, at least hide away the complexity by abstracting the checks away. The following would be more like it. Checks the arguments against the type, and in that order.

require(args, ['string', 'number']);

Additionally, the last 2 checks there are not type checks but more like app-specific logic. Your type checker should not concern itself with those anymore.


Runtime type checks should be avoided as they incur performance penalties as well as cause bloated code. Common practice is to offload this responsibility to development-time and/or build-time tools.

TypeScript and Flow are typed supersets of JS that will let you write in a typed language and whose compilers do type checks for you.

If you prefer vanilla JS, using an editor or IDE that utilizes JSDoc to do basic checks is also an option. I know Visual Studio's Intellisense or Intellij IDEA with the right plugins do this.

\$\endgroup\$
2
\$\begingroup\$

If I understand you correctly, you want to use this code to verify that your program is behaving correctly (like a runtime assertion), rather than to validate user input. I suggest throwing new AssertionFailure(…) rather than throwing a string, in case you want to catch these errors.

The failure message isn't of much use, though. All you will ever see is a stack trace with the message "Precondition failed {false}", and nothing more informative than that. You can't reliably tell which of the four conditions in your example triggered the failure. As you noted, all of the checks will be performed, even if the first check fails.

I think that

if (typeof cond !== "boolean") throw "Preconditions must be boolean expressions {" + cond + "}";

… is overkill. You're asserting that your assertion is written correctly. Will the self-doubt never end? Any expression can be considered truthy or falsy in JavaScript anyway, so what's the harm in dropping that check?

The named parameter in function requires(conditions) is a bit odd, since you never actually use conditions as such. I'd prefer to write it as function requires(/* conditions... */) … but that's a nitpick and just my personal preference.

\$\endgroup\$
  • \$\begingroup\$ Yes, basically I want runtime assertion, not user input validation. I am interested in using this to make it easier to pinpoint the source of problems when debugging. I only used the validation tag because I couldn't find one for design-by-contract which is what I was really looking for. What is AssertionFailure? The only examples I've seen of using the JavaScript throw keyword just throw literal strings or error codes. \$\endgroup\$ – JamesFaix Sep 19 '16 at 13:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.