JavaScript é uma das linguagens de programação mais populares do mundo. Com sua ampla utilização em diversas aplicações, é importante garantir que o código seja bem escrito, legível e fácil de manter. Escrever código limpo em JavaScript pode parecer um desafio, mas é uma prática essencial para melhorar a qualidade do seu trabalho e a eficiência da equipe de desenvolvimento. Neste artigo, exploraremos as melhores práticas para escrever código limpo em JavaScript.
🚨DICA: Crie um projeto de programação web com HTML5, CSS3 e JavaScript do zero em 3 aulas: MiniCurso Gratuito! 🚀
Leia o livro: Código limpo: Habilidades práticas do Agile Software
Edição Português
1. Atribua nomes a variáveis e funções.
Os nomes são importantes e as variáveis e funções com nomes descritivos podem ajudar os leitores a entender seu código sem entrar em detalhes.
// Ruim 😕 const CorAlvo = "roxo"; const fnd = true; const lista = ["azul", "verde", "amarelo"]; const obj = { cor: "azul", hex: "#800080" }; if (validarSalvar(obj)) { bancoDeDados.salvar(obj); } if (existe) { // ... } // Melhor 😀 const corAlvo = "roxo"; /* notação camelCase para definir variáveis, funções e métodos em JavaScript */ const corEncontrada = false; const cores = ["azul", "verde", "amarelo"]; const cor = { nome: "azul", hex: "#800080" }; const ehCorValida = validarCor(cor.hex); if (ehCorValida) { bancoDeDados.salvar(cor); } if (existeCor) { // ... } // ...
Evite gírias, abreviações e falhas de comunicação.
// Ruim 😕 user.goAway(); // gíria save(n); // abreviação comum allBooks = books.filter(book => {...}); // falsa representação // Melhor 😀 user.remover(); save(user); livrosFiltrados = books.filter(livro => {...}); /* Alternativamente, um nome diferente com base nos critérios de filtragem */
Não inclua informações redundantes no nome. Só ocupa mais espaço e faz mais barulho.
// Ruim 😕 const colorWithHexCode = Color("red", "#ff0000"); const book = { bookTitle: "Misery", bookId: 23423, bookAuthor: "Stephen King", bookISBN: "978-0-670-81364-3", }; // Melhor 😀 const cor = Color("red", "#ff0000"); /* É simples inferir que essa cor tem um nome e um código hexadeciamal, mesmo sem conhecer a descrição da classe. */ const livro = { titulo: "Misery", id: 23423, autor: "Stephen King", isbn: "978-0-670-81364-3", }; /* Utilizar nomes mais simples e claros para os atributos do objeto, sem repetições desnecessárias de palavras, torna o código mais legível e fácil de entender. */
Devemos nomear coisas relacionadas ao domínio da solução. Qualquer coisa que um desenvolvedor entenda, como um banco de dados, um MessageBus ou algo que descreva o problema que estamos tentando resolver.
Leia também: As 5 melhores placas de vídeo em 2023
2. Funções e métodos devem ser concisos em todos os aspectos
Chamar ou interagir com funções deve ser simples e compreensível. O número, a ordem e o comprimento dos corpos funcionais são importantes! Mantenha o número de parâmetros pequeno. Uma função preferencialmente tem dois ou menos parâmetros.
// Ruim 😕 const getEmployeeRecord = (id, sector, startDate, endDate) => { // ... }; // Melhor 😀 // Opção 1: const getEmployeeRecord = ({ id, sector, startDate, endDate }) => { // passando como um objeto // ... }; // Opção 2: /* Podemos enviar um número infinito de parâmetros para nossa função usando o parâmetro rest (...), que tem a mesma sintaxe do operador spread. */ function getEmployeeRecord(...args) { [id, sector, startDate, endDate] = args; // args é um array console.log(sector); // logs 'IT' } // Caso de uso: // Opção 1: const registro = { id: 1213, setor: "TI", dataInicio: "01-01-2020", dataFim: "10-01-2021", }; getEmployeeRecord(registro); // Opção 2: getEmployeeRecord(1213, "TI", "01-01-2020", "10-01-2021"); /* Utilizar parâmetros com nomes descritivos e coesos torna o código mais legível e fácil de entender. Além disso, a segunda opção exemplifica o uso do parâmetro rest, que pode ser útil em casos em que o número de parâmetros pode variar. */
Em vez de condições, use parâmetros padrão.
// Ruim 😕 const logError = (err) => { const error = err || "Algo deu errado"; console.log(error); }; // Melhor 😀 const logError = (err = "Algo deu errado") => console.log(err); // Caso de uso logError(); // logs 'Algo deu errado' const mensagemTeste = "usuário não encontrado"; logError(mensagemTeste); // logs "usuário não encontrado" /* Utilizar valores padrão para parâmetros de função pode simplificar o código e torná-lo mais legível. Na primeira opção, é necessário fazer a verificação do valor passado para o parâmetro err antes de utilizá-lo, o que pode ser um pouco confuso. Já na segunda opção, o valor padrão é definido diretamente na assinatura da função, o que torna o código mais simples e fácil de entender. */
Níveis de abstração importam
Uma função só deve executar um trabalho que seja um nível menos abstrato que seu nome. Ajuda a reduzir o tamanho e a complexidade das funções, o que simplifica o teste e a depuração.
Abstração de alto nível: operações em que pedimos para fazer algo sem nos importar como é feito, tornando-as mais fáceis de entender e usar. Por exemplo, “isValidPassword(senha)”.
Abstração de baixo nível: detalhar as operações para definir os detalhes de implementação de como algo funciona. Por exemplo, “password. length > 8” é usado diretamente.
O objetivo é introduzir operações de baixo nível onde a interpretação é adicionada ao nome da função.
// Ruim 😕 /* Este método deveria armazenar um livro, mas o nome não acrescenta nenhum significado para a validação de baixo nível que segue. */ function saveBook({isbn, title}){ if(isbn.length != 13 || title.trim() === ''){ console.log("Não foi possível adicionar o livro, entrada inválida"); } else { const book = new Book(book); book.save(); } } // Melhor 😀 function isValidBook(title,isbn){ return isbn.length === 13 && title.trim() !== ''; } function validateBook({title,isbn}){ if(!isValidBook(isbn,title)){ throw new Error('Não foi possível adicionar o livro, entrada inválida'); } } function saveBook(book){ validateBook(book); const book = new Book(book); book.save(); }
Fique SECO (não se repita)
// Ruim 😕 async function getPostsForUser(userId) { const userResponse = await fetch(/api/user/${userId}, { token: token, }); const user = userResponse.json(); const postResponse = await fetch(/api/posts/${user.authorId}); return postResponse.json(); } async function getCommentsForUser(userId) { const userResponse = await fetch(/api/users/${userId}, { token: token, }); const user = userResponse.json(); const commentResponse = await fetch(/api/comments/${user.commentId}); return commentResponse.json(); } // Melhor 😀 async function getUser(userId) { const response = await fetch(/api/v2/user/${userId}, { token: token, }); return response.json(); } async function getPostsForUser(userId) { const user = await getUser(userId); const response = await fetch(/api/posts/${user.authorId}); return response.json(); } async function getCommentsForUser(userId) { const user = await getUser(userId); const response = await fetch(/api/comments/${user.commentId}); return response.json(); }
Alguns conselhos de divisão de código: Não divida seu código se: Encontrar novas funções leva mais tempo do que ler funções extraídas. Não foi possível encontrar um nome adequado para a função extraída.
3. Estrutura do código – a aparência é importante
Substituir condições aninhadas por cláusulas de guarda. Uma cláusula de guarda é uma verificação que causa uma saída imediata de uma função, uma instrução de retorno ou uma exceção.
// Ruim 😕 function getTicketPrices() { let prices; if (isTravelByAir) { prices = airFares(); } else { if (isTravelByRail) { prices = railFares(); } else { if (isTravelByShip) { prices = shipFares(); } else { prices = carFares(); } } } return prices; } // Melhor 😀 function getTicketPrices() { if (isTravelByAir) return airFares(); if (isTravelByRail) return railFares(); if (isTravelByShip) return shipFares(); return carFares(); }
Limpe as instruções switch/if com o Maps
Se você tiver quaisquer instruções switch/if futuras que devam crescer, considere convertê-las em pares chave/valor.
// Ruim 😕 const getServerUrl = (env) => { switch (env) { case "prod": return "prod.myweb.com"; case "test": return "test.myweb.com"; case "staging": return "staging.myweb.com"; default: return "https://localhost:3000"; } }; // Melhor 😀 const serverUrls = { prod: "prod.myweb.com", test: "test.myweb.com", staging: "staging.myweb.com", }; const getServerUrl = (env) => serverUrls[env] || "https://localhost:3000";
4. Use os recursos do ES6
Destruição de objetos
const employee = { name: "Jack", age: 25, department: { team: "alpha", role: "Desenvolvedor", }, }; // ES5 😄 const name = employee.name; const age = employee.age; const role = employee.department.role; const team = employee.department.team; let manager = employee.department.manager; if (!manager) { // undefined manager = "haley"; //setando o valor padrão; } console.log(name, age, team, role, manager); // logs: Jack 25 alpha Desenvolvedor haley // ES6 😊 const { name, age, department: { team, role, manager = 'haley' }} = employee; console.log(name, age, team, role, manager); // logs: Jack 25 alpha Desenvolvedor haley
Modelo Literais
// ES5 😄 const email = "[email protected]"; const error = "usuário com o email" + " " + email + "não existe"; // ES6 😊 const email = "[email protected]"; const error = usuário com o email ${email} não existe;
Variedade
//Antes if (mode === "crédito" || mode === "débito" || mode === "dinheiro") return algo; // Depois if (["crédito", "débito", "dinheiro"].includes(payment)) return algo; //Antes let flores = ["🌹", "🌻", "🍁"]; let rosa = flores[0]; let girassol = flores[1]; let folhaDeMaple = flores[2]; //Depois let [rosa, girassol, folhaDeMaple] = flores;
5. Comentários e Formatação
A formatação do código melhora a legibilidade e transmite significado. Existem dois formatos:
Formatação vertical: incluindo espaçamento entre linhas e agrupamento de código.
Duas diretrizes gerais: conceitos relacionados devem ser mantidos juntos e conceitos diferentes devem ser mantidos separados.
// Ruim 😕 const user = {id: 23231242,agentId: "cwwesf4f"}; const today = Date.now(); const bookSpaAndNotifyUser = async (spaInfo) => { if (!findUser(user.id)) { showErrorMessage('Cant find the user'); } try { const confirmationId = await processSpaBooking(user, tripInfo); sendSpaConfirmationEmail(user, confirmationId); } catch (error) { showErrorMessage(Failed to book a spa ${error}); } } const caclulateTotalPrice = () => { // ... } const showErrorMessage = (error = 'something went wrong') => { throw new Error(error); } const processSpaBooking = (user, tripInfo) => { // ... findStaff(); calculateTotalPrice(); } const sendSpaConfirmationEmail = (user, confirmationId) => { // ... } const findStaff = () => { // ... } const caclulateTotalPrice = () => { // ... } const sendSpaConfirmationEmail = (user, confirmationId) => { // ... } // Melhorado 😀 const user = { id: 23231242, agentId: "cwwesf4f" }; const today = Date.now(); const bookSpaAndNotifyUser = async (spaInfo) => { if (!findUser(user.id)) { showErrorMessage("Cant find the user"); } try { const confirmationId = await processSpaBooking(user, tripInfo); sendSpaConfirmationEmail(user, confirmationId); } catch (error) { showErrorMessage(Failed to book a spa ${error}); } }; const showErrorMessage = (error = "something went wrong") => { throw new Error(error); }; const processSpaBooking = (user, tripInfo) => { // ... findStaff(); calculateTotalPrice(); }; const findStaff = () => { // ... }; const calculateTotalPrice = () => { // ... }; const sendSpaConfirmationEmail = (user, confirmationId) => { // ... };
Layout horizontal: incluindo recuo, espaçamento de código, largura de código
// Ruim 😕 const printUserData = ()=>{if(users.length > 0){for(let user of users){console.log(user)}}}; async function getconfigFile() { try { // o nome da variável é muito longo, e a linha está sendo truncada const lastYearConfigFileFromFolder = await fs.readFile('/Users/admin/folder/year/2021/config.txt', { encoding: 'utf8' }); } catch (err) { console.log(err); } } // Melhor 😀 const printUserData = () => { if (users.length > 0) { for (let user of users) { console.log(user); } } }; async function getconfigFile() { try { const arquivoDeConfiguracao = await fs.readFile( "/admin/pasta/ano/2021/config.txt", { encoding: "utf8" } ); } catch (err) { console.log(err); } }
É recomendado evitar ao máximo fazer comentários desnecessários no código. O código deve ser autoexplicativo, exceto em situações onde sejam necessários comentários para informações legais ou explicações que não possam ser substituídas por nomes adequados.
Por exemplo, no código abaixo, é desnecessário o comentário explicando que a lista é de frutas e a obtenção do comprimento do array de frutas:
// Frutas const ruins = ['🍇', '🍌', '🍉']; // esta é a lista de frutas const basketSize = fruit.length // obtendo o comprimento do array de frutas.
Já no exemplo abaixo, é válido utilizar um comentário para informar que o endereço só funciona em ambiente de preparação:
const serverUrl = '[email protected]'; // só funciona no ambiente de preparação.
Conclusão
A maioria dessas ideias pode ser estendida e aplicada a várias linguagens de programação. Adotar essas estratégias exige esforço, especialmente com bases de código maiores, mas garantirá que seu código seja limpo, escalável e mantenha a qualidade do código em sua equipe.
Portanto, a escrita de código limpo em JavaScript é uma prática essencial para garantir a qualidade do seu trabalho e da equipe de desenvolvimento. Então, com a adoção de boas práticas, como a utilização de nomes descritivos de variáveis, funções bem definidas e documentação clara, você pode garantir que seu código seja fácil de entender, manter e compartilhar. Dessa forma, ao seguir essas práticas, você pode melhorar a eficiência do processo de desenvolvimento, minimizar erros e garantir a satisfação do usuário final. Lembre-se de que a escrita de código limpo é um processo contínuo e deve ser aplicada em todas as etapas do seu trabalho. Com esforço e dedicação, você pode se tornar um programador de JavaScript mais eficiente e confiável.