Voltar

Clean Code com JavaScript

Aprenda como deixar seu código limpo e legível

5 min de leitura143 visualizações

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.

Gostou do conteúdo?

Inscreva-se para receber notificações por e-mail quando tiver conteúdo novo. Sem compromisso, cancele a qualquer momento.