top | item 4533737

CoffeeScript: less typing, bad readability

349 points| dcu | 13 years ago |ceronman.com

218 comments

order
[+] jashkenas|13 years ago|reply
Most of the bad code examples in this post are taken from the CoffeeScript compiler itself. For that I have to say "mea culpa". There's a lot of overly clever and dense bits that have accrued in the compiler over time -- sometimes due to the tangly nature of the codebase, but also sometimes because we tend to use the self-hosted compiler as a test bed for edge cases of combining different language features together.

If you're looking for more regular style, this style guide is a better place to start:

https://github.com/polarmobile/coffeescript-style-guide

[+] lowboy|13 years ago|reply
Perhaps a style guide should be put up at coffeescript.org, or at least linked to.
[+] bicknellr|13 years ago|reply
I feel like CoffeeScript is failing at what it's attempting to do if it needs its own style guide.
[+] crazygringo|13 years ago|reply
This is a fantastic post -- I feel like I've run into all these crazy "gotchas" over the past year, and more.

And the worst part of it is, nearly all the confusing/ambiguous/different compilation examples given rely on the undocumented rules CoffeeScript uses for parsing. It's amazing that, still, CoffeeScript's entire documentation is basically a "getting started" guide, and there is simply no reference documentation.

[+] luriel|13 years ago|reply
Magic overdose.

Compare with Go, where you can easily read the whole spec and keep the whole language in your head: http://golang.org/ref/spec

[+] FuzzyDunlop|13 years ago|reply
I think CoffeeScript seems nice in theory, but in the end, it's something made for people who want to code javascript, without actually using the language. It exchanges some of the terse, functional elegance javascript offers for a different kind of elegance, with its classes and inheritance and such like.

I won't say it's bad, as it is nice to work with when you're writing it, and I do quite like it. But I could never recommend it to someone who didn't have a good knowledge of javascript already.

[+] TazeTSchnitzel|13 years ago|reply
I personally describe CoffeeScript as "stabby". CS has all these little shortcuts and shorthands which seem great and appear to save time, but many of them are ambiguous, and then you get stabbed by their ambiguity, and you waste time debugging. Python, on the other hand, is still quite concise, but is generally very unambiguous.
[+] lowboy|13 years ago|reply
As I wrote in the other recent CS thread here, I think your coding style must be ill-suited to coffeescript. I've had a few situations where I've had to test things out for myself, but nothing like what you put forth. I just write CS as I would JS and that's that.

To that end, the docs on Coffeescript.org are the reference documentation (as they are the only official documents to reference) and they've suited me just fine. I get the sense that you're looking for a document in the style of ECMA-262, rather than one in the style of MDN.

[+] orangecat|13 years ago|reply
Right. I've started using CoffeeScript for a side project, and I really appreciate how it fixes most the warts in JS, but I wish they had stopped there rather than making gratuitous syntax changes. Parentheses in function calls is not an actual problem.
[+] johnbender|13 years ago|reply
I think it would be interesting to see the operational semantics for a very small subset of the language. Applying that type of formalism would likely suss out or highlight the corner cases like the ones highlighted in the article.
[+] no_more_death|13 years ago|reply
Maybe coffeescript programmers could just use their brain and choose not to descend the Infinite Staircase of Obfuscation (the carrot's not worth it). Just because Coffeescript can saw your foot off doesn't mean you have to.
[+] johnx123-up|13 years ago|reply
I have used CS on 3 projects and Scala in 2 projects. For me, both CS and Scala have terrible readability. Scala is more worse, I'll need at least 3x to read it than write it. Obviously, YMMV (tm)
[+] smacktoward|13 years ago|reply
I don't have a strong opinion on the article itself (it seems well-reasoned to me, but I don't use CoffeeScript personally so who knows)... but the comments attached to it are hilarious.

I think CoffeeScript has great readability, because I’ve taken the time to learn how it works.

If you have to take the time to learn how to read something, it's not easily readable.

CoffeeScript is only hard to read if you try to read it with a JavaScript mindset.

