Where I work, we moved the project I work on to CoffeeScript about a year ago, and I've been using it ever since.
Putting syntactic sugar aside, while some things are very welcome (list comprehensions, ===, ?), there are two main reasons why I would be wary of using CoffeeScript again:
1. Complete lack of documentation for syntax. Because there are basically no more braces and parentheses, CoffeeScript just tries to guess what you're doing, as far as I can tell, based on a bunch of internal heuristics. Unfortunately, there's no way for me to learn how to write parseable code without constantly pasting into the coffeescript.org site, and seeing if CoffeeScript understands it or not. This is the first language I've ever used where the syntax rules are essentially unknowable, and a lot of time gets wasted trying to discover them through trial and error.
2. Unexpected side effects. For example, functions return the last evaluated value by default. If you're using $.each(), and your function's last line is something that returns false (like a separate function you call), then your $.each() loop will terminate unexpectedly early, since jQuery does that when it receives a false. So CoffeeScript isn't just a wrapper around JavaScript, but it really changes its behavior. Another example: CoffeeScript gets rid of function hoisting. A significant JavaScript feature, completely gone.
I personally am slower to code in CoffeeScript, because I know JavaScript 100% inside and out, but with CoffeeScript that isn't really possible, because so much of its implementation is undocumented. I mean, many times you're forced to end a line with a backslash in order to continue it, and even the existence of that necessary feature isn't mentioned once in the docs.
But a lot of people seem to love the syntax, and that seems to outweigh the negatives for them. I personally don't find JavaScript that ugly, but coding in a language I can't ever fully understand gives me a huge headache.
#1 I agree, making stuff optional was a bad design decision. It encourages people to write code in different ways and makes for unreadable messes if coworkers aren't religious about how they format things or they do things differently than you do.
#2 has definitely bitten me, but I see that less of a failure of CS and more of a failure of the design of $.each(). The case is pretty well documented in CS though... the last code line is 'return'ed... if your code unexpectedly returns false, then really you are just bitten by the design of $.each().
Your only other options for early termination of $.each are pretty cumbersome. On the one hand you can bend over backwards to pass in the "should continue" flag "by reference". In C you might pass in bool * should_continue, then do *should_continue = false. In JavaScript you'd have to wrap that in another variable, like flags.should_continue = false, which is less elegant in my opinion. Alternatively, I've seen libraries abuse throw to achieve this, which I also don't like (and I believe has pretty bad performance characteristics, but I can't remember for sure).
... or you can write it in CS with a for loop which cleans up the syntax quite a bit... or it might be possible to reduce your input first so that you are only iterating over the things you actually want to iterate over...
"...or you can write it in CS with a for loop which cleans up the syntax quite a bit"
For each loops are great, but there is certainly value in having $.each be a function as well as a statement. For example, if you want to pass it to another function in more functional style programming: do_something($.each);
"or it might be possible to reduce your input first so that you are only iterating over the things you actually want to iterate over..."
It seems you don't understand the point of returning false from $.each, it is not to filter, it is to stop early. For example, if you are doing a linear search for something, it is reasonable to want to stop after you find it. There is no way to "iterate over the things you actually want to iterate over".
I think you're stretching here. Passing $.each to a function makes no sense when it is effectively a global anyway. Please give me a real world example of why you would do something like that (not in the functional aspect, that I understand... I am talking specifically about $.each).
I also wouldn't use something called $.each to do a linear search. I'd use underscore.js _.find(), which has a sane api, you return true when you want to stop. This thing I'm looking for has been found.
Array.forEach and _.each() chose not to support returning false. I can only assume that is @jashkenas being wise.
CoffeeScript can't really claim that "it's just javascript" either. I'd have to figure out how to hook up the CoffeeScript compiler into every single one of my workflows where I use javascript.
For example, I use swank (https://github.com/swank-js/swank-js) to send javascript to the current page in my browser from Emacs. I couldn't live without it. I'd have to figure out how to dynamically compile it, and I'm not sure if there's any restrictions with piece-meal compilation.
It's definitely a separate community and I'd lose out on lots of good tools if I had to use it. I'm glad nobody wants to use it where I work.
Pardon? The coffeescript site has pretty comprehensive info that describes the syntax. If you don't find a specific example on the site, "it's just Javascript" has rung true for me in almost every case.
Yes, there is a learning curve and it does things slightly differently. The implicit return value is just something you read once and then integrate. True, function hoisting is lost in cs, but that has never been a problem for me.
Just three completely undocumented syntax examples...
Mapping values in an object:
{a, b} = c
Backslash to continue lines:
a = long_variable_name \
+ other_long variable name
Switch statements without a parameter:
switch
when function_that_needs_to_return_true
stuff
But the bigger problem is just having no idea how things will parse. For example, this works:
a = b +
c
But this doesn't:
a = b
+ c
And then this works:
switch a
when b, c, d
e()
But this doesn't (a multi-line form you might want to use if the variable names were really long):
switch a
when b,
c,
d
e()
And this doesn't:
switch a
when b
when c
when d
e()
After trial-and-error, you discover it has to be this, with a seemingly random backslashes:
switch a
when b, \
c, \
d
e()
I could go on and on -- but you get the point. The whole language is only discoverable via trial and error. None of this is documented. (Apologies for taking up so much vertical space of the comments!!)
Basically, if statements are that long, the code should be refactored. This makes it easier to read and reduces the opportunity for bugs. That pattern is one of the main reasons I like CoffeeScript.
Parent's point is that he wants better documentation, and possibly better consistency, on things like breaking lines; breaking lines was just an example to illustrate the larger point.
Sorry, but that's completely bizarre. The whole point of a switch statement is to be able to test a single value against a list of single values, or groups of single values.
The supposed "correct way" you support moves the values outside of the switch statement, which completely destroys reading comprehension of the code. It explodes the number of lines of code necessary. This isn't refactoring, it's complete code obfuscation.
It also makes the switch statement itself completely superfluous, since you might as well just do a series of if / else if's, and save an extra line of code. I mean, the whole point of switch is to pass a parameter and compare it against values.
Sorry, what's completely bizarre is having variable names which push the 80 character limit. That doesn't help reading comprehension at all.
For instance, look at the recently posted Matches.js [1] -- there are many reasons to factor out the test case when the test case is unusual. 80 character test cases are unusual.
80 character test cases (variables or otherwise) should be factored out. The readability and purpose of the method is compromised if you have 5 groups of 30 constants. Factor the groups out into individual maintainable functions. Then there is one function per case, and an overview of the entire switch can be seen in a few rows rather than several pages.
What are you talking about? My particular use case that led to all this was just translating a simple, clean, elegant JavaScript switch like the following. It's ideal, really, in terms of readability:
switch (operation_code) {
case SUGGEST_ACTION:
...
break;
case PERFORM_ACTION_PREAUTHORIZE:
case PERFORM_ACTION_AUTHORIZE:
case PERFORM_ACTION_POSTAUTHORIZE:
...
break;
case DELETE_ACTION_REVERSIBLE:
case DELETE_ACTION_IRREVERSIBLE:
case DELETE_ACTION_NOT_PREAUTHORIZED:
case DELETE_ACTION_PREAUTHORIZED:
...
break;
case UNDO_ACTION:
...
break;
(5 more groups of 30 more constants...)
}
It's not variable names that push an 80 character limit. It's about translating groups of shorter constants, that taken together go over 80 lines, and which have similar prefixes, so they look very nice listed vertically, and make it very clear where their differences and similarities are.
I stand by my point: telling people that, in CoffeeScript, it's proper to "refactor" this into a switch statement with no parameter and creating entire new functions, is bizarre.
Hey, that's a really good use case for multi-line cases. Not exactly the fallthrough behavior of the JavaScript example you posted ... but a good reason to want linebreaks in your list of values to match. You should open a ticket suggesting a syntax improvement for it.
2) This has to do with the unary plus operator more than just cs - since +varname is an expression, it treats it as such. Fair complaint - I've never run into that. Also, this works just fine if you put the plus on the end of the first line:
a = long_var_name +
hey_another_var
3) When would you use those?
4+5) Are cases of the unary operator in 2)
6-10) Sure, this is limiting if you want to write switch statements like that.
I feel that your coding style is different, or that you're listing edge cases to be contrarian. Coffeescript has a much smaller range of syntax deviations but that's part of what allows it to be so brief and clean.
His 1-3 were complaining about undocumented syntax, and the rest about the actual syntax itself. Not that your response is wrong, just that might help you understand what he was saying.
Line-breaks end statements in CoffeeScript, this is the same as
a = b; +c
That should be pretty apparent. Your other examples are mostly abuses, and you'll not find "documentation on forbidden syntax" for any other language either.
Javascript or Coffeescript shouldn't ever have lines ending with a backslash.
I'm not so sure the addition one isn't actually also applicable to javascript automatic semicolon insertion. The google js style guide (publicly available) says that all lines must end in an infix operator, opening bracket or semicolon to always avoid accidental semicolon insertion.
Though the switch one seems surprising (read: a flaw) since it is usually safe to end a line with a comma in vanilla js.
Basically, you can almost always remove the semicolons without trouble and more often then not problems occur from semicolons not being inserted then from them being inserted in unexpected positions. The "returning an object literal" case is the only common case where a semicolon is mischeviously inserted.
I wasn't trying to express a strong stance on the issue, just pointing out what the Google's js style guide says, the + on the next line wouldn't be allowed there (nor .)
I always felt that function hoisting is harming code readability. I prefer reading the code from top to bottom, I don't want to stuble upon functions that were not declared before.
If I recall correctly the main reason why this feature is not supported by CoffeeScript is because of some awkward IE bug though.
The main reason is what you say -- it harms readability. Much better to have functions behave like any other value, like any other variable -- and only be usable after they've been created.
If your inner function names are sufficiently descriptive, you can write the high-level behaviour of the outer function at the top, first to be read, to get a quick overview of what it does. Then declare the inner functions below that (this also requires that the inner functions must not have surprising side-effects).
I've never seen or tried out code like that in practice, but it seems like it could work.
This is exactly my position. I have used CoffeeScript in 3-projects and moved back to JavaScript. The biggest problem for me is the readability. JavaScript has braces and good readability, IMHO. YMMV.
This sounds so much like some geeks wanting to hack. That's it. There isn't really a good reason to convert a codebase with tens of thousands of lines of code from js to CoffeeScript.
Not that there's anything wrong with that. But seriously, every single problem with javascript they mention is never a problem for javascript developers. You simply learn what's broken, and deal with it. Dealing with it is usually one line of code (or even less), making sure you just use ===, etc. It's really not a problem.
These kinds of posts smell a little like FUD to me, which is what I have a problem with.
To a lot of people CoffeeScript promotes the intangible benefit of happiness. (Similar to ruby.) If working in CoffeeScript makes everyone's work much more enjoyable (for whatever reason), with minimal cost, isn't it worth it? I, for one, want to minimize the amount of things I need to "deal with" at work, be it shoddy programming language workarounds or a half-broken coffee machine.
No, the parent is correct. JavaScript developers do not lose any sleep because '1' + 0 = 10. We learned to do integer math with integers and moved on. We learned to just use === and moved on. The Dropbox devs apparently didn't want to go through that learning curve, which is fine, but rewriting your application in another language just so that you don't have to look at semicolons is a big bikeshedding mistake. Writing new code in CoffeeScript and only rewriting legacy code as needed would have been a more sane way to transition.
How can you say it was a mistake when it only took one week? There was a cost, they paid it, now they can reap the benefits.
I program in C++ extensively. I "deal with it" a lot. I write things like this all the time:
MapType::const_iterator it = map.find(x);
if (it != map.end()) {
// x is in map, process it
}
I've done it for years, I know how to do it, and there are great benefits to getting iterators back from my data structures. Yet, I always feel a warm fuzzy when writing data processing scripts in Python, and I get to say:
if x in map:
# x is in map, process it
My point: even though one can have learned to "deal with it," there can still be a benefit to not having to deal with it anymore.
> How can you say it was a mistake when it only took one week? There was a cost, they paid it, now they can reap the benefits.
The cost is not just time (although that's certainly a big cost). They rewrote working production code for fun. Why this is a bad idea will likely derail the conversation so I'll just direct you here:
Joel's excellent essay does not apply here. The Netscape re-write was a re-write. That is, it was not a translation. Netscape looked at their code base, and decided, "We don't like the design of the architecture of our code. We're going to throw it out, re-design it, and re-implement it from scratch."
The Dropbox team clearly did not do that. They translated it from one language to another; the design did not change. Much of that translation was with an automatic tool. Again, there is a difference between re-implementing something, and translating it.
Further, the examples in Joel's posts took years. This took a week. The situations are completely different.
Personally, I find it disingenuous to link to an article and say "He has my argument" without actually stating what your argument is.
The scope is much smaller, but the problems are still the same, namely introducing new bugs to working production code and surrendering ground to competitors.
See http://www.perlmonks.org/?node_id=115511 for my original opinion when I read it. Nothing I've seen since has changed that basic option. (But wow, the technologies that I thought would go somewhere. It is humbling to see how wrong I was. But the points are still good.)
Now years later consider carefully that the "failure" that Joel criticized is what lead to Mozilla, Firefox, and the second browser wars. Furthermore consider that Netscape had no real choice with its rewrite. As a company they were going to die. Hence their effort to open source the code. But they didn't have licenses to everything in their browser, therefore the open source version couldn't even run!
Now I'll grant that the decision to rewrite working production code is always to be undertaken carefully. It can easily go wrong. But in this case Dropbox decided to do it, they've done it, they've had no ill consequences (the lack of consequences is not an accident - they knew how to do it well), and they are more than satisfied with the result. Given those facts it would probably be a good idea for you to be taking notes and asking yourself how come your immediate prediction turned out wrong instead of saying, "They were stupid to try that."
I've mentioned this on HN already but despite the fact that I agree with Joel about many things, I really hate when people pull that article out of their hat like it is some sort of debate ender.
Netscape was already fucked prior to starting the rewrite, mostly due to their ill-conceived forays into "groupware".
If Navigator wasn't rewritten when it was the layout engine from the rewrite wouldn't have existed to power Firefox and Mozilla probably wouldn't exist as an entity right now. So in a very indirect way, the Netscape rewrite actually saved the Netscape browser lineage and probably helped the web from falling into a much longer mini-dark-ages than it did around the NS/IE 5-6 era. And there's not really any good evidence that it did the old "Netscape" any harm since it isn't like Navigator was ever a significant profit center anyway (I know they did charge for corporate usage of Navigator, but that was never going to be a sustainable business, so the rewrite had no impact on that).
If they really rewrote it in a week, then either it was trivial or they were able to reuse the bulk of it. The Spoelsky article applies to neither of those cases.
No, because then I would need to find it again (eiter through the find() member function, or through operator[]) to get a reference to it. Without a reference, I can't process it.
Why? In your original example you said "process x". You can also "process x" in mine:
if (map.find(x) != map.end()) {
// do stuff with x
}
You don't need the iterator to do anything with x. And if you do need the iterator (to perhaps keep iterating or something?) then you would also need to do extra work in the JavaScript example.
> The Dropbox devs apparently didn't want to go through that learning curve
That's not a fair statement. I'm a competent js developer, having trudged up that curve, and I still love cs. Not for lack of learning, but because I value brevity and reduced keystrokes.
Ummm... i'm pretty sure that the devs over at DropBox know their JavaScript inside and out. These "rules to master" are in no way complex to understand.
First, it's not a matter of just learning to do something and moving on. There is incremental mental baggage that comes with every one of these "tricks". Since we are both Javascript developers, I won't list any, but I think we both know that some of the things you need to remember to do go beyond adding an additional equals sign. The sum of this is you can often find yourself falling into one of these traps, even after many years of experience, because there are just so many places you can be tripped up.
Second, I'm going to disagree with you on having a polygot codebase. If the developers decided that CoffeeScript is a better environment than Javascript (which you may disagree with), then I think it's clear the ideal scenario for them is their entire codebase is in a single language. Since it was tractable for them to convert the existing code, it seems logical to do that (not withstanding concerns like version control history, etc, which seems like an academic concern not really that important in practice.)
In fact, converting an existing code base is a common way to learn CoffeeScript and come to a judgement if it is suitable for future development. Such seems to have been the case here.
Developers aren't machines. "What should we work on today?" should not always be calculated as a function of "what provides the most immediate business value." Happy developers attempt harder problems, don't leave their current job to go work on $shiny_thing, and think about their latest project in the shower. All of which are of significant benefit to Dropbox.
That's not to say that you can't lose a lot of money refactoring working code — I dislike rewrites, they always cause unforeseen problems. But great software isn't a function of how many hours you've put into it. It's a complex result of developer morale, time, energy, experience, and skills, and there's a huge benefit to "hey I can think in a language that makes sense to me, instead of remembering weird bugs I have to work around." It's thinking on a higher plane. It lets you more easily load the program into your head. [1] And it can be all the difference if you want a truly exceptional product. [2]
In this specific situation, the conversion was done as part of a Dropbox hackweek.
There are almost certainly other projects they could have worked on for that week that would have resulted in a more tangible business gain for Dropbox as a company, but the point of teamwide hackweeks/hackathons/etc is more employee morale than anything else. It seems to me that it succeeded pretty well at that goal.
Rewriting working production code is not something to be done for fun at a hackweek. It's a very serious thing. And since it took a week they had a significant amount of code to rewrite.
What new bugs were introduced by the rewrite? Did they open the door to any XSS attacks? Who knows.
You really seem to be on a crusade to convince people that this was a reckless, short-sighted, waste of time. Clearly these developers are professionals because they went into detail how they prevented regressions. I wouldn't be surprised if they inadvertently fixed a number of bugs due to the fact those bugs are impossible to make in CoffeeScript, or because they filled in gaps in their test coverage.
Porting JS code to CoffeeScript is not very hard to do safely. I've done it myself for fairly large pieces of code (not this big.) In this case, it was for a hack week project and was a modest amount of code. It sounds like they got buy-in from the rest of the team that using CoffeeScript is a net win. It sounds like they are moving forward, it was a good decision and use of time, and they are happy with the result. Why do you think you can make arguments that it is in fact not any of these things, when the people who did it are stating otherwise, and the organization is acting otherwise? Why do you think you can make arguments against re-writes that suggest avoiding them because they never ship, when this project has, in fact, shipped, and did so in a week? Either you are missing something, or Dropbox is deluding themselves and are just falling for hype. What do you think is more likely?
Upgrading to CoffeeScript is an investment. In my experience, it's a low-risk and fairly high-reward one. A sensible investment which many have a hard time identifying.
The authors' arguments were correct and showed an understanding of their tools' histories.
If you're unable to deal with broken, buggy, or poorly designed things, that makes you a bad developer.
Just deal with it and move on. It's really not worth trying to achieve some sort of "Wow I love this language the best it's brilliant" state. It's far better to achieve a "I'm creating some cool stuff that solves a problem" state.
Language syntax is simply not very important. Every single language has quirks. Learn them, and move on.
If you're unhappy working in a particular language, maybe you're not cut out to be a software developer. You should be happy you're creating things that solve problems - if you're not, reconsider your career choice.
I'm just saying... Good developers concentrate on building things. Bad developers concentrate on how much they like their tools.
You see it in everything in life though. The best way to become a fantastic cyclist say, isn't to buy the best bike out there. It's to buy a crappy cheap bike, and learn how to get the most out of it.
Seriously... if I were to pick something I'd pick music. Miles Davis on a $100 trumpet could make better music than almost anyone else with a $10k one.
That's still missing the key element. While people who are the best at something (cycling, trumpet, archery, programming) could outperform 99.999% of the population using crappy tools, that's not the point. They don't compete against 99.999% of the population. They compete against the 0.001% of the population who are their peers. And at that level of competition - when everyone is so good, and the margins of victory so slim - they use the best tools they can find.
I suppose that's true in situations where there is a significant advantage to being 1st over third or just being in those top percentiles. Splitting hairs over a topic I didn't disagree with you on from 24 hours ago, but I'd say that sorta breaks the metaphor for this particular situation. There isn't really an advantage to being the world's greatest programmer vs the world's top 1% of programmers. When you get to that level it's all gravy.
Which makes me realize I have no idea who the world's best programmer is.
Yes, and at some point in your development of competence you become hindered by bad tools. Otherwise we would not be using debuggers, high-level languages, big monitors, or anything beyond a simple text editor and console.
The best way to become a fantastic cyclist is to cycle a whole heck of a lot. Buying a good or bad bike doesn't make you a good cyclist. But having a good bike can help you enjoy cycling more, and make you a better cyclist by virtue of the fact that you do it more often as a result.
This is like saying people should avoid jquery because everything jquery does can be done with javascript. Some of us enjoy the convenience of not dealing with hacks.
Regarding missing vars, JSLint will alert you that you've made a mistake, Coffeescript prevents it from happening in the first place, not quite apples to apples.
jquery improves the crappy DOM API and its vendor inconsistencies, not javascript the language. if anything improves javascript in terms of syntax, it's underscore/lodash or coffeescript.
"Dealing with it" adds cognitive load. Both when programming, and when reading code others wrote, or you wrote a while ago. Eliminating that cognitive load may (note, again, I said "may", not "will") be worth it.
Honestly, it makes it much worse for me. With brackets, I can place my cursor over the end one and my editor highlights the beginning one. While I'm coding that lets me double-check where I am while I'm working inside a callback.
This is what I was thinking. Convert to CS and then struggle to find developers in a few years when the guys that's thought this was a great idea have moved onto the next big thing.
The reason to use CoffeeScript over JavaScript is because it is better software engineering.
Number of lines of code has been shown to be one of the few good indicators for predicting defects. CoffeeScript significantly reduces the number of lines of code in a project and therefore the number of defects.
CoffeeScript is cleaner and easier to read, which is better software engineering. CoffeeScript solves many of the issues with JavaScript. The fewer warts and plumbing people have to deal with related to the language, the more they can focus on the actual problems they wanted to solve.
This is a little bit like the architectural decision to program user applications in languages and platforms with garbage collection and built-in frameworks rather than using a language like C or C++ with manual memory management and minimal standard libraries. Sure you get less control over the lower-level details such as allocation and deallocation of memory, but those lower level details actually are distracting from the application domain and were actually repetitive and could be automated. So the best way to solve those problems is to move to a platform that abstracts them away or handles them for you.
The reason that developers don't like CoffeeScript (other than the fact that they haven't learned it yet, and don't want to take the time) is simple, psychological rather than logical, and its the same reason that they don't like platforms like the ones I was talking about above: it makes things significantly easier, and that is problematic psychologically for programmers for two reasons:
1) The whole point of being a programmer is that you are mastering complex ASCII codes that other people can't decipher. Making things less complex takes away part of your identity as a programmer (not for everyone obviously, but for many).
2) Many programmers instinctively and falsely believe that any system which makes their job significantly easier must automatically come with compensatory trade-offs in things like performance or flexibility. This is intuitive, but false.
CoffeeScript improvements: indenting two spaces and using -> plus no closing parens and curly braces makes nested callbacks much easier to read and write, fat arrow binds this making it easier to use classes, class and extends provide a standard, simple way to create and extend classes, iterating over arrays and objects is much easier and cleaner, control flow and function definition is cleaner, better variable scoping, simplified closer wrapper in loops, more straightforward boolean operators, existential operator for cleanly and simply checking for variable existence.
CS's scoping is a bit... dangerous, because there's no indication of a variable's scope. If you're not paying attention to your environment, you'll inadvertently stomp on higher scopes or globals. You can lose assignments through typos too.
JS made a mistake by allowing globals by omitting "var", but that at least can be worked around by using a linter or "use strict". A number of languages have made this mistake, and many later followed a similar path to fix it.
CS has a lot of nice properties, but we should not laud conflating variable definition and assignment. It has always been a mistake, and I hope CS's author eventually comes around and fixes it.
>CS's scoping is a bit... dangerous, because there's no indication of a variable's scope. If you're not paying attention to your environment, you'll inadvertently stomp on higher scopes or globals. You can lose assignments through typos too.
Not just that, but adding a variable to an outer scope can break functions that were previously just fine. Isn't the whole point of scoping that changing code in one place won't affect unrelated code in another place?
It's been much discussed, but I'm afraid I'm still very strongly of the opinion that it's an idea that yields positive benefits -- and we should continue to do as much to avoid shadowed variables as possible.
Always open to debate though, and note that other dialects (Coco, perhaps LiveScript) have added another operator to distinguish higher scoped assignments.
> Number of lines of code has been shown to be one of the few good indicators for predicting defects.
Curious - is there evidence that that applies in a cross-language manner? It seems intuitively sensible that a smaller project will have fewer bugs than a big project, but that's not the same as saying that a big project rewritten in a language which causes fewer lines of code to be used will result in fewer bugs - in the latter case, the systemic complexity is the same.
The answer is yes. The research suggests that the rate of development and the error rate are both linearly correlated with length of code rather than how much the program does, and this rate seems fairly consistent across a range of languages. I believe that Code Complete makes reference to this fact.
From a random Google search, http://panko.shidler.hawaii.edu/HumanErr/ would seem like a promising online resource to try to find some of the literature on error rates.
> > Number of lines of code has been shown to be one of the few good indicators for predicting defects.
The token reduction is not that significant and the OP is honest enough to note that "line" reductions boil down to lack of closing braces. Relevant study to cite would be one that compared languages with or without explicit notation of scope. Intuitively it seems to be that braces are a visual aid that reduce bugs and do not promot them.
I'll dispute it. If I cram more code into a single line it will not lower my defect count. In fact there is a chance that the concision may turn into clutter and become harder to read/maintain and that may increase defect count.
Is there a measurement term for number of symbols (in the lisp sense) in a given program? Example: '+', 'add', 'defn' would all be individual symbols. I could see a loose correlation between symbol count and defect count... maybe.
Or to go even more abstract: number of concepts per program is probably the ultimate measurement we are looking for: https://en.wikipedia.org/wiki/Concept
I've been writing JS for over a decade and never had a problem with it. I put off CoffeeScript for ages, because nothing was wrong with my JS code. "Besides," I thought, "CoffeeScript compiles to JS, it can't do anything JS can't do."
And that is true. Nothing is exactly wrong with JS. In fact many things are great about JS, which is how we end up with ClojureScript and CoffeeScript and x86 emulators. JS is very powerful -- maybe too powerful. There are a million ways to accomplish the same task, which means that during an initial look at a new JS codebase, the program flow is not obvious.
Some developers hate semicolons, some always put commas at the end of the line and some at the beginning, some always use braces and some don't use braces with single-line statements. One can never be sure where a statement ends, or what constitutes a code block, unless one hunts down all the trailing braces, parentheses, and semicolons. I'm not even going to mention all the possible inheritance patterns.
CoffeeScript is not nearly as flexible in syntax. There is one way to make a code block, one inheritance method. It doesn't need semicolons, braces, or parentheses because of its strict style. Therefore there is less to think about. There is less to go wrong. No one needs to use a CoffeeScriptLint, because CoffeeScript is not full of non-obvious 'gotchas'.
CoffeeScript code is more concise and more intuitive, because it reads more like an English sentence. When I read another developer's CoffeeScript I can begin understanding the meaning instantly, rather than having to spend time determining the structure and programming technique. List comprehensions, ranges, and other functional programming tools simply don't exist in pure JavaScript -- the JS equivalent is much more verbose and much less obvious. CoffeeScript encourages creating small flexible code blocks, which together make a powerful structure. This all leads to fewer opportunities for bugs to appear.
The only thing I don't like about CoffeeScript, is that I didn't keep an open mind and start using it sooner. I think anyone who considers themselves a serious JS developer should give CoffeeScript a try. It's a much more relaxing, simple way to write powerful JS.
(Credit goes to Spine.js switching to CoffeeScript to finally push me into it.)
EDIT: Of course it is possible to write ambiguous, unclear code in any language. CoffeeScript is an opportunity to let go of old habits and find a fresh perspective on problem-solving: Maybe there is a better way to do things. (I had to let go of semicolons!)
CoffeeScript's functional programming style has changed the way I approach programming. My code is now more robust and less complicated. Less bugs and less keystrokes mean I ship faster. CoffeeScript is a better language because it's a tool which makes me a better developer.
There's definitely improvements to the language in CoffeeScript. It makes sense; it was able to take 10 years of js experience and write a new language. But that doesn't mean CoffeeScript itself is without problems, or that it even improves development time.
There are other ambiguities too due to the arrow pattern for defining functions. You have to be careful where you place stuff.
CoffeeScript is definitely an improvement in some ways, but please don't market it as superior to javascript (I certainly prefer brackets). It's just different. All of this really doesn't make much difference in how long it takes you to ship products.
> CoffeeScript encourages creating small flexible code blocks, which together make a powerful structure. This all leads to fewer opportunities for bugs to appear.
Personally I find single-line statements that do too much to be difficult to read.
I also find nested functions really difficult to read in CS. From a coffee mug I own:
coffee.height drink coffee, e.angle
That's only 2 functions, it gets much more confusing when it's 3 or 4 which multiple parameters. Before you say "don't nest functions that way", I agree completely, however a large part of the appeal of CS is reducing LOC and these one-liners, so in real world code I've come across this often.
Respectable CS coders I've seen "never" code like that. Jeremy (among others) have always said that you should "always" use parenthesis when not using them makes code ambiguous even in the slightest bit.
So, you should write than as
coffee.height (drink coffee, e.angle)
Also, understand that you can use parenthesis in a lot of places to remove ambiguity:
As a rule of thumb I use braces and parens as a standard. I think making parens and braces optional was a mistake and they are necessary punctuation to the language. There is simply too much ambiguity without them and then when you're mixing and matching things become unreadable.
I wonder whether people would be less skeptical of CoffeeScript if its syntax were a bit closer to Python's: more parens, but simpler and clearer. For instance, what are Perl-style backwards if statements doing in CoffeeScript? They make it impossible to tell what a statement does by looking at the beginning of it (e.g. in other languages, "foo = ..." is almost always semantically an assignment of foo, and always syntactically an assignment) and thus decrease readability, just to save a pair of parentheses.
Haskell programmers hate parentheses. They would write it as:
flip coffee.height e.angle $ drink coffee
Where "flip" is the function that reverses the order of two arguments of a function and "$" is basically like a set of parentheses that are implicitly closed at the end of the line.
On the former, I think you'll find most people find their implementation more readable/idiomatic, and the second case I'd guess is because they were doing a port and so probably didn't want to make too many semantic leaps.
Why are you bringing reduce() into this? Seems to fly in the face of its common purpose, stated on the MDN page: Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
By doing it this way, you've introduced another symbol for us to consider (hash) and by departing from the standard for loop format, you've made it less idiomatic, which means more people will have to expend mental energy parsing it.
Your first example is the exact reason I dislike CoffeeScript. Sure it's clever, but at a glance it is much harder to determine exactly what's going on.
Parent's reduce() example is harder to read than the original, and about the same length. If CoffeeScript had dictionary comprehensions like Python 2.7 / 3.x, we could make the code shorter while preserving and easier to comprehend:
@originalStyle = {k : @element.style[k] for k in ['top', 'left', 'width', 'height'}
Unfortunately this syntax was proposed and rejected [1]. But with a simple to_hash library function, you can achieve something similar [2]:
to_hash = (pairs) ->
hash = {}
hash[key] = value for [key, value] in pairs
hash
@originalStyle = to_hash ([k, @element.style[k]]
for k in ['top', 'left', 'width', 'height'])
What's with the dangling , {} on your first example? I can't tell if it's a typo or a weird wrapped line rendering issue or something. I hope that's not considered idiomatic CoffeeScript.
Just another datapoint, but contrary to the other comments here I find your reduce example a lot more readable than the original. This could be because it's fairly idiomatic ruby.
It says they mostly just ported the JS over using JS2Coffee. Maybe they plan to go back through everything and actually convert it over to coffeescript?
Why? It seems like a complete waste of time to this typical software engineer with 100x more objectives than time.
Why not convert all your Java code to Scala? Because you have real work to do and reskinning your existing code provides no benefits, that's why. If it works, don't rewrite it, go make more stuff work.
Maybe the dropbox folks don't have anything else to do, though. Not my place to judge.
Improve the codebase (make it small, cleaner, more easy to read, and fix issues of pastime) is NOT a wasted time. Not necessarily the BEST USE of time at a given moment, but dedicate some time to refresh the code with better tools, better syntax, better libraries, etc is a excellent way to move forward.
For a related sample, with obj-c I move to ARC ASAP, and plan to introduce the syntax literals as soon as possible. I certainly move the whole app codebase to python if exist a cheap way -and still 100% interface compatible to the coccoa-api - as like move js->coffescript for sure!
Is masochist persist in a old way just because is harder. Like when some refuse to upgrade the same language codebase to improve things (for example, I will move to LINQ, Lambdas, parallel code, async in C# as I move along) IF the change provide some advantage - reducing lines, better API, more performance, better clarity, etc -
I tough constant -but careful- refactoring of a live project is very healthy.
Is better doing it when the project is moving, that wait until a major event happend in the industry -like what happend to my competitors, still on .NET 1 / VB Classic- and suddenly, your move is damm hard, too costly, and you have no skills in the "new thing".
I certainly think something that replace or massively fix JS (including, a better JS) will be very usefull.
If is ok to move from inline styles to CSS, and from HTML to HTML5, then move from JS with issues related from years of hacking-along-design to anything else, like CS, is a good effort.
> Improve the codebase (make it small, cleaner, more easy to read, and fix issues of pastime) is NOT a wasted time. Not necessarily the BEST USE of time at a given moment, but dedicate some time to refresh the code with better tools, better syntax, better libraries, etc is a excellent way to move forward.
And as everybody has been repeatedly pointing out, switching from JavaScript to Coffeescript isn't what you're describing.
Tools? The tools aren't even close to what's available for JS.
Libraries? Libraries aren't even relevant since Coffeescript can call out to JS and vice-versa.
Translating to Coffeescript sacrifices tooling and a codebase you know for a prettier syntax and reliance on the much smaller, much more insular community driving the language. And I'll say it: syntax is a stupid reason to abandon code you KNOW for code you're about to write. A stupid, stupid reason. Engineers need to be more mature than liking to type fewer characters for an anonymous function or because list comprehensions are 3 lines fewer than a for loop.
By rewriting in coffeescript, their version history is likely trashed. They need to relearn the entire codebase. They need to develop new tools to replace the old ones they relied on, or more likely, will just live without those tools.
For a prettier syntax and a couple fewer gotchas. And if a team of 3 can't avoid the common JS gotchas in their day-to-day, I certainly won't trust them to rewrite an entire codebase in a language with different semantics just to get better syntax for what already works.
CoffeeScript is just "clean" JavaScript. You can (and I have done) convert a 200 LOC JS code to CS in less than 15-20 minutes and think carefully about every change you make.
Java -> Scala is like re-architecturing your project. JS -> CS is more like re-factoring.
You guys should use MD5 digests of files instead of mtime for file change comparison.
mtime works fine until you want to add other steps that are dependent on the mtime of ephemeral files occuring previously in your process. Steps such as minification, uglification and comparison for uploading to a CDN all become a bit more complex if mtime is used since every compile step has the tendency to modify mtime so recompilations cascade through the process causing at best unnecessary processing and at worst weird edge cases if the order of middleware isn't taken into account (the middleware issues can be avoided with precompilation before production.
I agree with them on the readability issue mostly, but I don't understand the preference for parens-less function calls, especially where there are arguments.
I just opt for parens every time. I really think they shouldn't ever be optional if in some cases they are mandatory. That this is a matter of personal style is somewhat frustrating.
Remembering to call a function differently depending on whether it has arguments or not has been a common source of errors for me.
It's also much easier for me to parse the difference between "foo(bar(baz))" and "foo(bar, baz)" than it is for me to parse the difference between "foo bar baz" and "foo bar, baz"
among CoffeeScript coders whenever there's a need for disambiguation. Also, it really depends. I bet that print statements don't look weird to you even though in most languages they don't use parens.
This is why our codebase uses paren-less calls when there is a trailing function argument, but uses parens in all other cases. It makes the code far, far more readable.
I agree, but I think making it a language restriction would be very odd. Coming from ruby, I also tend to think that this might be a nice way to do it, for things taking a final optional callback argument.
$.ajax('path', options) (data) ->
# do something with the data
CS fan here. As a rule of thumb I often include outermost parens only in my CS code for this reason. All situation dependent and readability comes first of course.
It's possible in any language to write incomprehensible code. I wish that for single lines like the OP's example, the parentheses were required, but I can see how in CS making them optional for multi-lines is kind of necessary. Like when the last argument is passing in a multiline ->
}); is not a line of code. Please stop counting it as such. Here's a good rule of thumb:
"If it can be moved to the line above, without any other changes, it's not its own line of code."
The argument that CoffeeScript saves countless }); }); } } is invalid, IMO. I would even argue that function definitions are not lines of code. Example: (ruby)
def foo
end
0 lines of code.
BTW, I'm not a CoffeeScript hater, just trying to level the playing field.
The argument that CoffeeScript saves countless }); }); } } is invalid, IMO.
I agree that counting }); as a line isn't necessary, but CoffeeScript saving you doing any of that is a very valid benefit. It cleans up code dramatically- ignore line numbers and just think about all the extra stuff that ends up surrounding multiple function calls.
I'm thinking it doesn't happen that often, and I would counter with having you think just how much more cognitive effort is used in parsing and debugging the syntax of CS. Remember, debugging and reading "old" code dominates our days, so it doesn't add up for me. As well, if you and the many others that like to claim keystroke reductions, there is no difference in "fu<tab>" for "->" and numerous other comparisons.
just how much more cognitive effort is used in parsing and debugging the syntax of CS
Have you tried CS? It's far more readable, by virtue of not having "function() {}" splattered all over the page. I don't care/worry about keystroke reductions, I care about readable code. The lack of curly brackets and addition of significant whitespace absolutely does make for more readable code.
Sure, I've tried it. Function calls are never splattered all over my page, so I don't have to care about that. And at best, it only allows me to cram more crap onto a single line, which is also not often helpful. Instead, I get to -- have to -- think about and end every function with something sane, in the event, for example with jQuery's .each(), I can't have my function magically returning false -- the last evalutated variable -- and terminating the each mapping.
My code already has significant whitespace; it's required by styleguide and linting. CS has prolific issues with whitespace usage, especially when it comes to parsing function arguments and lines broken over multiple lines. Just look through this or any other thread on CS being pushed.
CS often has mind fucks, and I'd rather have to ignore a couple lines with } on them.
Note: I mostly write in Python, if that gives any indication into how I very much enjoy significant whitespace.
'});' is absolutely a line of code. I would even consider ',' a line of code. Forgetting either one of those in a line of JS results in errors. Errors you have to spend mental effort looking out for. Don't discount the value of not having to match up all those ridiculous parentheses and curly braces. Oh and semicolons.
I have personally had more problems from not closing something than I ever had from assigning a variable.
> }); is not a line of code. Please stop counting it as such.
...then I hope that I never have to work with code you've written, because that certainly is given its own line for readability quite often.
What use is a LoC comparison that doesn't use conventional/reasonable formatting? Technically I can run a program of any length through jsmin and come out with one "line of code", but how is that at all useful?
I think OP mean that }); doesn't on its own affect program logic, and, realistically if you're reading JS all day your brain is filtering those characters out for you.
The other argument is that }); is line noise and therefore unnecessary, which I think boils down to preference.
I'm having trouble understanding your point here, how exactly is that 0 lines of code when you've actually written 1 line of code which directly changed the method definitions of a class?
It's not a LoC in terms of code complexity, but it is another line with symbols to parse when you're reading code and it's another few keystrokes to type out each time you have a function or object.
It's nice to see some more prominent companies using CoffeeScript that people could point to if they ever wanted to convince their boss.
Did any of the other Dropbox employees have a hard lead-in time getting used to the syntax in CoffeeScript? Would you say you run into a lot fewer syntax errors when running validation now?
I'd say it goes further than that, as this isn't 1992 any more. I can't possibly find code pronunciation to be of imperative importance because I rarely debug code over the phone.
At any given time, I'm likely to have at least two of the following with me: a phone, a tablet or a laptop. Just e-mail me the code. Make a gist with the problematic section. Push it to a branch in our git repo so I can take a look at it myself. IM it to me. Don't read it to me.
Speaking as someone who has managed coders writing far more lines of javascript than I will ever write, this is a better articulation of my belief that javascript should be generated from another language (CoffeeScript being the leader) than I could ever propose. Thanks Dropbox! This post should be read by everyone who writes or manages those who write javascript.
the question that should motivate a change like this:
what does new-language let your team accomplish that you can't accomplish with old-language?
question isn't answered in post, the answer is probably somewhere along a 2x increase in the complexity the same team can handle. i dunno if it is worth disturbing an existing codebase and the QA cost of making sure you didn't introduce any defects. meh. where i work, we have maybe 100k lines of javascript, and its not really a problem other than lack of types when refactoring imperative code, nothing near the scope of the problem of Java's lack of higher order functions.
this is great news! for people who prefer python-like syntax, coffeescript is a no-brainer. sites like js2coffee.com make it really easy to make the transition.
I tell my prospective clients that if their contractors/employees are still programming in js then they are stealing from them.
Coffee Script is clearer, more concise, more maintainable. It has made me 5x more efficient in writing code (just the reduction in scrolling is a massive time savings) and I've seen similar results with my developers.
It takes about 2 days to learn and has a significant long term benefit. Switch.
In our case, we avoided this problem entirely by instrumenting our server code: whenever someone reloads a Dropbox page running on their development server, it compare mtimes between .coffee files and compiled .js equivalents. Anything needing an update gets compiled.
I wonder how did they achieve this? Is this a feature of node.js or is this like an automated build system? I suspect the latter.
They said exactly how they did it, it's right there in your quote: when someone reloads a page running on their dev server, the server checks if the CS file has been modified and triggers the compilation… So no, it's not a build system feature, it's a server feature.
Wait, they're looking forward to "Native CoffeeScript support in browsers, so that during development, we can avoid the compilation to JavaScript altogether."?
Because an accurate summary would actually summarize what they said, not just list their bias. I would summarize it as, "The Dropbox team prefers the readability of Coffeescript, the conversion took one week, they reduced the lines of code in their codebase by 20%, and there was no impact on functionality."
Unlike CoffeeScript, but like Python, it has proper list comprehensions `["#x#y" for x in [\a \b] for y in [1 2]]` which I think is very important to any Python fan, dict comprehensions, and (more minor) you can use `is not`, like in Python, instead of `isnt`. (Along with many other features.)
I mostly do Perl, but I vastly prefer CoffeeScript to JS. The significant whitespace thing is actually a negative for me with CS, but it eliminates so many JS annoyances that it's worth putting up with.
"We’ve heard many arguments for and against debuggability, and in the end, we convinced ourselves that it’s easy only after jumping in and trying it."
So, in other words, you have only your own opinion and at best one (1) anecdotal experience. Congratulations! You've failed to prove your point from a logical perspective, let alone anything more!
Putting syntactic sugar aside, while some things are very welcome (list comprehensions, ===, ?), there are two main reasons why I would be wary of using CoffeeScript again:
1. Complete lack of documentation for syntax. Because there are basically no more braces and parentheses, CoffeeScript just tries to guess what you're doing, as far as I can tell, based on a bunch of internal heuristics. Unfortunately, there's no way for me to learn how to write parseable code without constantly pasting into the coffeescript.org site, and seeing if CoffeeScript understands it or not. This is the first language I've ever used where the syntax rules are essentially unknowable, and a lot of time gets wasted trying to discover them through trial and error.
2. Unexpected side effects. For example, functions return the last evaluated value by default. If you're using $.each(), and your function's last line is something that returns false (like a separate function you call), then your $.each() loop will terminate unexpectedly early, since jQuery does that when it receives a false. So CoffeeScript isn't just a wrapper around JavaScript, but it really changes its behavior. Another example: CoffeeScript gets rid of function hoisting. A significant JavaScript feature, completely gone.
I personally am slower to code in CoffeeScript, because I know JavaScript 100% inside and out, but with CoffeeScript that isn't really possible, because so much of its implementation is undocumented. I mean, many times you're forced to end a line with a backslash in order to continue it, and even the existence of that necessary feature isn't mentioned once in the docs.
But a lot of people seem to love the syntax, and that seems to outweigh the negatives for them. I personally don't find JavaScript that ugly, but coding in a language I can't ever fully understand gives me a huge headache.