Clean Code com JavaScript
Aprenda como deixar seu código limpo e legível
Quem nunca criou um código que não conseguia entender depois de um tempo? Ou que não conseguia entender o código de outra pessoa? Pois é. Isso é muito comum. Já tive essa experiência várias vezes e tive que gastar muito tempo em manutenção de código de vários projetos. O grande problema é que se você, a empresa ou a gerência não se preocupam com a qualidade e inovação de código, isso pode desmotivar os demais desenvolvedores e causar um grande impacto no negócio, gerando um débito técnico. E como se não bastasse isso, o código ruim pode causar problemas de segurança, bugs e até mesmo a falência de uma empresa. Até que isso fique nítido, pode ser tarde demais.
📊 Segundo um estudo feita pela Stepsize, 51% dos engenheiros deixaram ou consideraram deixar o emprego por causa de dívidas técnicas. Isso explica por que a remuneração e as oportunidades de crescimento podem não ser suficientes para manter os engenheiros satisfeitos; A dívida técnica causa frustração e dificulta a inovação.
E é por isso que existem os princípios de Clean Code para nos ajudar a manter o nosso código legível e de fácil manutenção. Vamos ver alguns desses princípios, como aplicá-los em JavaScript e evitar que nosso código se torne um verdadeiro pesadelo.
⚠️ Os códigos que vamos ver é apenas para exemplificar os princípios de Clean Code. Não são códigos que você deve usar em produção.
#Use nomes significativos
Todo desenvolvedor tem ou teve dificuldade para nomear suas variáveis e funções. Mas é importante que os nomes sejam significativos, e que digam o que elas são ou o que elas fazem.
⛔ Não abrevie, não use gírias e não use nomes genéricos.
// ruim 👎
const u = new User();
u.name = 'John';
u.age = 30;
const pwd = hash('123456');
const hasPermission = (user) => user.role === 'admin';
const doSomething = (user) => db.save(user);
u.addresses.forEach(a => {
// ...
});
// bom 👍
const user = new User();
user.name = 'John';
user.age = 30;
const password = hash('123456');
const isAdmin = (user) => user.role === 'admin';
const saveUser = (user) => db.save(user);
user.addresses.forEach(address => {
// ...
});
#Use nomes pronunciáveis
O nome da variável ou função deve ser pronunciável. Isso facilita a comunicação entre os membros da equipe e legibilidade do código.
// ruim 👎
const genymdhms = moment().format('YYYYMMDDHHmmss');
// bom 👍
const currentTimestamp = moment().format('YYYYMMDDHHmmss');
#Use nomes pesquisáveis
Os nomes devem ser pesquisáveis. Isso significa que o nome deve ser único e que você consiga encontrá-lo facilmente no seu código. Já pensou em fazer uma busca no seu código onde precisa ajustar uma configuração mas não lembra onde? Isso te custará um tempo.
// ruim 👎
setTimeout(() => {
// ...
}, 1000);
// bom 👍
const ONE_SECOND = 1000;
setTimeout(() => {
// ...
}, ONE_SECOND);
#Evite comentários óbvios
É importante que o código seja autoexplicativo. Comentários são úteis, mas devem ser usados apenas quando necessário. Comentários óbvios são desnecessários.
// ruim 👎
const sum = (a, b) => a + b; // Soma dois números
// bom 👍
const sum = (a, b) => a + b;
ℹ️ Particularmente, somente uso comentários quando preciso informar os outros membros de equipe sobre uma solução bem específica.
#Evite muitas condições
Muitas condições podem deixar o código confuso e difícil de entender. Principalmente quando temos muitas condições aninhadas. Tente manter o número de condições o mais baixo possível.
// ruim 👎
const getDiscount = (user) => {
if (user.age >= 18) {
if (user.isStudent) {
return 0.5;
}
return 0.2;
}
return 0;
};
// bom 👍
const MIN_AGE = 18;
const getDiscount = (user) => {
if (user.age < MIN_AGE) return 0;
if (user.isStudent) return 0.5;
return 0.2;
};
#Evite efeitos colaterais
Uma função deve fazer apenas uma coisa. Se ela faz mais do que isso, ela pode ter efeitos colaterais. Efeitos colaterais são ações que a função faz além daquilo que ela deveria fazer.
// ruim 👎
const add = (a, b) => {
console.log(a + b);
return a + b;
};
// bom 👍
const print = (value) => console.log(value);
const add = (a, b) => a + b;
print(add(1, 2));
#Evite repetição
Repetição é um dos maiores problemas que encontramos no código. Caso aconteça, é um sinal de que você deve refatorar o seu código e criar pequenos blocos de código que podem ser reutilizados.
// ruim 👎
async function getPosts(userId) {
const userResponse = await fetch(`/api/user/${userId}`, { token });
const user = userResponse.json();
const postResponse = await fetch(`/api/posts/${user.authorId}`);
return postResponse.json();
}
async function getComments(userId) {
const userResponse = await fetch(`/api/user/${userId}`, { token });
const user = userResponse.json();
const commentResponse = await fetch(`/api/comments/${user.commentId}`);
return commentResponse.json();
}
// bom 👍
async function getUser(userId) {
const response = await fetch(`/api/user/${userId}`, { token });
return response.json();
}
async function getPosts(userId) {
const user = await getUser(userId);
const response = await fetch(`/api/posts/${user.authorId}`);
return response.json();
}
async function getComments(userId) {
const user = await getUser(userId);
const response = await fetch(`/api/comments/${user.commentId}`);
return response.json();
}
#Mantenha o número de parâmetros baixo
O ideal para as funções é que elas tenham no máximo 3 parâmetros. Mais do que isso, pode ser um sinal de que a função está fazendo mais do que deveria. Caso precise de mais parâmetros, é melhor usar um objeto.
// ruim 👎
const dateFormatter = (date, format, timezone, locale) => {
// ...
};
// bom 👍
const dateFormatter = (date, format, options) => {
if (options.timezone) {
// ...
}
if (options.locale) {
// ...
}
};
#Não use flags como parâmetros
Flags são parâmetros que indicam se uma ação deve ou não ser executada. Isso pode ser um sinal de que a função está fazendo mais do que deveria.
// ruim 👎
const createFile = (name, temp) => {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
};
// bom 👍
const createFile = (name) => fs.create(name);
const createTempFile = (name) => fs.create(`./temp/${name}`);
#Use valores padrão ao invés de condicionais
Caso uma função precise de um valor padrão, é melhor usar um valor padrão ao invés de uma condicional. Isso conseguimos fazer direto na assinatura da função.
// ruim 👎
const logError = (err) => {
const error = err || "Something went wrong";
console.error(error);
};
// bom 👍
const logError = (err = "Something went wrong") => console.error(err);
#Abstraia funções condicionais
Caso uma função tenha uma condicional, é melhor abstrair essa condicional em uma função.
// ruim 👎
const getDiscount = (price, discount) => {
if (discount) return price - price * discount;
return price;
};
// bom 👍
const getDiscount = (price, discount) => price - price * discount;
const applyDiscount = (price, discount) => {
if (discount) return getDiscount(price, discount);
return price;
};
#Mapeie ao invés de condicionais
Se o seu código possui muitas condições ou um switch case muito grande, é melhor mapear essas condições em um objeto.
// ruim 👎
const getDiscount = (user) => {
switch (user.type) {
case "student":
return 0.5;
case "teacher":
return 0.2;
default:
return 0;
}
};
// bom 👍
const DISCOUNTS = {
student: 0.5,
teacher: 0.2,
};
const getDiscount = (user) => DISCOUNTS[user.type] || 0;
#Use desestruturação
A desestruturação é uma forma de extrair valores específicos de um objeto ou array.
Objeto
// ruim 👎
const getFullName = (user) => `${user.firstName} ${user.lastName}`;
// bom 👍
const getFullName = ({ firstName, lastName }) => `${firstName} ${lastName}`;
Array
// ruim 👎
const getFirstAndLast = (arr) => [arr[0], arr[arr.length - 1]];
// bom 👍
const getFirstAndLast = ([first, ...rest]) => [first, rest.pop()];
#Use o operador spread
O operador spread é uma forma de espalhar os valores de um objeto ou array. É uma boa pedida para criar novos objetos e arrays sem modificar os originais.
Objeto
// ruim 👎
const user = {
firstName: "John",
lastName: "Doe",
};
const newUser = Object.assign({}, user, { age: 30 });
// bom 👍
const user = {
firstName: "John",
lastName: "Doe",
};
const newUser = { ...user, age: 30 };
Array
// ruim 👎
const array = [1, 2, 3];
const newArray = array.concat(4);
// bom 👍
const array = [1, 2, 3];
const newArray = [...array, 4];
#Use template string
Template string é uma forma de concatenar strings de forma mais legível.
// ruim 👎
const getFullName = (firstName, lastName) => firstName + " " + lastName;
// bom 👍
const getFullName = (firstName, lastName) => `${firstName} ${lastName}`;
#Use operadores ternários
Operadores ternários são uma forma de escrever condicionais de forma rápida. Recomendo para retornos simples.
// ruim 👎
const getDiscount = (user) => {
if (user.type === "student") {
return 0.5;
} else {
return 0;
}
};
// bom 👍
const getDiscount = (user) => (user.type === "student" ? 0.5 : 0);
#Use o método includes
O método includes é uma forma de verificar se um array, uma string ou até mesmo substituir múltiplas condições que contém um determinado valor.
Array
// ruim 👎
const payments = ["credit", "debit", "cash"];
const mode = "credit";
const hasCredit = payments.indexOf(mode) !== -1;
// bom 👍
const payments = ["credit", "debit", "cash"];
const mode = "credit";
const hasCredit = payments.includes(mode);
Múltiplas condições
// ruim 👎
const mode = "credit";
const hasCredit = mode === "credit" || mode === "debit" || mode === "cash";
// bom 👍
const mode = "credit";
const hasCredit = ["credit", "debit", "cash"].includes(mode);
ℹ️ Existem outros métodos que podem substituir condicionais, como o
find
,some
,every
,filter
,map
,reduce
, etc. Recomendo que você estude cada um deles.
#Use um formatador e um linter de código
Cada desenvolvedor tem seu estilo de código. Porém, para que o projeto seja padronizado e consistente, é importante que todos os colaboradores sigam as mesmas regras.
Um formatador de código é uma ferramenta que formata o código de forma automática. Um linter é uma ferramenta que verifica se o código está seguindo as regras definidas. Ambos são essenciais para manter o código limpo e legível. Recomendo o Prettier e o ESLint para projetos JavaScript e TypeScript.
#Conclusão
Ninguém merece ter que lidar com um código mal escrito, não é mesmo? Manter o código limpo e legível é essencial para que o projeto seja escalável e de fácil manutenção. Portanto, é importante que você e sua equipe sigam as boas práticas de programação e documentem o código assim que possível, afim de evitar vários problemas e falta de comunicação no futuro.
Caso utilize algum framework ou biblioteca, recomendo que você leia a documentação oficial para entender as boas práticas de cada um.