Why Alice Runs JavaScript

Over at The Mad Botter I’ve been working my tail off trying to get a new version of our AI Slack bot Alice out before the end of the new year. As the code has become more complex, I turned to Typescript, Microsoft’s programming language that’s intended to bring static typing and a number of other quality of life improvements to JavaScript. After writing a few classes and a module in Typescript, I switched to pure ES6 JavaScript. Here are my reasons and please do keep in mind that I am writing a large scale bot, so I have some pretty high-end architecture needs.

Classes: As much as I’ve railed against previous efforts to impose classical inheritance and object oriented patterns on JavaScript by efforts such as, Class.js, they’re useful. Alice’s code-base is getting large enough that managing the overall architecture is a problem. For things like data models an object oriented class structure makes a lot of sense as longs as you avoid coupling. So, I went ahead and started writing some Typescript classes such as this simple example:

class Contact {
    fullName: string;
    emailAddress: string;
    profileImageUrl: string;

    constructor(fullName: string, emailAddress: string, profileImageUrl: string) {
        this.fullName = name;
        this.emailAddress = emailAddress;
        this.profileImageUrl = profileImageUrl;
    }
}

Nothing too fancy here. Out of curiosity I took a look at the generated JavaScript for this and was a little surprised:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Contact {
    constructor(fullName, emailAddress, profileImageUrl) {
        this.fullName = name;
        this.emailAddress = emailAddress;
        this.profileImageUrl = profileImageUrl;
    }
}
exports.Contact = Contact;
//# sourceMappingURL=contact.js.map

WOW! That looks basically the same? Couldn’t I just write that and not have to deal with the complexity of having an dist folder full of generated code? Yes, I can, so it seems ES6’s classes have done a pretty good job of mitigating that need from Typescript.

Static Typing: I can feel some of already drafting your tweets pointing out that in the vanilla JavaScript version of the code, I lost all the types. That’s true, but I’m not really sure I care. Sure, static typing has benefits, such as empowering the compiler to catch a number of common programming errors. There are ways around this issue; you could always do what Ruby developers by replacing the compiler type checking with a bunch of unit tests that do little more than check types; sorry, couldn’t help myself there. If you really want the type of static typing protection that Typescript offers, then that might be a reason to stick with Typescript, but I’d urge you read this great post by Eric Elliot before you fully throw down your gauntlet in favor of static typing as a must have.

Debugging: Debugging Typescript just doesn’t work the way I want it to in Node. When an exception is thrown, the line referenced in the exception is not the Typescript code the developer wrote but rather the generated JavaScript code. That’s pretty terrible. The workflow seems to be something like this: you look at the exception in the JavaScript code and have to extrapolate back to the Typescript you wrote and figure out what exactly is causing that behavior. In small code-bases that might not be too hard, but at scale this doesn’t make a lot of sense.

For me, the primary benefit of Typescript ended up being classical OO and the debugging issue became too much of a nuisance to tolerate. I am sure that there is some awesome project out there that I could have integrated into my work-space that would have at least mitigated the debugging issue, but that would again add more complexity to my tool-chain and possibly to my staging and production environment. Let me know what you think in the comments or on Twitter.

 

  • Steven Leadbeater

    Hi Michael, have you ever used source maps with your angular 2 work. You can use them with node and nodemon too. I know it adds another 3rd party library in to the code base but it isn’t very intrusive and gives you the line numbers from your source code rather than the transpiled output for errors. If that is enough to get over the debugging nasties I would suggest that the compiler errors are less overhead to deal with than having to write extra unit tests to ensure that object consumers aren’t breaking the conformance to the expected type. I don’t know how I’d even go about writing that sort of a test. It kind of sounds like you’d have to write your own compiler to parse the code and start inspecting all the objects created in every function and the classes from which they are instanced just to find out if there are any extras added that weren’t on the class or if types are incorrect in assignments. That kind of sounds like rebuilding typescript in unit tests though. Have I missed something here about how you could control this through testing? It kind of seems like an upside down problem. Having to monitor consumers is very difficult to do. It’s much easier to test around what is being consumed by any given piece of code but then you can’t guarantee that what you are consuming is being used correctly without spreading knowledge of its internals across all the consumers. Which is hard to maintain. What am I missing?