Yes, why would anyone approach a tool pitched explicitly at JavaScript developers, whose "golden rule" is "it's just JavaScript" (see the top of http://coffeescript.org/), with a "JavaScript mindset"? Truly, it is a mystery.

[+] tikhonj|13 years ago|reply
So is mathematical notation not readable? On the one hand, it takes quite a while to learn how to read it. On the other hand, it can condense literally pages of text into a few equations which somebody experienced can understand almost at a glance.

The same is true for programming languages. Sure, anybody can read Python. But it's fairly verbose and attempts to mirror English. On the other hand, Haskell is somewhat terse and attempts to mirror mathematical notation. I've found that, after about the same amount of time with either language (about a year of off-and-on use for both), Haskell becomes far easier to read than Python.

So just because something is easier immediately does not mean it is any more readable. It could, in fact, be less readable and merely more familiar!

[+] masklinn|13 years ago|reply
> If you have to take the time to learn how to read something, it's not easily readable.

I don't think that's completely true. If you're used to algol or C-style, Lisps tend to be hard to read. Concatenative languages even more so. Not necessarily because they're hard to read in and of themselves, but because the "shape" of the code is "all wrong" compared what you're used to, and your usual anchors (keywords, binary operators, infix assingment, ...) are nowhere to be found.

I mean Arabic or Korean are not necessarily harder to read than English, but you've got to know them and how they work before you even recognize patterns in the squiggly lines and little squares.

[+] jaequery|13 years ago|reply
i'll stick to javascript. coffeescript doesn't add a whole lot to the table, javascript is the standard and that's a good thing, re-writing the language is not the answer in my opinion. i'd have to imagine that saving a few keystroke might sound sexy to lesser experienced developers, but that really is all it adds to the table. being able to code on any project immediately off the bat with a standardized language is a huge plus, in terms of flexibility and scalability. you don't want to limit your company to exotic language, only to later find out few months later another new language called "frapscript" is now the new cool thing to do.

i recall the days when i was thrown into a project where the requirement was to use mootools, rather than jquery. even though they are both "javascript" in nature, it severely limited my productivity. i can see the same thing happening to companies trying out coffeescript, it may seem like you are doing the better thing long-term wise, but in reality, it is not so much. it's better to just stick to standards and you'll be more productive down the line, i can almost guarantee it.

[+] wtetzner|13 years ago|reply
> If you have to take the time to learn how to read something, it's not easily readable.

What? What language can you read without knowing it first?

[+] ricardobeat|13 years ago|reply
Though most examples are not something you'd write normally, this line is what kind of flopped the article for me:

    Given that CoffeeScript doesn’t fix any of the
    fundamental problems of JavaScript
same for a comment here:

    coffeescript doesn't bring much to table
There is so much coffeescript adds that I find these laughable:

    string interpolation 
    multi-line strings
    comprehensions
    guarded loops (hasOwn...)
    easy constructor/prototype definition (class)
    avoiding global leaks/shadowing
    function binding
    splats, defaults
    safe usage of reserved names
    existential operator
    chained comparisons
    READABLE regular expressions
This just off the top of my head. These make a huge difference to writing code everyday. I've been writing CoffeeScript for 90% of my projects in the past 2.5 years and it pretty clearly affected my productivity for the better. I can only conclude that whoever says "coffeescript doesn't add anything" hasn't really used it for work.

Case in point, if you look at the linked article at the end you'll see an update by the author: I actually love CoffeeScript now that I've been writing it for a year.

[+] tete|13 years ago|reply
I don't consider this article bad at all, because it really makes some good points.

However a lot of criticism seems to come from the fact that the author is a Python programmer and therefor wants CoffeeScript to be more like Python. No, I am not saying something like the author not understanding it isn't meant to be Python, but for example the implicit vs explicit debate is certainly a more philosophical view. Ask people who prefer other programming languages and they will have a different view. Hey, after all that's why there are so many, even if Ruby, Perl, Python, Falcon, etc. have very similar use cases.

My opinion for example is that dynamic languages are very implicit in first place so saying you don't want that (at all) doesn't make too much sense. I also don't have problems reading CoffeeScript, but occasionally had problems with very explicit languages being too verbose which can make it harder to follow. So looks like my mind works slightly different here.

But back to the article. There are lots of valid points. I think coding guidelines, which one should have working in a team, no matter what language could solve some of them. Some points look a bit like mixing different styles on purpose and at least can't see how you could find something like that in the wild. Just because you can code ugly, it doesn't mean you have to, but again that is more of an opinion. Some people like usually verbose languages, because they say they are easier to read, others like ones with shortcuts or where you have multiple options to express things, making it easier to read (for some people). I for example always enjoyed the fact that Perl has unless and until in place of negated if/while.

But that's more what you prefer. But hey, CoffeeScript is all Javascript, so if your team doesn't like it it's (comparatively) easy to step by step switch back.

[+] crazygringo|13 years ago|reply
Question: am I the only one who's driven nuts by "if"s that come after the "then" part? E.g.:

    do_something(with, these, args) if im_supposed_to
I mean, the processor/interpreter always needs to evaluate the "if" first, so what purpose does it ever serve to put it after the "then"? To me, it just confuses things because it feels like code is getting executed backwards -- like crossing an intersection, and then checking to see if the light is green.

I know it works "in English" ("do this if that"), but when I scan other people's code I'll sometimes completely miss the "if" (sometimes it's just off the screen).

Are there any examples where this reverse-if actually helps, instead of harming, code intelligibility?

[+] cnp|13 years ago|reply
Personally, i write my CoffeeScript with as much syntax as possible, to keep it as readable as possible: as a rule, I always use parenthesis and commas while leveraging CoffeeScript's "good parts".

But that said, the last couple of projects I've written in pure JavaScript and it's actually made things much easier to go back and read. Like the author of this post, I too have had difficulty reading back over my old code and, at a glance, understanding precisely what's going on.

Over the past couple of months I've gone from being absolutely devoted to CoffeeScript to on the fence, mostly due to readability.

(I'd love to see a fork of CoffeeScript that forces syntax.)

[+] cristianpascu|13 years ago|reply
I have recently implemented to quite large project with CoffeeScript, the most recent one for a full export to HTML for http://flairbuilder.com, and I have to say that CoffeeScript is a superb language. It's a pleasure to work with.

The kind of problems that the article points out are easy to spot. If small syntax changes yield significant output code, that will be immediately reflect in the program execution.

Plus, a decent code base will have unit testing in place, which should catch more intricate, harder to catch, unwanted code flavors.

[+] deanotron|13 years ago|reply
I've been using coffeescript for about two years - I thought it was the best way to keep the pleasant aesthetic of python, which was my favorite language, and have been happy working with it ever since.

I just want to throw out a positive experience with it and to say that I don't support the author's premise of "here's some misleading things you can do with CS, therefore CS is unreadable". This applies to all languages, and 'fanciful' features and syntax should be avoided almost always in every language for the sake of readability.

There are MANY reasons not to use coffeescript, but JS is all functions all the time, and -> is my best friend.

[+] debacle|13 years ago|reply
I find this review very even-handed. It doesn't really make any bold claims, but it does provide a slew of evidence.
[+] cheald|13 years ago|reply
Eh. I think it's pretty silly. It basically boils down to "If you write terrible Coffeescript, it's hard to read". Duh?

Any time you are writing code that is ambiguous, use what you need to in order to clarify it. Don't omit punctuation just because you can if it makes the code less readable.

   action(true, 
     option1: 1
     option2: 2
   )
Using empty parameters in a function declaration is even funnier; you're adding punctuation that makes the code more vague. Just omit it!

   doSomething -> 'hello'
   doSomething(-> 'hello')
The third example is totally valid, and one I completely agree with - omitting parentheses on inner nested function calls makes for unreadable code. Don't do it.

The fourth example - complaining that inconsistent indentation produces inconsistent results - is just incomprehensible to me. When you're using a whitespace-delimited language, you'd best pay attention to your whitespace.

Ambiguities with optional curly braces are certainly nothing new to Ruby developers. Convention is that only the last hash in an argument list may have its braces omitted (or more stringently, a hash may only have its braces omitted if it is the last parameter in the list). Makes for finely readable code. Don't write unreadable code.

One of the fundamental rules you should follow when working in any language is "don't be clever". Optional parentheses can make for very clean code, but if you try to get "clever" with them, you end up with ambiguous code. List comprehensions are awesome but if you use them when there's a simpler construct you can use, you're doing it wrong.

You can write bad, unreadable code in any language. Part of maturation as a developer is learning to write readable code, not just working code. A bad workman blames his tools.

[+] andrewingram|13 years ago|reply
A few years ago I made an email campaign editor using JavaScript, I re-implemented it in CoffeeScript a few months ago. What I now want to do is re-implement it in the good JavaScript I've learned from reading the code that CoffeeScript generates.

There are some annoying parts of CoffeeScript. If your function takes callbacks as the first parameters, and another value as the second (such as setTimeout), you end up with some really awkward syntax. I've seen a lot of people define a delay method that swaps the parameters of setTimeout just so it's easier to use with CoffeeScript.

Like the author of the article, I have a Python background, but I've also written a lot of JavaScript, I love the syntactic sugar that CoffeeScript brings, but I hate reading CoffeeScript code. The project that my re-implemented email editor is used in has a hybrid of JavaScript and CoffeeScript code, and I'm not kidding when I say that everyone (myself included) groans when they have to work on the CoffeeScript parts.

[+] jashkenas|13 years ago|reply

    > If your function takes callbacks as the first parameters, 
    > and another value as the second (such as setTimeout), 
    > you end up with some really awkward syntax.
Hopefully no more awkward than the equivalent call in JavaScript.

    setTimeout (-> alert "later"), 300
Versus:

    setTimeout(function(){ alert("later"); }, 300);
Or, on multiple lines:

    setTimeout(-> 
      alert "later"
    , 300)

    setTimeout(function(){ 
      alert("later"); 
    }, 300);
[+] klibertp|13 years ago|reply
As for `delay`... I had to think hard to think why would it be a bad idea and concluded that it is non-standard and can confuse readers. If this is the case then just define a `swap` or `flip` function, along the lines of

    flip f = (a, b) -> f(b, a)
and use it inline:

    flip(setTimeout) 300, -> "it's fun!"
Sweet ;)
[+] tiglionabbit|13 years ago|reply
Making the callback the last parameter is very common in JavaScript too. It just makes things look nicer when you want to put anonymous functions in there. Underscore.js does this for all their functions.
[+] ebiester|13 years ago|reply
To me, this says that you should define your callback as a function, and pass the function. If you're used to how to write functional code, this can be a clean way to do things.

That said, I've seen the same problem myself.

[+] ryankshaw|13 years ago|reply
whenever I see something like this that tries to rag on coffeescript it seems like they all reference ryan florence's post from a while ago: http://ryanflorence.com/2011/case-against-coffeescript/

for the sake of clarification, ryan is sitting right next to me, writing coffeescript (as with all new code we write here at instructure, see: https://github.com/instructure/canvas-lms/tree/stable/app/co... )--and loving it. whatever "crazy gotchas" he found back then are obviously trivial to how much easier coffeescript makes life. it seems like every third tweet he makes is about how much he loves CS now: https://twitter.com/ryanflorence

Other than that, everything in this post just comes down to "just because you can doesn't mean you have to or should." disambiguate if helps make things readable

[+] jashkenas|13 years ago|reply
Perhaps it's time for Ryan to write that follow-up post ;)
[+] johnx123-up|13 years ago|reply
One person has opposed CS and now using it.

Many people (including me) tried CS and back to JavaScript.

It's all about readability, team size, and choices.

[+] eranation|13 years ago|reply
I guess I'm lonely in this world, but I really want a statically typed JavaScript alternative. Dart is nice, but no JS interop (yet), GWT is just Java, which is not the most fun thing in the world, ClojureScript is nice, but not static typed and Lisp is a bit too extreme for me, but the new Scala JS DSL seems worth waiting for.
[+] gothy|13 years ago|reply
Using CS for about 1.5 years now. Yes, there're ways to hurt yourself with bad CS code. This is also true for Python.

When developer writes some fancy one-liner in CS or Python and I'm reviewing his commit, I just ask him to come over and explain what this thing is doing. Sometimes it takes more than 20 second to read and explain even by author. Then I say: "You wrote this line an hour ago and it's already hard for you to understand what it does. Imagine you'll need to change it in a month. You'll hate yourself. Go and rewrite this code explicitly to help yourself in the future."

If you force yourself and teammates to write explicit code, you get all the pluses of CoffeeScript avoiding bad readability.

[+] armored_mammal|13 years ago|reply
I concur with the author. When I write CoffeeScript I often use a more explicit or more c-like style for things I find ridiculously ambiguous or hard to parse when reading quickly.

Particularly, if one line has lots of commas, I put parens all over the place because no matter what I do, having to manually parse the comma arrangements and figure out what's nested and what's a function does not go quickly. I also find myself writing explicit returns somewhat frequently just so it's much more clear what's being returned.

The overarching issue is that the syntax blurs rather than pops -- there are too many instances where things that are significantly different in function look nearly the same.

[+] ilaksh|13 years ago|reply
Just because you CAN do something in CoffeeScript doesn't mean you SHOULD, or should ALL of the time. Just like in JavaScript where, if you want, you can write all of your code on one line, but you shouldn't.

    action true
      option1: 1
      option2: 2
If you really use CoffeeScript regularly, that isn't confusing. I don't usually write it that way though. I would do this:

  options =
    a: 1
    b: 2
  action true, options
doSomething () -> 'hello' just isn't valid code.

Usually I would write

    doSomething ->
      return 'hello'
or maybe

    doSomething -> 'hello'
If you write it the way he did, the parenthesis are confusing.

For this one:

    action = (token, i) ->
        @tokens.splice i, 0, @generate 'CALL_END', ')', token[2]
-- first of all, you always want to indent two spaces. But the main problem with that is in CoffeeScript you do need to use parenthesis after the first call because your code will be unreadable otherwise. You just can't write it the way he did.

    moveTo 10,
      20
      10

    doSomething 1,
    2
    3
    4

    doSomething 1,
      2
        3
       4
Those are ridiculous examples. No one does that. You just write moveTo 10, 20, 10

    doSomething (->
    'hello'), 1
Indentation is significant in CoffeeScript, just like it is in Python. Just having a parenthesis shouldn't change that. Anyway, what I do is this:

    somethingDo = (ms, func) ->
      doSomething func, ms
and then I can just write

    somethingDo 1, ->
      'hello'



    action(key: value, option: value, otherValue)
You don't write it like that if the function takes two objects. You would probably just write

    action {key: value}, {option: value}, otherValue


    x = {
      key1
      key2
      key3: value3
    }
This is called destructuring assignment, and its part of the new ECMAScript. Its useful to not have to repeat key1, key2 everywhere when the variable with the value has the same name as the object property. The extra curly braces are just necessary to differentiate from the normal syntax.

Sometimes yes/no or on/off are more readable than true/false. That's an advantage.

    x = 1 if y != 0;
Don't use semicolons in CoffeeScript. I also don't put if statements at the end of a line because I don't believe that is very readable for most people including me. Also, in CoffeeScript its probably better to use isnt instead of !=, although rather than !=, you would want !==.

    if y isnt 0
      x = 1
I'm not that sure about the use of unless, although it probably is a little bit more readable overall.

  break for [tag], i in @tokens when tag isnt 'TERMINATOR'
  @tokens.splice 0, i if i
He says that is supposed to delete TERMINATOR from tokens. I tested it, it doesn't do anything, and the second example of the correct way doesn't work either. I think he meant this:

    filtered = []
    for token, i in tokens
      if not (i is 0 and token is 'TERMINATOR')
        filtered.push token


    i += block.call this, token, i, tokens while token = tokens[i]
Another example of something you CAN do in CoffeeScript but shouldn't. Actually it is a bad idea in general. I think a CoffeeScript programmer would actually write something like this:

    class Parser      
      block: (token) =>         
        @currentNode.push new Token(token)
      parse: =>
        for token in tokens
          @block token
        
I would never write something like 'mainModule.moduleCache and= {}'

Instead of

    js = (parser.parse lexer.tokenize code).compile options
I would write

    tokens = lexer.tokenize code
    parsed = parser.parse tokens
    js = parsed.compile options
[+] sixbrx|13 years ago|reply
I haven't programmed in Coffeescript, but does it bother anyone else that new variables are introduced without "var"?

When reading js, seeing the "var" really helps me to know the programmer's intent that this is a new variable being introduced, not an attempt to reassign one that should already exist. Does CS have features that make this point mute?

[+] masklinn|13 years ago|reply
> I haven't programmed in Coffeescript, but does it bother anyone else that new variables are introduced without "var"?

Yes. I've come to see it as a mistake in Python and Ruby both, and I'm definitely not pleased by its usage in CS. Even less so as it uses the same rules as Ruby's lambdas without the stops of its methods and global scopes.

[+] svachalek|13 years ago|reply
Actually I think this is a big plus, as it's much more common to want to declare a new variable than to want to reassign a global, and hard to notice when something is missing like "var". It would be nice if there was an explicitly different operator for reassignment. (p.s. I think you mean "moot")
[+] lowboy|13 years ago|reply
Nope, and I don't miss having to have all of the var statements everywhere.

Can you provide an example in js where knowing whether it's a new var vs. reassignment is useful? I can't think of a case, but it could just be me.

[+] mcmire|13 years ago|reply
This doesn't bother me at all. Mostly because this feature comes from Ruby and so I'm already used to it, but also because it takes away the nasty surprise that JavaScript gives you if you leave 'var' out. I find this invaluable. As for knowing whether or not a new variable is being declared, well, that's why you keep your functions (and therefore your scopes) short.
[+] jeremyjh|13 years ago|reply
I think it depends on what you are used to. I find "var" to be noise.
[+] TeeWEE|13 years ago|reply
The big point here: less characters for the same logic is not always better. Saying that you need less characters to write function x in coffeescript compared to javascript doesnt mean it is better.

Its all about syntax and semantics. Code from languages with a small and consistent syntax, one-way-to-do-it, and a easy semantics are easier to understand than languages with a lot of semantics.

For example, Scala is cool. But also overly complex. The same holds for C++. But languages like clojure are very simple to grasp and the semantics of these languages are very small.

[+] mratzloff|13 years ago|reply

    Coming from Python [...]
    
    For example, in C-like languages, you can omit curly brackets 
    after a conditional expression if you only have one statement:
    
      if (condition)
          action();
    
    But what happens if we add a new statement:
    
      if (condition)
          action();
          action2();
Of course, that doesn't seem to be an issue in Python, the language he's coming from.

Most of his points are similarly contrived and could be equally applied to Ruby, a language which doesn't require parentheses and quite a lot of developers seem to like, or Python which is similarly whitespace-dependent and quite a lot of developers also seem to like. But inflammatory headlines do drive traffic from HN...

    Given that CoffeeScript doesn’t fix any of the fundamental problems of JavaScript
I think most developers who are well-versed with both JavaScript and CoffeeScript would disagree that CoffeeScript doesn't fix any of the problems with JavaScript. It certainly makes it easier to loop through object properties. It makes code more readable in a number of ways; post conditions, list comprehension-like syntax, simpler loops, the existential operator (`foo?`), and the maybe object-like syntax (`foo.bar?.baz?.quux`) are definitely improvements.

You can of course write bad code in any language, no matter how structured it is. I myself am about to embark on refactoring a massive, terribly-constructed Python system that processes millions of requests each day. The author didn't follow the strictures of the Python community at all. How is that any different from someone who exercises poor judgment when writing CoffeeScript? It's not.

[+] Macha|13 years ago|reply
The issue with the optional curly brackets is that for new users, or even experienced users reading through code quickly is that at first glance:

    if(something) 
      doSomething();

And:

    if(something) 
        doOneThing();
        doAnotherThing();
Both look like they should execute the same way - running the indented code if the condition is true. The reason this is a problem in C and not in python is that in python, this code operates as it intuitively looks, while in C, it does not.

(It can trip up experienced users if they're not paying enough attention and their mind just assumes the curly brackets are there.)

[+] lmm|13 years ago|reply
Can you give examples? I'd really like to see some unreadable python, and other than nested lambdas I can't see any real ways to screw it up at the basic syntax level. Even golf-style python tends to have a structure that's easy to follow (though what the code's actually doing can be another matter), and I feel it's significant that there's no obfuscated python contest.
[+] tharris0101|13 years ago|reply
This is exactly why I stopped using CoffeeScript. I hate Javascript so I was excited when I saw CS for the first time but after a while I realized I was doing two calculations in my head:

1: What is the JS that CS was generating?

2: What is the JS doing?

Maybe I just didn't give it enough time, but it seemed quicker and more reliable for me to just suffer through the JS syntax.