David Harron, autor do material que estamos publicando hoje, fez a seguinte pergunta: “Uma pessoa que trabalhou por mais de 10 anos na Sun Microsystems na equipe Java SE, até o último suspiro, deve pensar apenas no bytecode Java e criar instâncias de interfaces abstratas? " Ele fez essa pergunta em relação a si mesmo e, para ele, a plataforma Node.js, depois do Java, acabou sendo uma lufada de ar fresco. David diz que, quando foi demitido da Sun em janeiro de 2009 (pouco antes da aquisição desta empresa Oracle), ele descobriu o Node.js. Essa tecnologia o fisgou. O que significa "viciado"? Desde 2010, ele escreveu muito sobre programação para o Node.js. Nomeadamente, ele escreveu vários livros, incluindo o Node.js Web Development, cuja quarta edição foi lançada este ano. Ele preparou muitos materiais pequenos sobre o Node.js publicados na Internet. Na verdade, ele gastou muito tempo e esforço conversando sobre a plataforma Node.js. e os recursos JavaScript. Por que os que anteriormente trabalhavam exclusivamente em Java estavam tão atraídos pelo Node.js e pelo JavaScript?

Sobre o mundo Java
Enquanto trabalhava na Sun, eu acreditava na tecnologia Java. Fiz apresentações no JavaONE, participei do desenvolvimento da classe java.awt.Robot, organizei o evento Mustang Regressions Contest (era um concurso destinado a encontrar erros no Java 1.6), ajudei a lançar o projeto Distributions License for Java, que serviu de resposta à pergunta sobre distribuições Linux JDK antes do advento do OpenJDK. Mais tarde, participei do lançamento do projeto OpenJDK. Ao longo do caminho, por cerca de 6 anos, publiquei material de blog em java.net (agora este site está fechado). Tratava-se de 1 a 2 artigos por semana, dedicados a eventos significativos no ecossistema Java. Um papel significativo em meu trabalho foi desempenhado ao proteger o Java daqueles que previram um futuro sombrio para essa tecnologia.
Esse prêmio, o Duke Award, foi concedido aos funcionários mais destacados da Sun. Comprei depois que organizei o Concurso de Regressões do MustangO que aconteceu com a pessoa que fez tanto com tudo relacionado ao Java? Na verdade, aqui quero falar sobre como mudei de um devoto de Java para um fervoroso suporte a Node.js e JavaScript.
Devo dizer que o que aconteceu comigo não pode ser chamado de abandono completo do Java. Nos últimos 3 anos, escrevi bastante código Java, usei o Spring e o Hibernate. Embora eu realmente goste do que agora faço nesta área (trabalho no setor de energia solar, faço o que gosto de fazer, por exemplo - escrevo solicitações para trabalhar com dados do setor de energia), programação em Java agora perdeu seu antigo esplendor.
Dois anos de desenvolvimento usando o Spring me permitiram esclarecer claramente uma coisa importante: uma tentativa de ocultar mecanismos complexos não leva à simplicidade, apenas leva ao aparecimento de estruturas ainda mais complexas.
Aqui estão, em resumo, as principais idéias que abordarei neste material:
- Os programas Java estão cheios de códigos padronizados que ocultam as intenções do programador.
- Trabalhar com o Spring e o Spring Boot me deu uma boa lição: tentar ocultar mecanismos complexos leva a construções ainda mais complexas.
- A plataforma Java EE foi um projeto criado, por assim dizer, por "esforços comuns", que cobrem absolutamente todas as necessidades do desenvolvimento de aplicativos corporativos. Como resultado, a plataforma Java EE se mostrou proibitiva.
- Desenvolver com o Spring é, até certo ponto, uma experiência agradável. Essa ilusão desaparece no dia em que uma exceção que é completamente impossível de entender surge das profundezas de um subsistema que você nunca ouviu falar e leva pelo menos três dias para descobrir qual é o problema.
- Quais mecanismos auxiliares que criam uma carga excessiva no sistema você precisa de uma estrutura que possa "escrever" código para programadores?
- Embora IDEs como o Eclipse sejam aplicativos poderosos, eles são um indicador da complexidade do ecossistema Java.
- A plataforma Node.js surgiu como resultado dos esforços de uma pessoa para melhorar sua visão de arquitetura leve orientada a eventos.
- A comunidade JavaScript parece entusiasmada em se livrar do código padrão, que permite aos programadores expressar suas intenções da maneira mais clara possível.
- O assíncrono / espera é uma solução para o problema do inferno de retorno de chamada JS, que é um exemplo de rejeição do código do modelo e contribui para a clareza da expressão das intenções dos programadores.
- A programação para o Node.js é um verdadeiro prazer.
- Não há digitação forte específica para Java em JavaScript. Esta é a bênção e maldição da língua. Isso facilita a escrita do código, mas, para verificar sua correção, você precisa dedicar mais tempo ao teste.
- O sistema de gerenciamento de pacotes introduzido pelo npm / yarn é fácil e divertido de usar. Ela não pode ser comparada com Maven.
- Java e Node.js oferecem excelente desempenho. Isso contraria o mito de que o JavaScript é uma linguagem lenta, cujo uso leva ao baixo desempenho da plataforma Node.js.
- O Performance Node.js se baseia nos esforços do Google para melhorar o V8, o mecanismo que afeta a velocidade do navegador Chrome.
- A forte concorrência entre fabricantes de mecanismos JS baseados em navegador contribui para o desenvolvimento do JavaScript, e isso é muito benéfico para o Node.js.
Sobre problemas de desenvolvimento Java
Algumas ferramentas ou objetos são o resultado de muitos anos de esforços dos engenheiros para aprimorá-los. Os programadores tentam idéias diferentes, removem atributos desnecessários e, como resultado, obtêm entidades nas quais existe exclusivamente o necessário para resolver um determinado problema. Freqüentemente, essas tecnologias têm um tipo de simplicidade muito atraente que oculta recursos poderosos. Isso não se aplica ao Java.
O Spring é uma estrutura popular para o desenvolvimento de aplicativos da Web baseados em Java.
O principal objetivo do Spring, e em particular o Spring Boot, é fornecer a capacidade de usar a pilha Java EE pré-configurada. O programador que usa o Spring não deve, para criar um sistema pronto, cuidar de servlets, sistemas de armazenamento persistente, servidores de aplicativos e o que ainda é desconhecido. Todas essas preocupações são passadas para o Spring, e o programador está escrevendo um código que implementa a lógica do aplicativo. Por exemplo, os mecanismos JPARepository são responsáveis por gerar consultas ao banco de dados para métodos cujos nomes se parecem com
findUserByFirstName
. O programador não precisa escrever o código para esses métodos. Basta passar a descrição do método para o sistema, e o Spring fará o resto.
Tudo parece muito bom, é bom trabalhar nesse estilo, mas - até que algo inesperado aconteça.
Refiro-me a uma situação em que, por exemplo, um Hibernate
PersistentObjectException
é lançado com a mensagem que a
detached entity passed to persist
. O que isso poderia significar? Demorou vários dias para descobrir. Como se viu, se você descrever tudo de uma maneira muito simplificada, isso significa que os dados JSON recebidos no terminal REST possuem campos de ID com alguns valores. O Hibernate, novamente, se não entrar em detalhes, procura controlar os valores de ID e, como resultado, lança a exceção obscura acima. Existem milhares de mensagens de erro que são confusas e difíceis de ler. Considerando que existem cascatas inteiras de subsistemas baseadas uma na outra no Spring, a pilha do Spring parece um inimigo jurado de um programador que o observa e espera que o programador cometa o menor erro e, quando isso acontece, lança exceções que não são compatíveis com ele operação normal do aplicativo.
Além disso, aqui você pode recuperar os rastreamentos de pilha mais longos. Eles representam várias telas cheias de todos os tipos de métodos abstratos. O Spring obviamente cria a configuração necessária para implementar o que é expresso no código. Esse nível de abstração, sem dúvida, requer uma quantidade considerável de lógica auxiliar, cujo objetivo é descobrir tudo o que é necessário para o código funcionar, por exemplo, para atender solicitações. E os rastreamentos de pilha longa não são necessariamente ruins. Tais coisas provavelmente são um sintoma, levando à questão de que tipo de carga no sistema é criada por mecanismos auxiliares.
Como o método
findUserByFirstName
é
findUserByFirstName
, considerando que o programador não escreveu o código para esse método? A estrutura precisa analisar o nome do método, entender a intenção do programador, criar algo como uma árvore de sintaxe abstrata, gerar algum tipo de código SQL e assim por diante. Como tudo isso carrega o sistema? E tudo isso existe apenas para que o programador não precise escrever código?
Depois de passar algumas dezenas de vezes em algo como procurar o significado do erro acima, passar semanas tentando desvendar segredos que, em geral, você não deve desvendar, pode chegar à mesma conclusão que cheguei a . Seu significado é que uma tentativa de ocultar mecanismos complexos não leva à simplicidade, apenas leva ao aparecimento de estruturas ainda mais complexas. A plataforma Node.js é muito mais simples.
O slogan "Compatibility Matters" ocultava uma idéia maravilhosa, segundo a qual a compatibilidade com versões anteriores era a característica mais importante da plataforma Java. Levamos isso a sério, colocando imagens em camisetas como a que você pode ver abaixo.
A compatibilidade com versões anteriores é muito importante.Obviamente, esse nível de atenção à compatibilidade com versões anteriores pode ser uma fonte de ansiedade constante e, de tempos em tempos, é útil afastar-se dos mecanismos antigos que não se beneficiam mais.
Java e Node.js
Spring e Java EE são excessivamente complicados. A plataforma Node.js, em seu contexto, é vista como uma lufada de ar fresco. A primeira coisa que você percebe ao se familiarizar com o Node.js é a abordagem de Ryan Dahl para o desenvolvimento do núcleo da plataforma. Sua experiência lhe disse que as plataformas que usam fluxos são necessárias para criar sistemas complexos e pesados. Ele estava procurando outra coisa e passou alguns anos aprimorando o conjunto de mecanismos básicos incorporados no Node.js. Como resultado, ele obteve um sistema leve que se caracteriza por um único encadeamento de execução, uso inventivo de funções JavaScript anônimas como retornos de chamada assíncronos e uma biblioteca de tempo de execução que originalmente implementa mecanismos assíncronos. A mensagem inicial ao criar esse sistema foi fornecer processamento de eventos de alto desempenho com a entrega desses eventos em uma função de retorno de chamada.
A seguir, um recurso importante do Node.js é o uso de JavaScript. Há um sentimento de que aqueles que escrevem em JS tendem a se livrar do código do modelo, o que lhes permite descrever claramente as intenções do programador.
Como um exemplo da diferença entre Java e JavaScript, considere a implementação de funções de ouvinte (observadores). Em Java, para trabalhar com ouvintes, é necessário criar uma instância específica da interface abstrata. Isso implica o uso de construções volumosas de linguagem que ocultam a essência do que está acontecendo. Como discernir a intenção de um programador escondida sob as cobertas do código padrão?
O JavaScript usa funções anônimas simples. Ao implementar um ouvinte, você não precisa procurar uma interface abstrata adequada. É suficiente, sem a necessidade de usar uma variedade de textos auxiliares, para escrever o código necessário.
Portanto, aqui está uma idéia importante que pode ser extraída da análise dos mecanismos acima: a maioria das linguagens de programação oculta as intenções do programador, o que leva ao fato de que o código é difícil de entender.
A decisão sobre o uso das funções de retorno de chamada que o Node.js oferece parece muito atraente. Mas não é sem problemas.
Resolução de Problemas e Resolução de Problemas
No JavaScript, há muito tempo existem dois problemas associados à programação assíncrona. O primeiro é o que o Node.js chama de inferno de retorno de chamada. Esse problema está no fato de que, durante o desenvolvimento, é fácil cair em uma armadilha criada a partir de funções de retorno de chamada profundamente aninhadas, em que cada nível de aninhamento complica o programa, além de processar os resultados de código e erros. Havia outro problema relacionado a isso, cuja essência era que os mecanismos da linguagem JavaScript não ajudavam o programador a expressar adequadamente idéias de execução de código assíncrona.
Várias bibliotecas surgiram para simplificar o desenvolvimento assíncrono em JS. Mas este é outro exemplo de uma tentativa de ocultar mecanismos complexos que apenas levam ao aparecimento de estruturas ainda mais complexas.
Considere um exemplo:
const async = require('async'); const fs = require('fs'); const cat = function(filez, fini) { async.eachSeries(filez, function(filenm, next) { fs.readFile(filenm, 'utf8', function(err, data) { if (err) return next(err); process.stdout.write(data, 'utf8', function(err) { if (err) next(err); else next(); }); }); }, function(err) { if (err) fini(err); else fini(); }); }; cat(process.argv.slice(2), function(err) { if (err) console.error(err.stack); });
Esta é uma imitação indefinida do
cat
do Unix. A biblioteca
async
na simplificação de sequências de chamadas assíncronas. No entanto, seu uso requer uma grande quantidade de código padrão que oculta a intenção do programador.
Em essência, esse código contém um loop. Não está escrito como um ciclo regular, não utiliza as construções naturais da descrição de ciclos. Além disso, os resultados da execução do código e os erros gerados por eles não chegam ao local correto. Eles estão bloqueados nos retornos de chamada, o que é inconveniente. Porém, antes da implementação dos padrões ES2015 / 2016 no Node.js, nada melhor poderia ser feito.
Se reescrevermos esse código levando em consideração novos recursos, que estão disponíveis no Node.j 10.x, em particular, obteremos o seguinte:
const fs = require('fs').promises; async function cat(filenmz) { for (var filenm of filenmz) { let data = await fs.readFile(filenm, 'utf8'); await new Promise((resolve, reject) => { process.stdout.write(data, 'utf8', (err) => { if (err) reject(err); else resolve(); }); }); } } cat(process.argv.slice(2)).catch(err => { console.error(err.stack); });
Neste exemplo, usamos a construção
async/await
. Os mesmos mecanismos assíncronos são apresentados aqui como no exemplo anterior, mas as estruturas usuais usadas na organização de loops são usadas aqui. Trabalhar com resultados e erros parece bastante normal. Esse código é mais fácil de ler e escrever. Essa abordagem facilita a compreensão da intenção do programador.
A única desvantagem é que
process.stdout.write
não possui uma interface Promise, como resultado, esse mecanismo não pode ser usado em funções assíncronas sem envolvê-lo em uma promessa.
Agora podemos concluir que o problema do inferno de retorno de chamada no JavaScript foi resolvido de uma maneira diferente da tentativa de ocultar mecanismos complexos. Em vez disso, foram feitas alterações no idioma, que resolveu o problema em si, e nos salvou do inconveniente causado pela necessidade de usar grandes quantidades de código de modelo em uma solução temporária. Além disso, com o uso do mecanismo assíncrono / espera, o código simplesmente se tornou mais bonito.
Iniciamos esta seção com uma discussão sobre a falha do Node.js, mas uma ótima solução para o inferno de um retorno de chamada fez com que a conversa sobre falhas se transformasse em uma conversa sobre os pontos fortes do Node.js e do JavaScript.
Digitação forte, interfaces e clareza de código imaginário
Naqueles dias, quando eu protegia o Java de todos os tipos de ataques, enfatizei que a digitação estrita permite que você escreva aplicativos enormes. Naqueles dias, o desenvolvimento de sistemas monolíticos estava em uso (não havia microsserviços, não havia Docker e similares). Como Java é uma linguagem fortemente tipada, o compilador Java ajuda o programador a evitar muitos problemas, impedindo-o de compilar o código errado.
JavaScript, ao contrário de Java, não é fortemente digitado. A partir disso, podemos concluir que o programador não sabe exatamente com quais objetos ele tem que trabalhar. Como um programador pode saber o que fazer, por exemplo, com um determinado objeto recebido de algum lugar?
O outro lado da forte digitação Java é que você precisa executar constantemente ações padrão. O programador está constantemente lançando ou verificando se tudo está exatamente como o esperado. O desenvolvedor gasta tempo escrevendo código, o faz com precisão excepcional, usa volumes consideráveis de modelos e espera que tudo isso o ajude a economizar tempo detectando e corrigindo precocemente erros.
O problema de programar em uma linguagem fortemente tipada é tão grande que um programador, quase sem opções, deve usar um IDE grande e complexo. Um editor de código simples não é suficiente aqui. A única maneira de manter o programador Java em condições adequadas (com exceção da pizza) é mostrar-lhe constantemente listas suspensas contendo campos de objetos disponíveis ou descrições de parâmetros de métodos. Esse e outros mecanismos de suporte de IDEs, como Eclipse, NetBeans ou IntelliJ, ajudam na criação de classes, facilitam a refatoração e outras tarefas.
E ... eu não vou falar sobre Maven. Esta é apenas uma ferramenta de pesadelo.
No JavaScript, os tipos de variáveis não são especificados quando são declarados, a conversão de tipos geralmente não é usada e assim por diante. Como resultado, o código é mais fácil de ler, mas esse estado de coisas também significa o risco de erros de programação difíceis de detectar.
Se o precedente está relacionado às vantagens de Java ou às desvantagens depende do ponto de vista.
Dez anos atrás, eu acreditava que todas essas dificuldades se justificam, dando ao programador maior confiança no código que ele escreve. Hoje, acredito que a digitação forte aumenta a carga de trabalho do programador e é muito mais fácil desenvolver projetos do que em JavaScript.
Lute contra erros com pequenos módulos fáceis de testar
O Node.js empurra o programador para dividir seus projetos em pequenos fragmentos, nos chamados módulos. Você pode achar esse fato insignificante, mas resolve parcialmente o problema que acabamos de mencionar.
Aqui estão as principais características do módulo:
- Independência O módulo combina código interconectado em uma única entidade.
- Limites claros. O código dentro do módulo é protegido contra interferências por quaisquer mecanismos externos.
- Exportação explícita. Por padrão, os dados do código e do módulo não são exportados. O desenvolvedor decide independentemente quais funções e dados precisam ser disponibilizados ao público.
- Importação explícita. Ao desenvolver um módulo, o programador decide por si mesmo de quais módulos ele dependerá.
- Independência potencial. Os módulos podem ser disponibilizados ao público, no sentido mais amplo da palavra, publicando-os em npm ou, se forem destinados às necessidades internas da empresa, publicando em repositórios fechados. Isso facilita o uso dos mesmos módulos em diferentes aplicativos.
- Código fácil de entender. O fato de os módulos serem pequenos, simplifica a leitura e a compreensão de seu código, abre a possibilidade de discussão livre sobre eles.
- Facilite o teste. Um pequeno módulo, se implementado corretamente, pode ser facilmente testado em unidade.
Tudo isso torna os módulos do Node.js. entidades com limites claramente definidos, cujo código é fácil de escrever, ler e testar.
No entanto, se preocupar em trabalhar com JavaScript é o fato de que a falta de digitação forte pode facilmente levar o código a fazer algo errado. Em um pequeno módulo destinado a resolver um problema estreito com limites claros, "algo está errado" pode afetar apenas o código do próprio módulo. Isso leva ao fato de que os problemas que podem causar a falta de digitação estrita estão bloqueados no módulo.
Outra solução para o problema de digitação dinâmica em JavaScript é testar completamente o código.
O desenvolvedor precisa levar os testes a sério, o que tira parte dos benefícios advindos da simplicidade do processo de desenvolvimento do JS. Os sistemas de teste criados por um programador JS devem encontrar os erros que, se desenvolvidos por ele em algo como Java, o compilador pode encontrar automaticamente. Você está escrevendo testes para seus aplicativos JS?
Para aqueles que precisam de um sistema de digitação estática em JavaScript, pode ser útil dar uma olhada no TypeScript. Eu não uso essa linguagem, mas ouvi muitas coisas boas sobre ela. É compatível com JavaScript e estende a linguagem com um sistema de controle de tipo e outros recursos úteis.
No final, podemos dizer que o uso de uma abordagem modular para o desenvolvimento é a força do Node.js e do JavaScript.
Gerenciamento de pacotes
Eu me sinto mal com o simples pensamento de Maven, então nem consigo escrever normalmente sobre isso. E, pelo que entendi, Maven, sem compromisso, é amado ou odiado.
O problema aqui é que, no ambiente Java, não existe um sistema holístico para gerenciar pacotes. Existem pacotes Maven, você pode trabalhar com eles normalmente, eles são suportados pelo Gradle. Mas a maneira como o trabalho com eles é organizado não se parece muito com as conveniências que o sistema de gerenciamento de pacotes do Node.js. fornece ao desenvolvedor.
No mundo do Node.js, existem dois grandes gerenciadores de pacotes que trabalham em estreita colaboração. No início, a única ferramenta desse tipo era o repositório npm e a ferramenta de linha de comando com o mesmo nome.
Graças ao npm, temos um excelente esquema para descrever dependências de pacotes. As dependências podem ser rigorosas (por exemplo, é indicado que apenas a versão 1.2.3 de um determinado pacote é necessária) ou fornecida com vários graus de liberdade - até
*
, o que significa usar a versão mais recente de um determinado pacote.
A comunidade Node.js. publicou centenas de milhares de pacotes no repositório npm. Ao mesmo tempo, usar pacotes que não estão no npm é tão fácil quanto usar pacotes do npm.
O sistema npm acabou sendo tão bem-sucedido que não apenas os desenvolvedores de produtos para servidor no Node.js o utilizam, mas também os programadores de front-end. Anteriormente, ferramentas como o Bower eram usadas para gerenciar pacotes. O Bower foi descontinuado e agora você pode descobrir que todas as bibliotecas JS para desenvolvimento de front-end existem como pacotes npm. Muitas ferramentas de suporte ao desenvolvimento de clientes, como a CLI Vue.js e Webpack, são gravadas como aplicativos Node.js.
Outro sistema de gerenciamento de pacotes para o Node.js., yarn, baixa pacotes do repositório npm e usa os mesmos arquivos de configuração. A principal vantagem do fio sobre o gerenciador de pacotes npm é sua velocidade mais alta.
O repositório npm, seja usando o gerenciador de pacotes npm ou o gerenciador de pacotes yarn, é uma base poderosa para o que torna o desenvolvimento do Node.js tão fácil e agradável.
Uma vez, depois de ajudar a desenvolver o java.awt.Robot, fui inspirado a criar essa coisa. Enquanto a imagem oficial do Duke é composta de curvas, o RoboDuke é construído a partir de linhas retas. Somente as articulações do cotovelo deste robô são redondasDesempenho
Java, JavaScript . -, , . , , - .
Java, JavaScript . Java Node.js, . JavaScript . -.
JDK Sun/Oracle HotSpot — , -. , , , , , . HotSpot — , .
JavaScript, , JS-, , , - . , JavaScript . , , . , , Google Docs, . JS .
Node.js , V8 Google Chrome.
, Google, V8, . , V8 Crankshaft Turbofan.
— , , R Python. , , . JavaScript, , ,
JavaScript.
JavaScript , TensorFlow.js. API API TensorFlow Python, . , , , .
IBM, Node.js, , , Docker/Kubernetes. , Node.js Spring Boot. -, . , Node.js , , , V8.
, Node.js . . - , Node.js , . , «Node.js Web Development», , :
JavaScript , Node.js. — Node.js-. Node.js-
node-gyp
, .
, Rust- Node.js.
WebAssembly , , JavaScript, . WebAssembly , JavaScript-.
WebAssembly Node.js.
-
- (Rich Internet Applications, RIA) . , , ( ) JS-, .
, 20 . Sun Netscape Java- Netscape Navigator. JavaScript , , Java-. , Java-, — Java-. , . .
JavaScript , . RIA, , - Java -.
, RIA . Node.js , , , . JavaScript.
Aqui estão alguns exemplos:
- Google Docs ( ), , .
- , React, Angular Vue.js, , HTML, CSS JavaScript.
- Electron — Node.js Chromium. - . , Visual Studio Code, Atom, GitKraken, Postman.
- Electron/NW.js , -, , React, Angular, Vue, .
Java, , - -, JavaScript. , , - Sun Microsystems. Sun , . . Java- , Java Java Web Start. Java- Webstart-.
Java, , , IDE NetBeans Eclipse . Java , , Java.
JavaFX.
JavaFX, 10 , Sun iPhone. , Java, , , . Flash iOS. . JavaFX , , . - React, Vue.js .
JavaScript Node Java.
Java, - JavaONE. Java . , , , .
Java
Sumário
. «P-» (Perl, PHP, Python) Java, Node.js, Ruby, Haskell, Go, Rust, . .
, , , Java, Node.js, , , Node.js-. Java , Node.js . , , , Java, .
. , , Node.js - , - . . , XBRL-. XBRL Python, , , Python. , , , .
Caros leitores! , , JavaScript - , - Node.js, .
