Programming language debuggability

A few weeks ago I had the fortune to attend a talk by Douglas Crockford on various ideas related to JavaScript past and future and programming language design. The talk was thoughtful and well-received (and thanks to Crockford and Bigcommerce for making it happen!). The main theme, not new to those familiar with Crockford’s work, was that as programmers, we have a responsibility to create bug-free programs; and that therefore when faced with choices about which language features to use, we should always choose the safest ones – that is, we should always avoid the features that can sometimes be used unsafely when a safer feature is available.

On the surface, this seems like a no-brainer. This is the principle behind linters and other static analyzers, including Crockford’s own JSLint. (My favorite is still the less popular javascriptlint.) I try to lint just about all of the JavaScript I write not just before committing it, but each time I run it, because I find so often even while writing small chunks of new code I can easily wind up with super subtle bugs that take a while to debug but lint can catch quickly.

But while static analysis is enormously valuable, it’s not the only consideration for software developers. There are basically two paths for writing bug-free software:

  1. Write bug-free code the first time.
  2. Write good code, then test it, then find the bugs, and then fix the bugs.

This also sounds like a no-brainer, but it actually suggests something different from Crockford’s advice. Linters and static analysis are part of path 1: they attempt to find and fix well-known bugs before the program ever runs. That’s terrific, but it’s incomplete. Lint cannot verify that a chunk of reasonable code correctly implements what the programmer intends. There are always problems you don’t discover until you at least run the code (and in most cases, there will be problems you don’t discover until long after you’ve started running the code in production).

Path 2 accepts that many bugs won’t be found until you run the code, and for that reason making choices about code debuggability should be at least as important as choices about code safety. Some constructs are intrinsically more debuggable than others. JavaScript closures are great, but they do have a debuggability problem: even if you had a debugging oracle that you could use to show you any variable in your program while the program was running (which we basically do), how does the developer even express to the debugger what variable they’re interested in? When variables are referenced by closures and not by any globals, there’s no unique global name for it. It’s important to realize that this is an intrinsic challenge with closures in JavaScript, not a problem with some particular debugger.1 (That doesn’t mean you should never use closures, but this consideration can inform their use. For example, if there’s any state I want to be able to see in the debugger, I make sure it’s not just referenced by the closure, but is also hanging off some object I can find from the debugger.)

Another example of this came up later in Crockford’s talk, when he revealed that he no longer uses new or even Object.create, but instead defines classes using a normal function (not really a constructor) using local variables for instance state and closures to reference that state. Here’s an example (this is my interpretation, not his code):

function mypoint(args)
    var x = args.x;
    var y = args.y;

    return ({
        'getX': function () { return (x); },
        'getY': function () { return (y); },
        'setX': function (newx) { return (x = newx); },
        'setY': function (newy) { return (y = newy); }

There are advantages to this style. You can avoid all the nastiness associated with this, and you can implement strong data hiding. But it has important downsides for debuggability. First, we have the closure problem we talked about earlier: given that you’ve got your program open in the debugger, how do you find a particular Point object? Supposing you do find one, how do you see what “x” and “y” actually are?

But there’s a deeper, subtler problem. Many JavaScript programs run into issues with memory usage (or leaks), and the first piece of information you typically want to debug an issue like that is a report of how many of each kind of object you have. But assuming you had a debugger that could do that, without a constructor name and without any non-function properties, how would you group objects in a way that’s useful for the developer?

Similar to the way there are language features to prefer in the name of safety, there are patterns to prefer to improve debuggability. It helps to name all functions, for example. Even inline functions. This makes stack traces more readable, and it also enables the debugger to tell you that you’ve created 800,000 closures of function “authenticateRequest” instead of “(anon) from file server.js line 37”.

These are just basic examples. I’d love to see the issue of programming language debuggability play a more significant role in discussions about programming language design and in selecting the good parts of a language. With a programming language built for debuggability and first-class tools to take advantage of it, there’d be even more to gain from developers investing time to build, learn, and use tools to help find bugs in code while it’s running or after it’s been run. And that’s how we fulfill our obligation to build bug-free software: not by writing perfect code, but by finding and fixing the bugs.

  1. We addressed this problem in MDB by letting you search for objects by property names, but it’s still far easier if you can find relevant state through a singleton global object than if you have to wade through all objects with a common property like “hostname”. ↩︎