Os 3 maiores problemas do NodeJS (e o que pode ser feito a respeito)

Por -

A natureza assíncrona do JavaScript dá bastante poder para escrever web backends com IO intensivo. Então por que ainda existe tanta resistência e pouco sucesso na adoção do NodeJS em novos projetos?

Nenhuma linguagem de programação (ou tecnologia de qualquer tipo) é perfeita. JavaScript não é diferente. O importante é tomar consciência dos pontos fracos para lidar bem com eles, possivelmente solucionando-os completamente.

(atualização 02/05/2018): Tive contato com projetos no qual o NodeJS parecia fazer muito sentido como tecnologia base, mas ele não foi considerado na lista de technologias possíveis desde o começo do projeto ou da formação da equipe. Nessa lista compartilho quais foram os problemas mencionados e o que eu acredito ser a solução para cada um deles.

TL; DR; YouTube: Os 3 maiores problemas do NodeJS (e o que pode ser feito a respeito) Parte 1, Parte 2

Thumbnail: Os 3 maiores problemas do NodeJS

Thumbnail: Parte 2

Os problemas

Eu trouxe aqui os 3 maiores problemas do NodeJS e JavaScript segundo a minha experiência com a minha atual equipe e outras equipes com as quais tive a oportunidade de conversar.

Classificados na ordem de importância, esses são os três itens mais mencionados como problemas que impedem a adoção de Node para projetos mais sérios.

1 - Nano bibliotecas e a gestão de dependencias

Houve várias melhorias na gestão de dependências nos últimos anos: (Graças ao yarn) Hoje o npm passou a guardar um arquivo "lock", o que garante uma melhor consistência das versões instaladas (feature antiga e provada do Composer e Bundler). A falta de resolução de dependências (que causava 3000 instalações de chalk por projeto) também foi corrigida.

Apesar do ecossistema de pacotes do JS ter melhorado bastante ainda existe uma coisa que ainda incomoda muitos desenvolvedores: A prática de nano bibliotecas/frameworks. Não me leve a mal, não é que nano frameworks seja algo necessáriamente ruim, o problema é SÓ usar nano bibliotecas! Veja bem:

package count

É preciso ser muito ingênuo para não achar que tem algo de errado nesse gráfico. Fonte: http://www.modulecounts.com/

A maioria das equipes que tentam adotar NodeJS começam por "juntar" essas nano bibliotecas, middlewares e frameworks para tentar construir algo para o Cliente.

Por exemplo: Uma aplicação básica precisa do setup de ao menos 20 dependencias, por exemplo: express, express-session, body-parser, csurf, winston, errorhandlerm, accepts, content-type, serve-favicon, express-validator, express-fileupload, cookie-session, cookie-parser, mongoose, jade, etc, etc, etc...

Para piorar, essas dependências foram escritas por pessoas diferentes, não se falam entre sí, não tem documentação e podem ter side-effects quando usadas em conjunto.

Trabalhar em um projeto assim é bastante oneroso para o desenvolvedor. Além disso a manutenção futura pode se tornar um pesadelo (em custos e trabalho).

(atualização 02/05/2018): Muitos desenvolvedores não veem isso como um problema. E realmente, ter a possibilidade de trabalhar com libraries menores e com menos responsabilidades é uma coisa boa em muitos casos. No entanto, devido a frequência com a qual mencionam esse ponto, acredito que é importante incluí-lo na lista.

Mais adiante vamos abordar o que pode ser feito a respeito.

2 - Standard library inconsistente

O JavaScript sofre do mesmo mal que o PHP só que em uma dose ainda maior: Uma standard library inconsistente e estragada pelo tempo, com vários remendos e comportamentos bizarros que não podem ser concertados ou deprecados devido a implementação em diferentes browsers.

(atualização 02/05/2018): Alguns exemplos de inconsistências: Array.sort(), Date, Promises in core, Collection of JS cotchas

Felizmente, com a padronização do ECMAScript e a introdução de transpiladores e supersets como Babel e TypeScript parte do problema foi amenizado. Hoje temos (quase) um padrão para Promises, Classes, Async/Await, etc. Além disso quasi-standard libraries como o Lodash acabam por padronizar um pouco a forma como escrevemos JS.

3 - Bagunça de padrões

A natureza multi-paradigma da linguagem aliada a falta de união e governança da comunidade causa uma explosão de standards onde não existe padrão ou guidelines definidas. Na verdade, ironicamente, existem tantos padrões, frameworks e comunidades diferentes, que o resultado tende a ser o caos!

