Friday, March 14, 2008

Faster failing JavaScript

Though having applied JavaScript as a 'necessary evil' ;-) in the web applications I work on, I have rarely delved into its powerful OO and meta-programming capabilities. Then, last week, I built a small HTML component that could be opened from (or embedded in) any other HTML page. Values would be retrieved and returned through a small number of callback functions. Consequently, these callbacks became the contract between my little component and any page that would open/embed it.

Not being able to enforce such an 'interface contract' in the dynamically typed JavaScript posed us with a maintainability challenge. So far we've not been able to automate testing of the mostly context specific JavaScript within our dynamically generated HTML and therefore we have always needed to rely on the intrinsically unreliable manual labor of clicking through the application to see if everything works the way it should. In this modus operandi the addition of a new feature can easily break an existing feature, and that mistake can go unnoticed for a long time if the original feature isn't used much, even though it might be vital for the overall quality of the product. Fortunately, our sharp test team would catch it in Beta most of the time, but that of course is not nearly as efficient as finding and dealing with it in the development stage. Therefore we now asked ourselves the question how we could help ourselves noticing breakage as soon as possible in case the 'contract' to the component would need to change in the future and some dependent HTML pages would get overlooked in that refactoring.

I was aware that others had run into similar challenges with the also dynamically typed Python language and came up with tools like Zope interfaces and PyProtocols, neither of which I have used or studied, because... in my small spare time projects the need just hasn't arisen. :-) Even though so far I have not been able to find a similar solution in the land of JavaScript (filled with many powerful libraries/toolkits of which I only know the name, if at all), I find it hard to believe that nobody has picked up this challenge and tackled it, so I probably did not search thoroughly enough. My apologies for reinventing that wheel, though I admit to greatly enjoying the exercise. It certainly helped me grow a better understanding of JavaScript's OO mechanics.

Here is the simple utility that I came up with:
/**
* JSInterface is a class that allows to specify an interface and
* test/assert whether an object implements it.
*/
function JSInterface(functions) {
this.functions = functions;
}
Function.prototype.getName = function() {
if (this.name == undefined) {
// necessary for IE; not for FF
this.name = this.toString().match(/function\s*(\w*)\s*\(/)[1];
}
return this.name;
}
JSInterface.prototype.assertCompliance = function (obj) {
for (i in this.functions) {
functionName = this.functions[i].getName();
f = obj[functionName];
if (!f || typeof(f) != 'function') {
throw "Interface compliance assertion fails: '" +
functionName + "' is missing on object.";
}
if (f.length != this.functions[i].length) {
throw "Interface compliance assertion fails: number of arguments for '" +
functionName + "' is different from what's expected.";
}
}
return true;
}
JSInterface.prototype.testCompliance = function (obj) {
try {
return this.assertCompliance(obj);
} catch (e) {
return false;
}
}
JSInterface.prototype.signalIfNotCompliant = function (obj) {
try {
return this.assertCompliance(obj);
} catch (e) {
document.write(e);
document.close();
alert(e);
document.location = "about:blank";
}
}

With this JSInterface class in place, a specific interface can be declared as:
/**
* FooEmbedderContract:
* required to be implemented by pages that embed the Foo component
*/
FooEmbedderContract = new JSInterface([
function getFooInput() {},
function getBarStatus() {},
function setFooStatus(status) {},
function setFooResult(result) {}
]);

Can you tell that I attempted to design this to somewhat resemble a Java interface definition? ;-)

The idea is to link to this code (should be in a separate .js file) in all pages that intend to implement the contract and put the "fail faster" check after the functions that implement the interface, like so:
function getFooInput() {
return document.getElementById("fooInput").value;
}
function getBarStatus() {
return barStatus;
}
function setFooStatus(status) {
fooStatus = status;
}
function setFooResult(result) {
document.getElementById("fooResult").value = result;
barForm.submit();
}

FooEmbedderContract.signalIfNotCompliant(window);

The FooEmbedderContract object simply tests whether all functions that should be in window are there, each with the same number of arguments as specified in the interface definition. The signalIfNotCompliant function is meant to give immediate feedback in an HTML document context (although that feedback looks really raw in this implementation, it simply is not supposed to ever occur in a production situation), while the assertCompliance and testCompliance functions can be useful in a pure JavaScript context.

It is a good idea to also make the interface contract explicit within the component itself (increasing readability of the code), by testing the opening/embedding window against the interface at the start of the component's HTML (effectively carrying out the same test twice, but that should not really be a problem performance wise).

This approach doesn't of course take away the need to test the application by clicking through it, but it can signal problems with features behind links and buttons that are not yet clicked (and might not get clicked in a hurried test). As we're optimizing for failing as fast as possible in case of an error, this utility should at least help in 'failing faster' than would have been the case without it. Now, for maximal benefit, we have some work to do in starting to introduce these interface declarations and assertions in more places in our existing code base...