Discriminating user input errors from other errors

(This entry is a follow-up of my design-by-contract blog entry a few days ago.)
Last night, while working on certain user-interface scripting, I caught myself using assert() to validate user input. This is not smart. The assert() function is intended for bugs in the source code. It only throws exceptions or warnings if there’s a certain debug flag activated. Since the end-user product will have that debug flag off, the assert() does no good for checking user input.
There has to be some other bailout mechanism in place. Firing an exception with the throw statement works great… if you have something to catch it. But we only want to catch certain errors.
The first step is the creation of an exception constructor which is unique. In my assert() function, each error which leaves it has a name property of “AssertionError”. This is unlike any other error JavaScript natively supports. It also identifies a particular class of exceptions to the application.
So, if I want a unique class of errors to catch, one of the best ways is to make sure each error created in that class has a specific name property.

function UserError(aMessage) {
var e = new Error(aMessage);
e.name = "UserError";
return e;
}

Then I can go back to the old-fashioned if statements to check user input:

if (userDidSomethingWrong) {
throw new UserError("You broke one of my rules!");
}

Okay, so what catches this error before it hits the JavaScript Console?
In my particular application, I have a global object called editor. Each XUL button to execute a user-interface function would call a method of this editor object. For instance, if I want to do a search, I call editor.search().
Because of the beauty of the apply method of functions, I can do a little redirection and call another function instead which will then itself call search. (The apply method allowed me to write the constructNew function I talked about in my ecmaDebug.js script. The constructNew function may get renamed, though…) So, why not set up a special try…catch for user errors in an intermediary function?

editor = {
search: function() {
throw new UserError("search failed");
},
watchUserErrors: function(methodName) {
var args = [];
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
try {
this[methodName].apply(this, args);
}
catch(e) {
if (e.name == "UserError") {
doSomethingWithTheError(e);
} else {
throw e;
}
}
},
toString: function() {
return "[object AbacusEditor]";
}
}

Now I can instead call editor.watchUserErrors('search').
One other possibility exists. If you note how the assert() function processes its results, it's almost a macro.
assert(false, "The sky is falling!", true);
works much like:

if (!false) {
throw new Error("The sky is falling!");
}

Only the last argument of the assert() makes it an error instead of a warning. Of course, if I can create this macro, I can create another:
assertAboutInput(false, "The sky is falling!");
might work like:

if (!false) {
throw new UserError("The sky is falling!");
}

With ecmaDebug.js included, I can simply write:

function assertAboutInput(mustBeTrue, errMsg) {
var throwException = arguments.length > 2 ? arguments[2] : false;
try {
if (!mustBeTrue) {
var aError = new UserError(errMsg);
throw aError;
}
}
catch(e) {
e.shiftStack(2) // this takes us to the actual stack error.
if (throwException) {
e.message = errMsg; // reset
throw e;
} else {
warn(e);
}
}
return mustBeTrue;
}

I'm undecided about that throwException default. Setting it to false means consistency with the standard assert() macro. Setting it to true means the developer doesn't have to override a bias towards warnings when errors are the true intent of the function. (Developers can use a false value for throwException to indicate deprecated methods, for example.) I prefer consistency over bias...

2 thoughts on “Discriminating user input errors from other errors”

  1. As you are in a tag for the code why do you add a at the end of each line – it makes line too spaced and is hard to read so…
    Either remove the – or better all the … but maybe it is abug of the BLog editor?

  2. Hm, the blog garbled your text, I think… as for the spacing… hey, I put it in as <pre> elements. That’s Movable Type for you.

Comments are closed.