CoffeeScript Under Pressure
mattdw.github.com
mattdw.github.com
> It wasn’t just fancy HTML we had to build; it was a port of a
> graphic- and animation-intensive Flash app, with the polish
> intact,1 and it had to work on iOS. It had to work on IE7.
> It had to work on Android 2.1 on one of the clients’ phones.
> And we had six weeks. We did the maths and figured that at
> twelve hours a day, six days a week for six weeks, we might
> just sneak in.
> [...]
> There were also three levels of stakeholders above us, with
> different priorities, and constantly shifting (and always
> growing) specs cascading down from above as the site took shape.
>
> After some discussion with my ‘if I get hit by a truck’ backup
> developer, I decided to add CoffeeScript to this, despite no
> real experience with it.
Then you're a brave, brave man. After this intro, I fully expected to hear a disaster story of missed deadlines and feature cuts ... not a successful project and a Super Bowl launch.I think it goes without saying that "on deadline" is probably not the best time to learn a brand new JavaScript dialect. I'm relieved to hear that it all turned out for the best.
You mention that you're thinking of adding an explicit validation step -- great idea. For CS & JS (which can be compiled and validated quickly), even better than having it run when you build is having it run every time you save the file in your text editor...
After working hours and hours on a Coffeescript framework, I can say thinking in Coffeescript finds a different place to exist than Javascript or any other language. I particularly made use of object and array structuring, destructing.
Given no tooling that I liked in the Java realm I work in, I made my own compiler using Rhino and Coffeescript.js.
Within a month I had accomplished what would've taken me 3 months in Javascript. Within two months I had rewritten my original framework to Coffeescript paradigms that were emerging from the code AND I was adding features to my framework rapidly. My Coffeescript code came in about 55% of comparable Javascript. In framework version 1 it was about 75% of comparable Javascript.
My biggest critique of the article is the process. The author picked a tool he are not familiar with, probably produced sub-par code, and then for the next project aims to pick something else they appear to be equally unfamiliar with. That's great for padding the resume and getting the next gig, but terrible for producing high quality.
It's fantastic that everything worked out in the end, and the CoffeeScript pain point analysis is nice to have so clearly put, but still, my mind is fixated on that original "Why?"
There was never any question of "can we actually do this?", just "can we do this in time?" Hence picking a tool that looked like it might offer some shortcuts. Yeah, it was a hell of a lot of work, but that was always going to be the case, and I accepted the project on those terms.
(My post perhaps came across as more extreme than the reality. We delivered. The site works. The code never got to 'unmanageable'. Given the opportunity to refactor this particular site, I'd leave it in Coffee, although I'd rework the build and deployment process.)
The author is right: this is annoying.
Now, the correct way to solve this would probably be to either not check in generated files, to leave the JS out of the repo, or, if that's not possible due to politics, then use the same version of coffee which I would highly recommend anyways because it gets rid of really bad WTFy issues if one version of the compiler has a bug which manifests itself in an application bug which will then only be seen if the last commit was done by the person with the broken compiler.
Anyways. Let's say you can't use the same compiler and you have to check in JS in addition to coffee (you are not trying to sneak in coffee by ONLY committing the JS, are you?)
Now we have the perfect oportunity to show off the git index:
Instead of committing the whole file, you would use add -p to only select the changes which are actual code changes and not changes in compiler output.
Then you would only commit those changes and then undo the unneeded stuff and test again, to ensure that you added the right lines to the index (commit --amend or rebase -i if not).
This will give you a much cleaner history, which will be a huge help when blaming or reviewing the code. No more annoying flip-flopping between compiler output styles. No more meaningless changes in a commit. All the changed lines in the diff are the lines that count. The lines you should look at when reviewing. No risk of missing the trees in the forest.
But, you might say, history rewriting is bad. I don't want to commit something I haven't tested.
Remember though: you haven't pushed yet. You haven't altered public history yet. Nobody knows about those commits yet. You have all the time for testing, patching and massaging commits. Only once you pushed, your changes (should) become set in stone. Only then, history gets written. Only then it can't be changed any more.
This is why I love rebase and I'm really glad I finally found a good real-world example to show why.
In hindsight (1) I should have used something other than Make for my build process and (2) I should have figured out a way to move some of the build upstream.
Things to get right next time.
Thinking about it further, I think the tone is due to the level of humility you get from someone who is really experienced.
Two years and 60k LOC later, (www.activityinfo.org) I'm not entirely satisfied with the tool -- sometimes more abstraction introduces new problems to be solved, and it's hard to find good, affordable java devs to work on the project here in NL, but it does IMHO bring tremendous advantages in terms of modularity, dependency management, and generally keeping the code base maintainable. And the optimizing compiler is nothing short of amazing.
I realize the idea of Java-to-Javascript is sacrilege for many, but if you've looking at building a large, single-page javascript application, I think it's worth taking a look at.
Used tdd for almost everything even clicking ui widgets to populate from a rest resource and then validating the result. CS rocks!
I've considered learning CoffeeScript but have decided for now to let things play out and focus on learning JavaScript better. There are a ton of frameworks for javascript (backbone/spine) that make it more bearable. Also JS for all its quirks is ridiculously powerful.
To me maintainability is huge in a project. There needs to be a really strong reason to choose CoffeeScript over plain javascript. Catching all syntax errors seems okay but not entirely justified. I usually catch those in a staging environment on a console.
On the second and third project, I am also teaching coffeescript to 3 people who had never written any coffeescript OR BackboneJS before.
Granted, there is a fair amount of coaching involved, but the quality of the code produced is top notch, and the maintainability is _vastly_ improved compared to anything we've done in plain javascript before.
This may be due to Coffeescript, and it may be due to Backbone adding some much needed structure, but I'd like to believe it's a combination of both and even using one in isolation should yield an improvement.
If you are familiar with Javascript, picking up Coffeescript should be a breeze, and in my opinion, the benefits are there.
On a closing note, you say that JS is ridiculously powerful which leads me to believe that you feel using CS means sacrificing some of that power. Since CS translates directly into JS, I think you'll find this fear is unwarranted.
That alone is really not any evidence that CoffeeScript is as powerful as Javascript. Just because the target language exposes a particular feature does not mean that source language does.
On the whole, though, I don’t think CoffeeScript adds quite enough benefit to outweigh the costs It's not clear what are those costs? The --watch mode bug? Or the generated JS which is perhaps less a complaint about CoffeeScript, and more a warning about versioning and standardising all components of a project.? Or the author's trouble with indentation rules?
I've had to read this article too many times to figure out, but it sounds like the author is looking for a framework like backbone. Author used the wrong "tool" for his main problem. A language doesn't really help avoid spaghetti code, as author concedes: It is still possible that I may find a return to plain Javascript sufficiently painful that I’ll stick with Coffee.
Add `// Generated by' to surpress JS diffs on Github.
> I really like for x in xs. I don’t really use the list comprehensions (which Andrew Brehaut had some things to say about), because I tend to use map and filter anyway.
CoffeeScript's list comprehensions can be used for `map' and `filter' too.
> I was a bit surprised to discover that CoffeeScript has no more opinions on code structure than does Javascript (short a class syntax which I barely used.)
One cannot be saved sola gratia.
He took on a large project on short notice with a tight deadline, browser compatibility issues, and growing requirements (client is a big company after all) using a language he's "not terribly confident with". This alone is a really Bad Idea. But I'll give him the benefit of the doubt and assume he recognizes this in hindsight and felt he could handle it at the time.
Then he decides to do it in CoffeeScript, something he has never used before. It seems he had expectations that CS was much more heavy handed, providing library-like code structure and compensating for his lack of JS knowledge. Mistake #2.
He finds a bug and some other small annoyances. This is understandable. There are some things about CS that annoy me too. Every language has its problems. I suppose this alone could be enough to drive someone away from a language. Personally I find the trade offs worth it for increased productivity, but that's just my opinion.
His conclusion about CoffeeScript is that "it just doesn’t feel quite robust enough (as a language or as a tool) that I’m confident in it at this sort of scale, and at smaller scales it doesn’t confer enough benefit to be worth the added complexity." Again, problems with confidence.
My suggestion? Be confident with JavaScript first, then use CoffeeScript. Not understanding the underlying language is just going to lead to problems like this.
For those of us not in the US - anyone have any idea what site he is talking about?
Edit: Just saw the link in a previous comment. Seems the site is: http://toyota.com/camryeffect/
Paraphrasing: We were under massive time constraints and by our own estimation, already barely able to meet the deadline, so we decided to add a language which compiles Javascript even though I am not that well versed in Javascript.
There are so mnay things wrong with this approach I really don't know where to begin.
Thought it might be useful to others seeking these advantages that passing your JS through JSHint in the build process could provide these benefits. It's not a cure-all, of course--the author cites other benefits that linting can't achieve. But if you're a like-minded developer looking for assurances along these lines in JavaScript, check out http://jshint.com and https://github.com/jshint/jshint/
> I decided to add CoffeeScript to this, despite no real experience with it.
Care to elaborate? I'm not saying I don't have my own notions of what's missing in JS, but I'd like to hear your take, given your experience.
One of my personal gripes with javascript as an environment is that out of the box there is exactly two composite data structures, and both are associative: Objects, and Arrays.
As you are probably aware, Objects are the bread and butter of Javascript and double as full OO objects and also as map/dictionary types with the limitation of only allowing String keys. Arrays have their own quirks but lets leave that aside for now.
Javascript lacks a well understood model of value identity[1]. Without a common model for value identity (i.e. more than reference identity) it becomes very difficult to implement custom data structures (e.g. a BTree for implement a true dictionary type) in a way that can easily (and naturally) be consumed by third parties. Lacking the ability to hook into the [] operator increases the awkwardness for anyone attempting to provide their own custom collections. I would suggest that the lack of good quality collections deployed widely in the javascript world underscores this problem. Contrast this with just about any other language you can think of and it is even more stark.
As an example, any good language probably has a Set datastructure these days. There are two ways to fake it: Degenerate map with the key being the real value and either some sentinal (such as true) or the key itself as the value, or (as is very common in javascript) an array and then doing indexOf.
Let that sink in: a common approach to sets in javascript (when you cant easily provide a string key for your object) is to do a linear search. Yuck.
For a wonderful counter point, have a look at how ClojureScript implements its own collection types, complete with value identity, indentity partitions and proper maps with object keys.
[1] Leaving aside all the unknowns most javascript programmers have about the `valueOf` method.
Arrays? Secretly hash tables.
Objects? Well, yeah, them too.
I miss a set type, I miss solid iteration (although coffescript mitigates that), and immutable datatypes would be nice (I've got used to them in Clojure.) I miss a solid FP stdlib, although jQuery has map and filter, which goes a long way.
That's the ones I can immediately think of :).
I hear this over and over again but I frankly do not get it. I write JavaScript every day and got over the nested callback problem ages ago. I don't say this to refute the point; I legitimately don't get it and feel like maybe I'm doing something wrong because I don't encounter the problem. Here's how I write stuff that requires several callbacks:
function TakesAWhile() {
}
TakesAWhile.prototype = {
start: function() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'api/foo', true);
xhr.onreadystatechange = this.posted.bind(this);
xhr.send('bar');
},
posted: function(e) {
if(!(e.target.readyState !== 4)) {
this.complete('error :-(');
return;
}
db.saveSomething(someObject, this.saved.bind(this));
},
saved: function(e) {
db.getFreshCopy('foo', this.got.bind(this));
},
got: function(e) {
var foo = e.target.result;
this.complete(foo);
}
};
Then I consume it like this: var stuff = new TakesAWhile();
stuff.complete = function(foo) {
console.log('All complete!');
};
stuff.start();I hear you. Have you heard of iced coffee script ?(http://maxtaco.github.com/coffee-script/) I didn't sleep well the night after I read that.
.
follow = (from, to, cb) ->
# Invokes four async operations in a series, which requires 4 `await`, `defer`
# using iced. Note how error handling is handled
# in one place rather than each step.
# That's a big drawback for me with await/defer.
async.series
user: (cb) -> User.find name:from, cb
addFollowing: (cb, results) ->
results.user.following.push to
User.save results.user, cb
followee: (cb) ->
User.find name:to, cb
addFollower: (cb, results) ->
results.followee.followers.push from
User.update results.followee, cb
, (err, results)
cb err, resultsI feel exactly the same as you: Javascript, as a language, just does nothing to help you. You've done a better elucidation of it than I could, I just always felt JS was being unhelpful; be it getting the value of an object, be it the inheritance syntax (I have no issues with the prototypal aspect, just the way you express it), lack of good ways to move through a list, the somewhat difficult to understand scoping of 'this'...
It's not that any one thing is especially broken, it's just that all added up I feel like it's not finished, and I'm not Getting Stuff Done as I do with Python.
Google Closure seems to go some of the way there, but I get the feeling that's probably just adding some syntactic sugar to make swallowing the bitterness a bit easier.
The explosion of the web opened the doors to new languages after a long stagnation of client-side code. Why let an accident of browser development history dictate the tools we use for the next 5-10 years?
JavaScript is also getting the ability to change property lookup behavior on objects using Proxy (available since Firefox 4).
Browsers are moving faster, which helps here... and for people using Node, you can use these features as soon as Node gets them.
Those "costs" were the cost of learning, not development/efficiency costs.
If you are already comfortable with Javascript and know coffeescript beforehand, the cost is zero and the benefits far outweigh that.
I've not used them in production yet but it looks like a very cool way to ensure code quality. The syntax of the Contracts resembles Haskell type definitions. Contracts are very flexible -- for example, you could create and enforce a "Prime Number" type (or whatever type you wish).
Also, you don't have to include the contracts in your production code -- you can compile to JS for production and omit the contracts once you've validated the code against them.
Actually, the more I look at this, the more I think I'll use it for my next CoffeeScript project.
now, trying to think of which apps were featured during the super bowl. the GM one? anyone have any guesses?
Interestingly, I think that quality is entirely orthogonal to production worthiness and any implied or explicitly stated guarantees about utility and unbreakiness.
I've personally thrown my weight behind up and coming technologies in the past (Merb to be specific, back in the Merb vs Rails insurgency), and ended up supporting a technology stack that was left abandoned. In spite of that, my company continued running happily on Merb for 3 years after that (until a move to Scala).
That jashkenas warns people with a "caveat emptor" doesn't mean a tool shouldn't be used in production. It means that you should carefully evaluate how you use your tools in production, make sure you write modular testable code, be willing to dive down into the weeds, and most importantly always have a backup plan.
The interesting part about CoffeeScript is that the backup plan (again if you have properly factored code) can just be "use more Javascript". :P
(my own caveat is that I do not CoffeeScript in production. At least, not yet.)