Sunday, August 15, 2010

JavaScript...For Good or Ill

So, I've been spending a lot of time working with JavaScript, lately. For whatever it's worth, I've been thrust into a role in which I find myself working with a large volume of JavaScript code that needs to be maintained and it falls on me to do that.

Now, generally speaking, in my eyes, code is code. I don't really care what language you're writing in, but some things are just generally true regardless of the language. Cryptic code is to be shunned At All Costs™. A coding standard--even a minimal standard--should apply. Variable names should be clear. The code should document itself, mitigating the need for comments. Every variable should be predeclared. And so on, and so on, and so on.

But over the long course of my career, I've observed something about scripting languages like JavaScript and VBScript. There's just something about them that seems to encourage people to write bad code.

I've seen and had to debug a lot of script code in my lifetime. And that code tends to be riddled with subtle and not-so-subtle defects that could easily have been avoided if we simply treated scripting languages like they were real languages and not toys.

Don't Blame the Browser. One could argue that many of the defects that crop up in script code are due to browser incompatibilities. But let's be honest with ourselves: it's our job to know what those incompatibilities are and write the software to take them into account so that script errors don't occur. If script errors are occurring "due to a browser incompatibility," that's not really the reason: they're occurring because the developers didn't account for the browser incompatibility.

Functions, Formal Parameters, and Arguments. How many times have you seen a function that takes a set of arguments and then starts working with them without performing any sort of argument validation? How much time have you spent scratching your head, trying to figure out what type of data the function expected to receive for any given formal parameter? And have you ever wondered what guarantee the function has that it is going to receive arguments of the correct type, or that it will behave correctly if it doesn't?

The truth is, the vast majority of the functions in script code never check their arguments to ensure that they are what they expect them to be. They don't check to ensure that the values are present, that they're of the correct data type, that they fall within the allowed ranges or have the right formats. Any number of errors that occur in the browser in front of end-users in production could be avoided if those checks were put in place early on, when the functions were first written, and a developer found out about them during development.

But we're lazy, and scripting languages somehow encourage us to be even lazier. I'm not sure why this is so, it just seems to be so. Now, when ou consider a language like JavaScript, where any variable, object, or function can be modified at any time by anyone, it's not a good idea to assume that the arguments you've received are what you expect them to be. To quote a classic mantra:

Assert, assert, assert.


Assume Nothing. Too often I'm presented with code that grabs an element from the document using document.getElementById. This code then proceeds to use the control as if there was never any doubt in the world that the element existed.

If you're writing code like this, stop it right now. What if the content of the page unexpectedly changed on you (a write to document.innerHTML), or you're reading from an iframe and it doesn't have the correct document loaded into it? What if the control you're after was never created? If you aren't checking for these conditions yourself early in the development cycle, the customer is bound to find out for you.
var theElement = document.getElementById("myElement");
if(theElement === null)
{
throw new Error("myElement was not found.");
}
else
{
// Code to work with the control.
}

We tend to make these kinds of assumptions all the time in scripting languages. We assume that a property or method exists on an object, that a given variable is an array, that a variable is not undefined or null, that a variable has not been preinitialized by someone else to hold a value that is different from what we want to store in it. All of these are unsafe assumptions. Unsafe assumptions lead to subtle defects that are notoriously difficult to track down and correct. We need to make it a priority to ruthlessly eliminate them by adopting a Zero-Assumption policy.

What Happened to Black Box Programming? Remember the idea behind black box programming? The basic principle is simple:

A function or object knows nothing about the outside world except what is passed into it.

At some point in your career, you should have been introduced to this fundamental concept. Functions and objects don't rely on global variables. They just don't. Everything they need to know is passed to them through their formal parameter list. In this way, the function has an opportunity to validate that its arguments are sound, and it is decoupled from its host so that it can be tested more easily.

These days, we push the notions of loose coupling and high cohesion to explain black box development in greater detail. We also talk about things like inversion of control, which should be nothing new to anyone who's ever written an event handler before. In short, the function assumes nothing about its external environment (sound familiar?); we pass it everything it needs to know to get its job done.

But if you look at JavaScript or VBScript code you will be inundated be vast swaths of code that references global variables with complete abandon. Of course it does. Scripting languages embrace global variables like flies embrace a fertilizer factory.

  • History Lesson #1: global variables are a loaded weapon with a hairpin trigger. When you give that many sketchy loaded weapons to people with no training, Bad Things Will Happen.

  • History Lesson #2: Humans frequently fail to learn from history.

I implore you. I beg you. Please, please stop using global variables. It is, of course, necessary atthe topmost layer of your application, but once you're past that, there's no reason whatsoever for your functions and objects to have any knowledge of global objects (unless they're provided by JavaScript or VBScript itself). Do not assume anything about the global,document or top objects. When you write a function, if it needs some piece of information to do its work, insist that your callers pass it to you.

In Closing... I think you can see where I'm going with this. We've got decades of script code out there, and a lot of it is really badly written. But if we want to be perfectly honest about it, we have no one to blame for its state but ourselves. We look at what JavaScript can do, and we never stop to ask ourselves if what it can do is somethig we should do.

Making a bad situation worse, we don't apply the same disciplined coding practices to JavaScript that we do to languages like C++, C#, Java, or even VB.NET. And that's a shame, because there's nor real reason we shouldn't or couldn't. Perhaps, in the near future, we'll see things like code contracts, automated unit tests, argument validaton frameworks, StyleCop for JavaScript, and so on. Pipe dreams, probably, but it would be nice if we could have all of that and take a huge, collective leap forward in improving the quality of the script code we have to maintain every day.
Blogged with the Flock Browser