Executando verificação de segurança...
2

3 regras simples para ter um código mais limpo e profissional

Sabemos que existem dezenas de design patterns por aí cada um com bonus/onus. Chegar nesse nível, de usar design patterns com maestria, é algo que se aprende somente com o tempo!

Mas, mesmo para quem está começando, é possível aplicar técnicas simples no seu código de forma a deixá-lo mais profissional!! Dentre estas regras, eu deixo aqui 3 que considero importantes. Se houver mais, deixe nos comentários.

1ª regra

Não use ifs encadeados, muito menos else

Exemplo ruim:

function verificarIdade(idade) {
  if (idade < 0) {
    return "Idade inválida";
  } else {
    if (idade >= 0 && idade < 18) {
      return "Menor de idade";
    } else {
      if (idade >= 18 && idade < 60) {
        return "Adulto";
      } else {
        if (idade >= 60) {
          return "Idoso";
        }
      }
    }
  }
}

A solução para esse monte de if encadeado é separar cada um deles, da seguinte forma:

function verificarIdade(idade) {
  if (idade < 0) {
    return "Idade inválida";
  }
  if (idade < 18) {
    return "Menor de idade";
  }
  if (idade < 60) {
    return "Adulto";
  }
  return "Idoso";
}

Lembre-se que é possível sempre transformar um código encadeado de ifs em algo mais simples. Vc pode usar o chatgpt pra isso!

2ª Regra

Transforme todos os comentários de código em funções com nomes bem definidos! Não crie uma função que faça duas coisas distintas, separe !!!!

Por exemplo, se você for lançar um foguete, esse tipo de código nao seria muito bem vindo:

function lancarFoguete() {
  // Verificar se todos os sistemas estão prontos
  let sistemasProntos = true;
  let temperatura = 30; // temperatura dos motores em graus Celsius
  let pressao = 90; // pressão nos tanques de combustível em psi
  
  if (temperatura > 50 || pressao < 80) {
    sistemasProntos = false;
  }
  
  // Iniciar a contagem regressiva
  if (sistemasProntos) {
    console.log("Contagem regressiva iniciada...");
    
    for (let i = 10; i >= 1; i--) {
      console.log(i);
      // esperar 1 segundo antes de prosseguir
       const data = new Date();
      let tempoAtual = null;
      do { tempoAtual = new Date(); }
          while (tempoAtual - data < 1000);
        }
    
    // Acionar os motores
    acionarMotores();
    
    // Monitorar o progresso
    let altitude = 0;
    let velocidade = 0;
    
    while (altitude < 200000) { // o foguete sobe até 200 km de altitude
      altitude += calcularAltitude(velocidade); // função para calcular a altitude baseada na velocidade
      velocidade += calcularVelocidade(); // função para calcular a velocidade baseada na força dos motores
      
      if (temperatura > 100 || pressao < 60) { // verificar se há problemas
        abortarLancamento();
        return;
      }
    }
    
    // Chegar à órbita
    console.log("Foguete alcançou a órbita!");
  } else {
    console.log("Lançamento abortado. Problemas nos sistemas.");
  }
}

Veja que, apesar de termos alguns métodos como "acionarmotores()", essa funcao está claramente confusa. Nesse ponto, podemos dividir o máximo possível as tarefas em métodos, por exemplo

class Foguete {
  constructor() {
    this.sistemasProntos = false;
    this.motoresAcionados = false;
    this.emOrbita = false;
  }

  verificarSistemasProntos() {
    // verificar se todos os sistemas estão prontos
    this.sistemasProntos = true; // exemplo, considerando que todos os sistemas estão prontos
  }

  iniciarContagemRegressiva() {
    // iniciar a contagem regressiva
    for (let i = 10; i > 0; i--) {
      console.log(`Contagem regressiva: ${i}`);
      sleep(1000); // esperar 1 segundo
    }
  }

  acionarMotores() {
    // acionar os motores
    this.motoresAcionados = true; // exemplo, considerando que os motores foram acionados
  }

