overwatering.org

blog

about

This is the second post in a series on observations and questions from implementing a single page pure JavaScript web application for a recent project. For background and the other posts in this series, see the introduction

Before joining this project I spent two weeks on another project helping them make a technology decision. This other project had a requirement for a sophisticated web UI. The choices were Flash or JavaScript. After helping with some spikes the team decided to use JavaScript. As this would be the first time anyone on the team would be using JavaScript, I made some recommendations. One of these was not to use CoffeeScript.

I was wrong.

It was a new project with a team of people who weren’t very familiar with JavaScript and certainly had not written a large JavaScript application. I felt that CoffeeScript would be another new thing for what until very recently had been a Java team. It would be better to first learn JavaScript; understand that and then learn how CoffeeScript can make the language better.

After that short consulting gig, I started on this project on the following Monday. This was another sophisticated JavaScript UI application. But it was already about two months into development. This project had already decided not to use CoffeeScript, for similar reasons as I’d gone through on the previous Friday. It was like jumping forward in time two months. More than anything else, with the amount of JavaScript code we had, the syntax was just agony. It simply made the code harder to read. This may sound like a small detail, but I believe very strongly that code needs to be readable by humans above everything else. JavaScript’s syntax is simply woefully inappropriate for the style of programming the language is otherwise very tuned for.

As I mentioned above, we used underscore.js pretty heavily. This encourages a lot of anonymous functions passed as parameters, something that reads very nicely in Ruby. In JavaScript, a simple loop to lowercase every string in an array reads pretty painfully.

var keyNames = _.map(recipientNames, function(name) {
  return name.toLowerCase();
});

By the way, the glyph }); is henceforth known as the coffee-break.

The absolute bare minimum for a lambda is function() { return; }. That’s 19 characters! 19! Here’s the same code in CoffeeScript:

keyNames = _.map recipientNames, (name) -> name.toLowerCase()

That is one plausible line. Yes please, can I have some more? This may seem like a small detail on its own. It’s just syntax, you say. You may disagree with my opinion that syntax is critical. Allow me to offer another example from this project. A couple of us are Ruby programmers, and fans of the very declarative style of test that is possible in RSpec, using let.

describe "an account" do
  let(:subject) { Account.new(:balance => balance) }
  describe "positive balance" do
    let(:balance) { 100 }
    it "should allow withdrawals" ...
  end
end

The JavaScript unit testing library of choice, Jasmine doesn’t (yet) include any equivalent of this. So I whipped something up pretty quickly to allow the equivalent:

describe("an account", function() {
  define({subject: function(ctxt) { return createAccount({balance: ctxt.balance()}); }}, function(ctxt) {
    describe("positive balance", function() {
      define(ctxt, {balance: function() { return 100; }}, function(ctxt) {
        it("should allow withdrawals", function() { ...; });
      });
    });
  });
});

There are two problems here. Firstly, JavaScript doesn’t have instance_eval or method_missing so you can’t decorate a lexical scope before passing it downwards, hence explicitly passing down the ctxt parameter. Actually, while the lack of method_missing is a real pain, I think instance_eval is no great loss and I would happily write much, much code in a language that doesn’t allow libraries to mess with name bindings quite so liberally.

Secondly, the sheer noise of the JavaScript syntax is incredibly painful. Here is the equivalent, translated directly into CoffeeScript.

describe "an account", ->
  define
    subject: (ctxt) -> createAccount balance: ctxt.balance()
  , (ctxt) ->
    describe "positive balance", ->
      define ctxt,
        balance: -> 100
      , (ctxt) ->
        it "should allow withdrawals", -> ...

The most interesting thing from this conversion, to me, is that it shows that the double-nesting of defines and describes is actually pretty unclear. In my copious spare time I’m working on a better implementation of that define() function, in the hope of getting it added to Jasmine. If I’d been staring at that second version throughout the project I would have started down that path earlier.

But the better syntax is not everything; the other features in CoffeeScript, like list comprehensions and default arguments are Good Things and would have been very nice to have.