Comparando com minha experiência anterior (com C#, Python, Ruby e PHP) a quantidade de bike shedding (discussão inútil) que acontece no mundo JS é incomensurável. Pessoalmente, a discussão e tumulto que ocorre na minha atual equipe equipe (trabalhamos com Node) acontecem com muito mais frequência do que com outras linguagens: paradigma, var const ou let, framework, patterns, como fazer oop, organização dos diretórios, pre-compilers, test runner, assertion lib, etc, etc.

E por fim, as chances de acabar com um código frankenstein são grandes. Onde fica difícil de entender e manter o sistema.


Como contornar esses problemas

1 - Usar UM framework feature rich

Em resposta à Nano bibliotecas e a gestão de dependências

Enquanto desenvolvedores NodeJS continuarem a "juntar" ExpressJS (, Hapi, Restify, Koa ou outro microframework), middlewares e bibliotecas para "criar seu próprio framework" as pessoas ainda vão ter a impressão de que NodeJS não é estável nem produtivo.

"Vanilla is malpractice, 'File -> New Project' is malpractice. Start with something more opinionated." - Cory House

Cory House fez uma excelente talk sobre esse assunto: The Reusable JavaScript REVOLUTION

Cory recomenda que equipes façam o uso de um "starter kit opinionated". Em sua apresentação ele também explora exemplos e práticas de starter kits. No entanto o que eu quero salientar que não é preciso criar um "starter kit" do zero.

A solução pra isso é usar um starter kit público ou framework mais completo e com um ecossistema mais desenvolvido como por exemplo AdonisJS. Isso é um ponto chave para ter produtividade e capacidade de fazer o seu projeto evoluir.

Se você não concorda com isso, acompanhe o raciocínio: O desenvolvimento para web chegou no patamar atual graças a frameworks completos e que aceleraram o desenvolvimento.

Ruby-on-Rails, Django, Laravel, Play e Phoenix tem um lema em comum: "Produtividade e estabilidade":

Framework slogans

Slogans de bons frameworks: "produtividade e estabilidade"

Solução: Utilizar um framework "feature rich" focado em estabilidade e produtividade como AdonisJS (minha preferência), Sails ou Meteor

(atualização 02/05/2018): Essa mesma tendência de "produtividade e estabilidade" ocorre no próprio JavaScript, no frontend. Angular, Ember e VueJS são alguns exemplos.

Não se preocupe com o suposto "inchaço" de framework mais completos. Eles permitem que funcionalidades sejam desligadas caso não sejam necessárias, sendo possível atingir níveis de performance melhores que os nano frameworks

Existem mais alguns argumentos referentes a usar um Framework feature rich no item número "3 - Padronizar o código escrito" mais adiante...

2 - Use o novo ECMAScript e faça code-reviews

Em resposta à Standard library inconsistente

Visto que comportamentos antigos da linguagem jamais serão "depreciados", não existe uma solução ideal para esse problema. Douglas Crockford, fez uma talk onde ele expressa alguns problemas (sem solução) do JavaScript.

Minha equipe prefere utilizar o Lodash ao invés da standard library em diversos casos. Para dar um pouco de contexto, veja os problemas com o Array.sort() quebrado do JS.

Outras ações importantes para amenizar os problemas da standard library são: Utilizar a versão mais moderna de ES (ES2017 no momento em que escrevo esse artigo), Usar ESLint e ter um processo de code-review na sua equipe para escrever um código um mais limpo e menos dependente da parte "suja" da standard library. (Recomendações válida para projetos com linguagens como Java, PHP, C++)

Solução: Escrever ES2017+, Lodash (ou similar) e Code-reviews

3 - Padronizar o código escrito

Em resposta à Bagunça de padrões

Pessoalmente eu encontrei conforto no StandardJS, um padrão de estilo de código bastante estrito que ajuda muito a escrever um código legível e compreensível.

Além do StandardJS existem algumas outras regras do ESLint que ajudam a controlar a manutenibilidade do código: complexity:7, require-jsdoc, valid-jsdoc, max-depth:4, no-nested-ternary.

Exemplo de configuração do ESLint que utilizamos:

extends: standard ## Includes all "standardjs" rules

## [Omitted non-important things]

rules:
  no-console: error ## check "console.log()"
  mocha/no-exclusive-tests: error ## check ".only()"
  complexity: [error, 7] ## Most important rule for clean-code
  max-depth: 4
  valid-jsdoc: error
  no-nested-ternary: error
  no-lonely-if: error
  no-else-return: error
  no-confusing-arrow: error
  camelcase: error ## force camelCase

JavaScript é tão permissivo que ouso dizer que é impossível implementar um projeto sério sem um coding style + linter bastante estrito e executado com frequência.

Na minha equipe temos um commit hook que executa o ESlint e os testes unitários. Logo após o git push, nosso CI executa novamente o linter, os testes unitários e dessa vez os testes de integração. Tudo automatizado, isso tem nos ajudado muito a manter o código limpo e padronizado.

Outra coisa que auxilia bastante a encontrar standards de desenvolvimento é "1 - Usar UM framework feature rich". Conforme mencionei, o uso de um framework desse tipo dá padrões opinados sobre: Estrutura de diretórios, organização de rotas, middlewares, exception handling, formato de migrations, sessions, websockets, logs, models, i18n, event handlers, test framework, test runners, configurações, etc...

Parece óbvio, mas muita gente da comunidade JS ainda ignora o fato de que dois ou mais desenvolvedores que conhecem o mesmo ~~não-micro~~ framework (Rails, Laravel, Play, Spring ou similar) conseguem colaborar e trabalhar juntos de forma relativamente fácil.

Solução: ESLint bastante estrito, StandardJS e utilizar um framework "feature rich".

Conclusão

A natureza assíncrona do JavaScript é muito poderosa e muito útil para o Backend.

(atualização 02/05/2018): Caso algum dos problemas acima tenha soado familiar, saiba que ...

... hoje é possível contorna-los e usufruir de todos os benefícios com produtividade e estabilidade.

Se você concorda ou discorda, não hesite em comentar abaixo.