A linguagem CoffeeScript existe desde meados de 2009, e evoluiu bastante desde então, mas o destaque que vem recebendo nos últimos meses é sem precedentes. Tem havido várias discussões acaloradas em torno da linguagem, em grande parte devido a controvérsias em volta da decisão de tornar CoffeeScript a linguagem padrão para scripts no futuro Ruby on Rails 3.1, no lugar do JavaScript, o padrão anterior. Embora a mudança necessária para continuar usando JavaScript no RoR seja pequena, muitos desenvolvedores entraram numa polêmica que evoluiu para algo mais profundo, relacionado a linguagens em geral e ao seu papel no desenvolvimento web.
Os principais motivos da discussão da utilidade do CoffeeScript envolvem as vantagens técnicas do seu uso e se o ganho é apenas estético. É levantada também a preocupação com o esforço de aprendizado de mais uma linguagem. É importante, entretanto, entender as motivações do uso da linguagem para que se possa tirar uma conclusão mais objetiva.
Origens e Motivação
Para falar de CoffeeScript, precisamos começar com a razão de sua criação. O CS foi criado, segundo seus desenvolvedores, devido ao excesso de verbosidade, além da falta de “elegância” do JavaScript e de outras linguagens hoje usadas para o desenvolvimento na web.
Como sabemos, o JavaScript se tornou famoso pelo extenso uso em projetos web e, após a Web 2.0 a linguagem se tornou fundamental. Mas apesar de seu alto grau de adoção, a linguagem em si pouco evoluiu. O progresso ocorreu principalmente em volta da linguagem, com a criação de frameworks como jQuery, Prototype e muitos outros. O JavaScript já mostra efeitos da sua idade, se comparado com linguagens com paradigmas mais modernos como Groovy, Python e Ruby (embora algumas destas tenham sido lançadas antes mesmo do JS).
Contudo, com o suporte de JavaScript nos principais browsers e a quantidade de aplicações "legadas", uma atualização da linguagem quebrando a compatibilidade é algo que certamente não vai acontecer tão cedo. É neste ponto que entra o CoffeeScript.
Em vez de se posicionar como um substituto completo ao JavaScript, o CoffeeScript é construído sobre o JavaScript. Programas em CS são "compilados" para JavaScript através de um pré-processador. Os pré-processadores já são usados há décadas, por exemplo em C, com seus #ifdef e #define. Mas a técnica evoluiu muito desde então, sedo utilizada em vários outros contextos, como no framework GWT, em que o código escrito em Java é transformado em JavaScript e HTML; e no SASS, no qual uma "compilação" gera código CSS.
Sintaxe e formas de uso
A extensão utilizada em arquivos CoffeeScript é .coffee. Os arquivos são compilados para arquivos .js, sendo usados scripts para a compilação. Há instruções específicas no site do CoffeeScript, para instalação do compilador e outras ferramentas. Há ainda extensões que permitem a editores de texto reconhecer a sintaxe e indentação, como vim e gedit.
A seguir, faremos um mergulho na sintaxe da linguagem, por meio de trechos de códigos que destacam as principais diferenças (e semelhanças) entre CS e JS. O leitor notará que o CoffeeScript recebeu inspiração de várias linguagens, especialmente de Ruby e Python.
É possível testar trechos de código em CS no próprio site da linguagem, onde é oferecido um interpretador online acessível através do botão "Try CoffeeScript". Além das saídas dos códigos (se houver), você verá o código JavaScript gerado.
Os exemplos a seguir (baseados em códigos no site do CoffeeScript), começam com o obrigatório "Hello World"; a primeira coluna sempre mostrará o código em CS e a segunda, o código gerado em JavaScript:
CoffeeScript |
JavaScript |
alert "Olá Mundo!" |
alert("Olá Mundo!"); |
Já no "Olá Mundo" vemos algumas diferenças em relação ao JavaScript, por exemplo a ausência do ponto-e-vírgula e os parênteses opcionais, característica também encontrada em Ruby.
Seguindo com os exemplos, ficarão mais evidentes outras características da linguagem, como a utilização do espaço significativo do Python; ou seja, não são necessários delimitadores de início e de final de blocos, com o contexto sendo demarcado através da indentação. Além disso, o if pode ser usado não só da maneira convencional, mas também no final das expressões, assim como em Ruby.
Atribuições
numero = 42 oposto = true numeros = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] numeros[3..6] = [-3, -4, -5, -6] |
var numero, oposto, numeros, _ref; numero = 42; oposto = true; numeros = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; [].splice.apply(numeros, [3, 4] .concat(_ref = [-3, -4, -5, -6])),_ref; |
Observe que não é necessário declarar as variáveis em CoffeeScript; além disso a facilidade para trabalhar com atribuições, inclusive em vetores, é útil em várias situações.
Objetos
math = root: Math.sqrt square: square cube: (x) -> x * square x |
var math; math = { root: Math.sqrt, square: square, cube: function(x) { return x * square(x); } }; |
Acima, veja como construções mais complexas, como a definição de objetos, ficam mais claras com a simplificação na declaração de funções e atributos.
Splat (parâmetros variáveis)
corrida = (vencedor, competidores...) -> alert competidores corrida "Jose", "Primeiro", "Segundo" corrida "Joao", "Primeiro", "Segundo", "Terceiro", "Quarto" |
var corrida; var __slice = Array.prototype.slice; corrida = function() { var competidores, vencedor; vencedor = arguments[0], competidores = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return alert(competidores); }; corrida("Jose", "Primeiro", "Segundo"); corrida("Joao", "Primeiro", "Segundo", "Terceiro", "Quarto"); |
Nesse exemplo de utilização do operador splat, fica evidente o quanto de código é economizado e a clareza da construção.
Condicionais
alert "Eu sabia !" if elvis? numero1 = 1 numero2 = 2 numero3 = 3 numero4 = -4 if oposto numero5 = 5 |
var numero1, numero2, numero3, numero4, numero5, numero6, numero7, numero8; if (typeof elvis !== "undefined" && elvis !== null) { alert("Eu sabia !"); } numero1 = 1; numero2 = 2; numero3 = 3; if (oposto) { numero4 = -4; } numero5 = 5; |
Em casos em que há muitas atribuições, o alinhamento das variáveis e seus valores aumenta a legibilidade. Além disso, se precisamos incluir uma condição isso pode poluir visualmente a sequência. Com a opção de condições no final da linha, esse desconforto é evitado.
Note que as melhorias não são somente estéticas. Por exemplo, no tratamento de existência da variável "elvis" acima, o código JS gerado já trata condições inesperadas para evitar erros em tempo de execução, especialmente erros comuns para usuários inexperientes em JS.
Comprehensions
cubes = (math.cube num for num in list) |
var cubes, num; cubes = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = list.length; _i < _len; _i++) { num = list[_i]; _results.push(math.cube(num)); } return _results; })(); |
Estas sentenças "em uma linha" são úteis para operações que são executadas em coleções, simplificando o laço e o controle de condições.
Valores padrão para parâmetros
preencher = (recipiente, liquido = "café") -> "Preenchendo o #{recipiente} com #{liquido}..." |
var preencher; preencher = function(recipiente, liquido) { if (liquido == null) { liquido = "café"; } return "Preenchendo o " + recipiente + " com " + liquido + "..."; }; |
Parâmetros opcionais e com valores padrão são úteis para evitar uma cascata de "overloads" muito comum em linguagens estaticamente tipadas. Em JavaScript os parâmetros são opcionais, mas muitas vezes é necesário percorrer a funcão para descobrir qual o valor padrão, caso determinado parâmetro seja omitido. Já com CoffeeScript, isso é explicito.
Interpolação
autor = "Wittgenstein" citacao = "Uma foto é um fato. -- #{ autor }" sentenca = "#{ 22 / 7 } é uma aproximação de π" |
var autor, citacao, sentenca; autor = "Wittgenstein"; citacao = "Uma foto é um fato. -- " + autor; sentenca = "" + (22 / 7) + " é uma aproximação de π"; |
A interpolação é um recurso utilizado para deixar mais clara a concatenação de uma descrição fixa com uma variável. Essa característica da sintaxe do CoffeeScript foi emprestada do Ruby. O conteúdo dentro do operador #{} é avaliado e executado no caso de expressões mais complexas, gerando uma string com a substituição já feita.
Ligação (binding) de funções
Conta = (cliente, carrinho) -> @cliente = cliente @carrinho = carrinho $('.shopping_cart').bind 'click', (event) => @cliente.efetuar_compra @carrinho |
var Conta var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; Conta = function(cliente, carrinho) { this.cliente = cliente; this.carrinho = carrinho; return $('.shopping_cart') .bind('click', __bind(function(event) { return this.cliente.efetuar_compra(this.carrinho); }, this)); }; |
A sintaxe para a criação de funções anônimas é muito mais legível, em comparação com o JS. Há duas variações. Com a seta simples, "->", o pré-processador apenas cria a função com os parâmetros determinados; com a seta dupla "=>" é enviado automaticamente o "this" (objeto com o qual a função está ligada).
Os atributos com prefixo @ são uma substituição ao "this", utilizados para que o escopo das variáveis seja apenas no contexto da função, ou seja enviado para outra função no caso de "callbacks". Outro recurso útil é a possibilidade de colocar valores padrão para os parâmetros, deixando bem clara a forma de utilização da função.
Como o código JavaScript gerado pelo tradutor do CoffeeScript é sempre "um-para-um", ou seja para cada instrução CoffeeScript temos uma correspondente em JavaScript, podemos utilizar funcionalidades de biblioteca de terceiros. No exemplo acima (que utiliza JQuery), o pré-processador do CS irá fazer a tradução de maneira correta para o JavaScript, preservando as funcionalidades que temos só no JQuery.
Vigiando o código
É bem comum, ao montar JavaScript, utilizar o arquivo .js separadamente da aplicação para facilitar os testes e ajustes finos, utilizando apenas um arquivo html estático com um trecho de código específico para isso. Com a opção "--watch" do compilador do CoffeeScript, é possível deixá-lo "vigiando" os arquivos .coffee de maneira que, assim que forem alterados, os arquivos .js correspondentes sejam criados evitando passos adicionais – como chamar o compilador manualmente – para esses testes separados da aplicação em arquivos html pontuais.
Conclusões
Examinando alguns exemplos da sintaxe do CoffeeScript, já se pode observar como a linguagem traz os mesmos recursos do JavaScript, de maneira bem mais elegante e clara, exatamente como esperado de linguagens mais modernas.
A legibilidade do código tem sido valorizada cada vez mais. Estudos conhecidos mostram que a manutenção do código representa mais tempo do que sua criação em seu ciclo de vida, e autores como Robert C. Martin (Uncle Bob) em seu famoso livro "Clean Code" mostram técnicas para manter o código limpo e legível. Uncle Bob argumenta que o desenvolvimento de software se compara ao trabalho de artesãos, e defende a preocupação em se criar código legível e compreensível por outros desenvolvedores, e não somente por quem o escreveu.
Além da elegância e clareza, há outros fatores que determinam o uso de uma nova linguagem. Muitos deles não quantificáveis, cabendo à avaliação da equipe, arquiteto ou empresa; outros envolvem preferências pessoais.
Você escolheria a linguagem CoffeeScript para um novo projeto?