  monitorarProgresso() {
    // monitorar o progresso do lançamento
    console.log("Monitorando progresso do lançamento...");
    // exemplo, considerando que o lançamento foi bem sucedido
    this.emOrbita = true;
  }

  checarOrbita() {
    // verificar se o foguete está em órbita
    if (this.emOrbita) {
      console.log("Foguete em órbita!");
    } else {
      console.log("Foguete não atingiu órbita desejada...");
    }
  }

  lancarFoguete() {
    this.verificarSistemasProntos();
    if (!this.sistemasProntos) {
      console.log("Erro ao verificar sistemas. Lançamento abortado.");
      return;
    }
    this.iniciarContagemRegressiva();
    this.acionarMotores();
    this.monitorarProgresso();
    this.checarOrbita();
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

const meuFoguete = new Foguete();
meuFoguete.lancarFoguete();

Em algumas linguagens é possível, para cada método da classe, retornar ela mesma, de forma que possamos fazer um encadeamento de métodos:

const foguete = new Foguete();

foguete
  .verificarSistemas()
  .iniciarContagemRegressiva()
  .monitorarProgresso()
  .chegarEmOrbita()
  .lancarFoguete();

3ª Dica

Transforme vários parâmetros de um método em um objeto. Ao invés de termos um:

function salvarUsuario(nome, email, dataNascimento, telefone) {
  const erro = { descricao: "Parâmetro inválido" };
  if (!nome) return erro;
  if (!email) return erro;
  if (!dataNascimento) return erro;
  if (!telefone) return erro;
  // Aqui vai a lógica para salvar o usuário
  return { sucesso: true };
}

Podemos ter algo do tipo:

function salvarUsuario(user) {
  const erro = { descricao: "Parâmetro inválido" };
  if (!user.nome) return erro;
  if (!user.email) return erro;
  if (!user.dataNascimento) return erro;
  if (!user.telefone) return erro;
  // Aqui vai a lógica para salvar o usuário
  return { sucesso: true };
}

É claro que tendemos a usar objetos para que possamos ter um tipo. No exemplo anterior, podemos ter em typescript:

interface Usuario {
  id?: number;
  nome: string;
  email: string;
  dataNascimento: string;
  telefone: string;
}

interface Erro {
  descricao: string;
}

function salvarUsuario(user: User): User | Erro {
  const erro: Erro = { descricao: "Parâmetro inválido" };
  if (!user.nome) return erro;
  if (!user.email) return erro;
  if (!user.dataNascimento) return erro;
  if (!user.telefone) return erro;
  
  user.id = UsuarioService.save(user).getId();
  return user;
}

Neste exemplo, podemos ver que tanto User quanto Erro são classes de tipo (também chamadas de interface). Claro que existem algumas formas de reescrever esta função, já que alterar um objeto que foi passado por parâmetro nao é uma boa prática! Deixo como exercicio a solução.

Eu acredito que, seguindo essas três regras, seu código ficará melhor estruturado a um nível mais profissonal, mesmo que você não entenda muito de design patterns.

Carregando publicação patrocinada...
2

Complementando, vale mencionar que praticamente nada em programação é sagrado (no sentido de ser uma regra inviolável que vc deve seguir cegamente em todos os casos). Boas práticas só são boas de fato quando tem justificativa técnica, e isso sempre depende do contexto.

Claro que existem coisas que na maioria dos casos costumam ser melhores, e outras que geralmente são ruins em muitas situações. Mas pra muitas coisas a resposta é "depende". Nem sempre é "melhor" usar OOP, por exemplo (não estou discutindo o exemplo dado).

Falando de forma mais ampla, em vez de decorar receitas de bolo e sair usando em tudo sem pensar, o melhor é entender bem cada coisa, os prós e contras, quais problemas resolve bem e em quais casos não é uma boa, e aí sim analisar o problema e decidir se faz sentido usar.

Vc vai perceber naturalmente que algumas soluções costumam ser melhores, como é o caso do exemplo da cadeia de if's. Mas não tenha medo de quebrar uma regra se tiver uma boa justificativa técnica, de preferência obtida depois de uma análise decente.

Por fim, sobre o último exemplo: agrupar parâmetros em um único objeto só é adequado se fizer sentido que eles estejam juntos. Tem casos em que um meio termo fica melhor: juntar alguns dados relacionados em um objeto, e outros que não tem nada a ver com os primeiros vc junta em outro, ou então mantém separado mesmo (na minha opinião, criar um objeto pra juntar dados que não tem nada a ver uns com os outros é pior que manter esses dados separados - no seu exemplo fez sentido juntar, mas é o que eu disse, sempre analise antes de sair fazendo).

1

Com certeza, nada é sagrado. Se o código rodou, ta valendo!

Ainda nem cheguei no ponto de design patterns! Mas existem coisas que se bater o olho, dá pra perceber que o dev ainda nao tem uma experiencia adequada. No exemplo da idade, eu acredito ser totalmente sujo e dificil de ler usar if, outro if, aí um else if rs, sendo que fica mais fácil de ler a versão sem else.

No caso do objeto, claro, se estão com objetivos diferentes, nao junte !

2

Qual a fonte disso? Vejo um monte de "faça isso, não faça isso" mas nunca tem explicação lógica. Essas regras não podem ser aplicadas em qualquer lugar e nem em todas as situações.

Além disso, está forçando um code pattern que as vezes nem é o que o projeto já segue, e quando isso cai na mão de um leigo ou de um estudante, vira aquele código tudo misturado e sujo, tipo Laravel.

Não existem "boas práticas", e sim recomendações, e essas recomendações devem estar contextualizadas, em um cenário específico, e quase nunca geral.

1

a fonte é "MINHA OPINIÃO" (leia na voz do gaveta hahahahah). mas falando sério, eu posso estar sendo preconceituoso (talvez esteja mesmo), mas eu vendo um código desses:

function verificarIdade(idade) {
  if (idade < 0) {
    return "Idade inválida";
  } else {
    if (idade >= 0 && idade < 18) {
      return "Menor de idade";
    } else {
      if (idade >= 18 && idade < 60) {
        return "Adulto";
      } else {
        if (idade >= 60) {
          return "Idoso";
        }
      }
    }
  }
}

eu nao dou moral pro programador nao!!

Ahhh, e também.... claro Clean code e Object Calisthenics, são otimas referencias

2

Clean Code, aproveitando como resposta para o comentário abaixo, é um excelente livro e eu já li ele, mas estamos equivocados com a interpretação do mesmo.

Em nenhum momento o livro força que devemos usar essas práticas a todo momento.

Pelo contrário, o livro diz que deve existir coesão e sentido no código, e para ter isso deve haver necessidade de fazer tais otimizações. Um código limpo é ele ser facilmente legível por um humano, naquele contexto, interpretar o que o código faz, por quê está ali e qual seu propósito.

Seus exemplos não são ruins, mas não recomendo pregar estes conceitos como únicos e universais. É importante interpretar a necessidade, a utilidade e o custo dessas otimizações, que por muitas vezes são só syntax-sugars pro programador se sentir "confortável" mas não tem nenhuma motivação técnica ou embasada em fatos da área.

1

Sobre a regra 1

Legal também é usar ternário para quando for só duas opções fica fácil de ler, eu uso até no máximo 3, ternário encadeado também é ruim.

Outra coisa legal é objeto literal, principalmente trabalhando com strings

  function getIconName(name: string): string {
        const icon = {
            check: "fa-circle-check",
            error: "fa-circle-minus",
        };
        return icon[name] || "";
    }

E outro exemplo pode ser feito usando Map

function getAnimalName(name: string) {
        const animals = new Map([
            ["m", "Mouse"],
            ["c", "Cow"],
            ["l", "Lion"],
            ["e", "Elephant"],
        ]);
        return animals.get(name);
    }
1

Acho que um bom truque na verdade, é fazer o código e depois ir "limpando" ele.

Com isso você começa a pegar o costume de programar limpo antes mesmo de fazer. E mesmo o código parecendo bom, sempre verificar aonde dá pra melhorar mais.

Ou seja fazer a famosa refatoração. Por mais que seja chato no começo, você pega o costume e o código fica com cara mais profissional.