Em nossa pesquisa de desenvolvimento de 2019, perguntamos que tipo de conteúdo os usuários do Avance Network gostariam de ver além das perguntas e respostas. A resposta mais popular foi “artigos de tecnologia escritos por outros desenvolvedores”. Portanto, de agora em diante, publicaremos regularmente artigos de colaboradores. Se você tiver uma ideia e quiser enviar um argumento de venda, envie uma mensagem.
Não vejo gente suficiente falando sobre maneiras práticas de melhorar o JavaScript. Aqui estão alguns dos principais métodos que uso para escrever um JS melhor.
Use TypeScript
A primeira coisa que você pode fazer para melhorar seu JS é não escrever JS. Para os não iniciados, TypeScript (TS) é um superconjunto “compilado” de JS (qualquer coisa que seja executada em JS é executada em TS). TS adiciona um sistema de digitação opcional abrangente em cima da experiência JS vanilla. Por muito tempo, o suporte de TS em todo o ecossistema era inconsistente o suficiente para que eu me sentisse desconfortável em recomendá-lo. Felizmente, esses dias estão muito atrás de nós e a maioria dos frameworks oferece suporte a TS fora da caixa. Agora que estamos todos na mesma página sobre o que é TS , vamos falar sobre por que você gostaria de usá-lo.
TypeScript impõe segurança de tipo
A segurança de tipo descreve um processo em que um compilador verifica se todos os tipos estão sendo usados de maneira legal em todo o código. Em outras palavras, se você criar uma função fooque recebe um número:
function foo(someNum: number): number {
return someNum + 5;
}
Essa foofunção deve ser sempre chamada com um número:
Boa
console.log(foo(2)); // prints "7"
nada de bom
console.log(foo("two")); // invalid TS code
Além da sobrecarga de adicionar tipos ao seu código, não há desvantagens na aplicação de segurança de tipo. O benefício, por outro lado, é grande demais para ser ignorado. A segurança de tipo fornece um nível extra de proteção contra erros / bugs comuns, o que é uma bênção para uma linguagem sem lei como JS.
Tipos de texto datilografado tornam possível a refatoração de aplicativos maiores
Refatorar um grande aplicativo JS pode ser um verdadeiro pesadelo. Grande parte da dor de refatorar JS se deve ao fato de que ele não impõe assinaturas de função. Isso significa que uma função JS nunca pode ser mal utilizada. Por exemplo, se eu tiver uma função myAPIque é usada por 1000 serviços diferentes:
function myAPI(someNum, someString) {
if (someNum 0) {
leakCredentials();
} else {
console.log(someString);
}
}
e eu mudo um pouco a assinatura de chamada:
function myAPI(someString, someNum) {
if (someNum 0) {
leakCredentials();
} else {
console.log(someString);
}
}
Tenho que ter 100% de certeza de que em cada lugar onde esta função é usada (milhares de lugares), eu atualizo corretamente o uso. Se eu perder um, minhas credenciais podem vazar. Aqui está o mesmo cenário com TS:
antes
function myAPITS(someNum: number, someString: string) { ... }
depois de
function myAPITS(someString: string, someNum: number) { ... }
Como você pode ver, a myAPITSfunção passou pela mesma mudança que a contraparte JavaScript. Mas, em vez de resultar em JavaScript válido, esse código resulta em TypeScript inválido, pois os milhares de lugares que ele usa agora fornecem os tipos errados. E por causa da segurança de tipo que discutimos antes, esses milhares de casos bloquearão a compilação e suas credenciais não vazarão (isso é sempre bom).
TypeScript torna a comunicação da arquitetura da equipe mais fácil
Quando o TS está configurado corretamente, será difícil escrever código sem primeiro definir suas interfaces e classes. Isso fornece uma maneira de compartilhar propostas concisas e comunicativas de arquitetura. Antes do TS, outras soluções para esse problema existiam, mas nenhuma o resolvia nativamente sem obrigar você a fazer um trabalho extra. Por exemplo, se eu quiser propor um novo Requesttipo para meu back-end, posso enviar o seguinte a um colega de equipe usando TS.
interface BasicRequest {
body: Buffer;
headers: { [header: string]: string | string[] | undefined; };
secret: Shhh;
}
Já tive que escrever o código, mas agora posso compartilhar meu progresso incremental e obter feedback sem investir mais tempo. Não sei se o TS é inerentemente menos sujeito a bugs do que o JS. Eu realmente acredito que forçar os desenvolvedores a definir interfaces e APIs primeiro resulta em um código melhor.
No geral, o TS evoluiu para uma alternativa madura e mais previsível ao vanilla JS. Definitivamente, os desenvolvedores ainda precisam se sentir confortáveis com o JS vanilla, mas a maioria dos novos projetos que inicio hoje em dia são TS desde o início.
Use recursos modernos
JavaScript é uma das linguagens de programação mais populares (se não a mais) do mundo. Você pode esperar que uma linguagem com mais de 20 anos, usada por centenas de milhões de pessoas, já seja quase totalmente descoberta agora, mas o oposto é realmente verdadeiro. Nos últimos tempos, muitas mudanças e adições foram feitas ao JS (sim, eu sei, tecnicamente ECMAScript), fundamentalmente transformando a experiência do desenvolvedor. Como alguém que só começou a escrever JS nos últimos dois anos, tive a vantagem de entrar sem preconceitos ou expectativas. Isso resultou em escolhas muito mais pragmáticas sobre quais recursos da linguagem utilizar e quais evitar.
async e await
Por muito tempo, callbacks assíncronos baseados em eventos foram uma parte inevitável do desenvolvimento de JS:
retorno de chamada tradicional
makeHttpRequest('google.com', function (err, result) {
if (err) {
console.log('Oh boy, an error');
} else {
console.log(result);
}
});
Não vou perder tempo explicando por que o acima exposto é problemático ( mas já fiz isso antes ). Para resolver o problema com retornos de chamada, um novo conceito - Promises - foi adicionado ao JS. As promessas permitem que você escreva lógica assíncrona enquanto evita os problemas de aninhamento que anteriormente afetavam o código baseado em retorno de chamada.
Promessas
makeHttpRequest('google.com').then(function (result) {
console.log(result);
}).catch(function (err) {
console.log('Oh boy, an error');
});
A maior vantagem do Promises em relação aos retornos de chamada é a legibilidade e o encadeamento.
Embora as promessas sejam ótimas, elas ainda deixam a desejar. Para muitos, a experiência Promise ainda lembrava muito os callbacks. Especificamente, os desenvolvedores estavam pedindo uma alternativa para o modelo Promise. Para remediar isso, o comitê ECMAScript decidiu adicionar um novo método de utilização de promessas asynce await:
async e await
try {
const result = await makeHttpRequest('google.com');
console.log(result);
} catch (err) {
console.log('Oh boy, an error');
}
A única ressalva é, qualquer coisa que você awaitdeve ter sido declarada async:
definição necessária de makeHttpRequest no exemplo anterior
async function makeHttpRequest(url) {
// ...
}
Também é possível fazer awaituma promessa diretamente, pois uma asyncfunção é, na verdade, apenas um invólucro sofisticado de uma promessa. Isso também significa que o async/awaitcódigo e o código Promise são funcionalmente equivalentes. Portanto, fique à vontade para usar async/awaitsem se sentir culpado.
let e const
Para a maioria da existência de JS, havia apenas um qualificador de escopo de variáveis: var. vartem algumas regras bastante exclusivas / interessantes em relação a como ele lida com o escopo. O comportamento do escopo do varé inconsistente e confuso e resultou em um comportamento inesperado e, portanto, em bugs ao longo da vida do JS. Mas a partir do ES6, há uma alternativa para var: conste let. Praticamente não há mais necessidade de usar var, então não use . Qualquer lógica que use varsempre pode ser convertida em código equivalente conste letbaseado.
Quanto a quando usar constvs let, sempre começo declarando tudo const. consté muito mais restritivo e “imutável”, o que geralmente resulta em um código melhor. Não há muitos “cenários reais” onde o uso leté necessário, eu diria variáveis 1/20 que declaro com let. O resto é tudo const.
Eu disse que consté “imutável” porque não funciona da mesma forma que constem C / C ++. O que constsignifica para o tempo de execução do JavaScript é que a referência a essa constvariável nunca será alterada. Isso não significa que o conteúdo armazenado nessa referência nunca será alterado. Para tipos primitivos (número, booleano, etc.), constse traduz em imutabilidade (porque é um único endereço de memória). Mas para todos os objetos (classes, arrays, dicts), constnão garante imutabilidade.
=Funções de seta
As funções de seta são um método conciso de declarar funções anônimas em JS. Funções anônimas descrevem funções que não são nomeadas explicitamente. Normalmente, as funções anônimas são passadas como um retorno de chamada ou gancho de evento.
função anônima vanilla
someMethod(1, function () { // has no name
console.log('called');
});
Na maioria das vezes, não há nada de “errado” com esse estilo. As funções anônimas Vanilla se comportam “de maneira interessante” em relação ao escopo, o que pode / resultou em muitos bugs inesperados. Não precisamos mais nos preocupar com isso graças às funções de seta. Aqui está o mesmo código implementado com uma função de seta:
função de seta anônima
someMethod(1, () = { // has no name
console.log('called');
});
Além de ser muito mais conciso, as funções de seta têm um comportamento de escopo muito mais prático. As funções de seta herdam thisdo escopo em que foram definidas.
Em alguns casos, as funções das setas podem ser ainda mais concisas:
const added = [0, 1, 2, 3, 4].map((item) = item + 1);
console.log(added) // prints "[1, 2, 3, 4, 5]"
As funções de seta que residem em uma única linha incluem uma returninstrução implícita . Não há necessidade de colchetes ou ponto e vírgula com funções de seta de linha única.
Eu quero deixar isso claro. Esta não é uma varsituação; ainda existem casos de uso válidos para funções anônimas básicas (especificamente métodos de classe). Dito isso, descobri que se você sempre usar como padrão uma função de seta, acabará fazendo muito menos depuração em vez de usar funções anônimas básicas como padrão.
Como de costume, os documentos do Mozilla são o melhor recurso
Operador Spread ...
Extrair pares de chave / valor de um objeto e adicioná-los como filhos de outro objeto é um cenário muito comum. Historicamente, existem algumas maneiras de fazer isso, mas todos esses métodos são muito desajeitados:
const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
const merged = Object.assign({}, obj1, obj2);
console.log(merged) // prints { dog: 'woof', cat: 'meow' }
Esse padrão é incrivelmente comum, então a abordagem acima rapidamente se torna tediosa. Graças ao operador de propagação, nunca há necessidade de usá-lo novamente:
const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
console.log({ ...obj1, ...obj2 }); // prints { dog: 'woof', cat: 'meow' }
A grande parte é que isso também funciona perfeitamente com arrays:
const arr1 = [1, 2];
const arr2 = [3, 4];
console.log([ ...arr1, ...arr2 ]); // prints [1, 2, 3, 4]
Provavelmente não é o recurso JS recente mais importante, mas é um dos meus favoritos.
Literais de modelo (strings de modelo)
Strings são uma das construções de programação mais comuns. É por isso que é tão embaraçoso que a declaração nativa de strings ainda seja pouco suportada em muitos idiomas. Por muito tempo, JS esteve na família dos “crappy string”. Mas a adição de literais de modelo colocou JS em uma categoria própria. Literais de modelo resolvem nativa e convenientemente os dois maiores problemas com a escrita de strings: adicionar conteúdo dinâmico e escrever strings que ligam várias linhas:
const name = 'Ryland';
const helloString =
`Hello
${name}`;
Acho que o código fala por si. Que implementação incrível.
Destruturação de Objetos
A desestruturação de objetos é uma forma de extrair valores de uma coleção de dados (objeto, array, etc.), sem ter que iterar sobre os dados ou acessar suas chaves explicitamente:
à moda antiga
function animalParty(dogSound, catSound) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
animalParty(myDict.dog, myDict.cat);
desestruturação
function animalParty(dogSound, catSound) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
const { dog, cat } = myDict;
animalParty(dog, cat);
Mas espere, tem mais. Você também pode definir a desestruturação na assinatura de uma função:
desestruturação 2
function animalParty({ dog, cat }) {}
const myDict = {
dog: 'woof',
cat: 'meow',
};
animalParty(myDict);
Também funciona com matrizes:
desestruturação 3
[a, b] = [10, 20];
console.log(a); // prints 10
Existem muitos outros recursos modernos que você deve utilizar. Aqui estão alguns outros que se destacam para mim:
Sempre presuma que seu sistema está distribuído
Ao escrever aplicativos paralelizados, seu objetivo é otimizar a quantidade de trabalho que você está fazendo ao mesmo tempo. Se você tem quatro núcleos disponíveis e seu código só pode utilizar um único núcleo, 75% do seu potencial está sendo desperdiçado. Isso significa que as operações de bloqueio síncronas são o maior inimigo da computação paralela. Mas, considerando que JS é uma linguagem de encadeamento único, as coisas não funcionam em vários núcleos. Então, qual é o ponto?
JS é de thread único, mas não de arquivo único (como em linhas na escola). Mesmo que não seja paralelo, ainda é simultâneo. O envio de uma solicitação HTTP pode levar segundos ou até minutos, portanto, se JS parasse de executar o código até que uma resposta da solicitação retornasse, a linguagem ficaria inutilizável.
JavaScript resolve isso com um loop de eventos. O loop de eventos percorre eventos registrados e os executa com base na lógica interna de agendamento / priorização. Isso é o que permite enviar milhares de solicitações HTTP simultâneas ou ler vários arquivos do disco ao mesmo tempo. Aqui está o truque: o JavaScript só pode utilizar esse recurso se você utilizar os recursos corretos. O exemplo mais simples é o loop for:
let sum = 0;
const myArray = [1, 2, 3, 4, 5, ... 99, 100];
for (let i = 0; i myArray.length; i += 1) {
sum += myArray[i];
}
Um loop for vanilla é uma das construções menos paralelas que existem na programação. Em meu último trabalho, liderei uma equipe que passou meses tentando converter Rlang for-loops tradicionais em código automagicamente paralelo. É basicamente um problema impossível, que só pode ser resolvido esperando que o aprendizado profundo melhore. A dificuldade de paralelizar um loop for decorre de alguns padrões problemáticos. Os for-loops sequenciais são muito raros, mas por si só tornam impossível garantir a decomposição dos for-loops:
let runningTotal = 0;
for (let i = 0; i myArray.length; i += 1) {
if (i === 50 runningTotal 50) {
runningTotal = 0;
}
runningTotal += Math.random() + runningTotal;
}
Este código só produz o resultado pretendido se for executado em ordem, iteração por iteração. Se você tentou executar várias iterações de uma vez, o processador pode ramificar incorretamente com base em valores imprecisos, o que invalida o resultado. Estaríamos tendo uma conversa diferente se isso fosse código C, pois o uso é diferente e existem alguns truques que o compilador pode fazer com loops. Em JavaScript, os loops for tradicionais só devem ser usados se for absolutamente necessário. Caso contrário, utilize as seguintes construções:
mapa
// in decreasing relevancy :0
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
const resultingPromises = urls.map((url) = makHttpRequest(url));
const results = await Promise.all(resultingPromises);
mapa com índice
// in decreasing relevancy :0
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
const resultingPromises = urls.map((url, index) = makHttpRequest(url, index));
const results = await Promise.all(resultingPromises);
para cada
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
// note this is non blocking
urls.forEach(async (url) = {
try {
await makHttpRequest(url);
} catch (err) {
console.log(`${err} bad practice`);
}
});
Explicarei por que eles são uma melhoria em relação aos loops for tradicionais. Em vez de executar cada iteração em ordem (sequencialmente), construções como mappegar todos os elementos e enviá-los como eventos individuais para a função de mapa definida pelo usuário. Na maioria das vezes, as iterações individuais não têm nenhuma conexão ou dependência inerente umas com as outras, permitindo que sejam executadas simultaneamente. Isso não quer dizer que você não possa realizar a mesma coisa com for-loops. Na verdade, seria algo assim:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function testCall() {
// do async stuff here
}
for (let i = 0; i 10; i += 1) {
testCall();
}
Como você pode ver, o loop for não me impede de fazer isso da maneira certa, mas com certeza também não torna as coisas mais fáceis. Compare com a versão do mapa:
items.map(async (item) = {
// do async stuff here
});
Como você pode ver, ele mapsimplesmente funciona. A vantagem do mapa fica ainda mais clara se você deseja bloquear até que todas as operações assíncronas individuais sejam concluídas. Com o código for-loop, você mesmo precisaria gerenciar um array. Aqui está a mapversão:
const items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const allResults = await Promise.all(items.map(async (item) = {
// do async stuff here
}));
é realmente tão fácil
Existem muitos casos em que um loop for teria o mesmo desempenho (ou talvez mais) em comparação com um mapou forEach. Eu ainda diria que perder alguns ciclos agora vale a vantagem de usar uma API bem definida. Dessa forma, quaisquer melhorias futuras para a implementação de padrões de acesso a dados beneficiarão seu código. O loop for é muito genérico para ter otimizações significativas para o mesmo padrão.
Existem outras opções assíncronas válidas fora de mape forEach, como for-await-of.
Limpe seu código e aplique um estilo
Código sem um estilo consistente (aparência e comportamento) é incrivelmente difícil de ler e entender. Portanto, um aspecto crítico de escrever código de ponta em qualquer linguagem é ter um estilo consistente e sensato. Devido à amplitude do ecossistema JS, há MUITAS opções para linters e especificações de estilo. O que não posso deixar de enfatizar é que é muito mais importante que você esteja usando um linter e impondo um estilo (qualquer um deles) do que qual linter / estilo você escolhe especificamente. No final do dia, ninguém vai escrever código exatamente como eu faria, então otimizar para isso é uma meta irreal.
Eu vejo muitas pessoas perguntarem se deveriam usar eslint ou mais bonito. Para mim, eles têm finalidades muito diferentes e, portanto, devem ser usados em conjunto. Eslint é um linter tradicional na maior parte do tempo. Ele vai identificar problemas com seu código que têm menos a ver com estilo e mais a ver com correção. Por exemplo, eu uso eslint com regras AirBNB . Com essa configuração, o código a seguir forçaria o linter a falhar:
var fooVar = 3; // airbnb rules forebid "var"
Deve ser bastante óbvio como o eslint agrega valor ao seu ciclo de desenvolvimento. Em essência, garante que você siga as regras sobre o que é ou não uma boa prática. Devido a isso, os linters são intrinsecamente opinativos. Como acontece com todas as opiniões, aceite-o com cautela. O linter pode estar errado.
Mais bonito é um formatador de código. Ele está menos preocupado com a correção e muito mais preocupado com a uniformidade e consistência. Prettier não vai reclamar do uso var, mas irá alinhar automaticamente todos os colchetes em seu código. Em meu processo de desenvolvimento pessoal, sempre corro mais bonito como a última etapa antes de enviar o código para o Git. Em muitos casos, faz sentido que o Prettier seja executado automaticamente em cada confirmação de um repo. Isso garante que todo o código que entra no controle de origem tenha estilo e estrutura consistentes.
Teste seu código
Escrever testes é um método indireto, mas incrivelmente eficaz, de melhorar o código JS que você escreve. Recomendo que você se familiarize com uma ampla gama de ferramentas de teste. Suas necessidades de teste irão variar e não há uma ferramenta única que possa lidar com tudo. Existem toneladas de ferramentas de teste bem estabelecidas no ecossistema JS, portanto, a escolha de ferramentas depende principalmente do gosto pessoal. Como sempre, pense por si mesmo.
Driver de teste - Ava
Os drivers de teste são simplesmente estruturas que fornecem estrutura e utilidades em um nível muito alto. Eles são freqüentemente usados em conjunto com outras ferramentas de teste específicas, que variam de acordo com suas necessidades de teste.
Ava é o equilíbrio certo entre expressividade e concisão. A arquitetura paralela e isolada de Ava é a fonte da maior parte do meu amor. Os testes mais rápidos economizam tempo dos desenvolvedores e dinheiro das empresas. Ava possui uma tonelada de recursos interessantes, como asserções integradas, enquanto consegue ser mínimo.
Alternativas: Jest, Mocha, Jasmine
Spies and Stubs - Sinon
Os espiões nos fornecem análises de funções, como quantas vezes uma função foi chamada, por que foi chamada e outros dados perspicazes.
Sinon é uma biblioteca que faz muitas coisas, mas apenas algumas muito bem. Especificamente, o sinon se destaca quando se trata de espiões e stubs. O conjunto de recursos é rico, mas a sintaxe é concisa. Isso é especialmente importante para stubs, considerando que eles existem parcialmente para economizar espaço.
Alternativas: testdouble
Mocks - Nock
HTTP Nock on Github é o processo de falsificar alguma parte do processo de solicitação de http para que o testador possa injetar lógica customizada para simular o comportamento do servidor.
Zombar de Http pode ser uma verdadeira dor, mas nock torna menos doloroso. Nock substitui diretamente o requestbuilt-in de nodejs e intercepta solicitações de saída de http. Isso, por sua vez, dá a você controle total da resposta.
Alternativas: Eu realmente não sei de nenhum?
Automação da Web - Selenium
Tenho emoções confusas quanto à recomendação do Selenium. Como é a opção mais popular para automação da web, tem uma grande comunidade e um conjunto de recursos online. Infelizmente, a curva de aprendizado é bastante íngreme e depende de muitas bibliotecas externas para uso real. Dito isso, é a única opção realmente gratuita, então, a menos que você esteja fazendo alguma automação da web de nível empresarial, o Selenium fará o trabalho.
Alternativas: Cypress, PhantomJS
A jornada sem fim
Como acontece com a maioria das coisas, escrever melhor em JavaScript é um processo contínuo. O código sempre pode ser mais limpo, novos recursos são adicionados o tempo todo e nunca há testes suficientes. Pode parecer opressor, mas como há tantos aspectos potenciais para melhorar, você pode realmente progredir em seu próprio ritmo. Dê um passo de cada vez e, antes que perceba, será um ás do JavaScript.
Se você gostaria de contribuir com artigos para a comunidade Avance Network, entre em contato em nosso página.
O Avance Network é uma comunidade fácil de usar que fornece segurança de primeira e não requer muito conhecimento técnico. Com uma conta, você pode proteger sua comunicação e seus dispositivos. O Avance Network não mantém registros de seus dados; portanto, você pode ter certeza de que tudo o que sai do seu dispositivo chega ao outro lado sem inspeção.