Código Limpo

A maioria dos desenvolvedores quando escutam “código limpo” pensam: “Não preciso me preocupar, meu código é ótimo”.

Se você pensou assim, saiba que as chances de estar completamente enganado são gigantescas!

É impressionante como encontramos código de péssima qualidade em projetos de manutenção, ou até mesmo de inovação, e o pior, os artistas destas publicações sabem que aquilo não está bom.

Não se engane, um bom código é extremamente importante e robusto para tudo que envolve o processo de desenvolvimento de software. Quantas vezes nos deparamos com programas que começam a funcionar bem, mas com o tempo o usuário pára de utilizar o software por problemas de lentidão ou bugs? E quantas vezes escutamos de profissionais ligados a esses softwares que a coisa está como está porque a manutenção do código é extremamente difícil e tudo está uma bagunça?

Um código ruim pode acabar com uma empresa ou com um projeto de grande potencial.

Se você é desenvolvedor, eu posso garantir que um código ruim e/ou confuso já fez você gastar horas e horas com desenvolvimento, entendimento, refatoração, dentre outras atividades.

É importante lembrar que não devemos julgar o código de ninguém, pois o número de variáveis que fez o desenvolvedor criar aquela bagunça é enorme: prazo insuficiente, novas demandas, necessidade de apoio ou até mesmo desmotivação (seja ela qual for) são grandes colaboradores de um código ruim.

Mas o que é um código limpo?

É comum que tenhamos várias definições sobre esse tema, é provável que o “conceito” de código limpo seja completamente diferente de um desenvolvedor para outro. Mas garanto que pelo menos um dos pontos abaixo serão citados:

  • Ser eficiente;
  • Ser simples;
  • Ser direto;
  • Ser de fácil entendimento para outro desenvolvedor realizar uma manutenção;
  • Não conter duplicidade de código.

Pensando nesses pontos, o seu código é limpo?

Lembre-se: um código limpo é simples! Muitas vezes um desenvolvedor acha que um bom código é aquele complexo e de difícil entendimento. E que as pessoas o respeitarão como um técnico de vasto conhecimento, quando na verdade a melhor forma é a mais simples, onde o material entregue atenda ao esperado e que qualquer desenvolvedor consiga ler e dar manutenção.

Nomes Significativos

Uma das coisas que está mais presente na vida de um desenvolvedor é o simples fato de colocar nomes nas coisas. Classes, funções, parâmetros e variáveis são bons exemplos de itens que nomeamos aos montes durante um projeto.

O primeiro passo, quando falamos sobre nomenclatura, é pensar em nomes que revelem realmente o propósito do que está sendo nomeado.

Sim, eu sei que você viu isso na faculdade. Porém minha pergunta é: por que você segue esta lógica, já que está tão familiarizado?

É muito importante escolher bons nomes para as variáveis que estão presentes no seu código. Não tenha medo de trocá-los, quantas vezes for necessário, até que estejam completamente claros para você (e principalmente para os outros que precisarão interagir com o seu código).

Veja o exemplo abaixo, ele está claro?

int d = 0; //Número de dias do projeto

Essa variável não quer dizer absolutamente nada! E o comentário está sendo usado de uma das piores maneiras possíveis. Suas variáveis têm vida própria e podem ser utilizadas em diversos pontos do código. Imagine uma manutenção neste código daqui a dois anos. Imagine também que essa variável está sendo referenciada novamente em um ponto completamente distinto de sua declaração, lá também terá o comentário? Acredito que não.

O nome da variável, função ou classe deve responder o porquê de ela existir, o que faz e como é usado. Olhe os exemplos abaixo:

int diasProjeto = 0;

int idadeProjetoEmDias = 0;

int tempoDecorridoProjeto = 0;

Melhorou, não é mesmo? Escolher bons nomes torna o trabalho bem mais fácil e o seu entendimento mais rápido. Será que não está aí a forma de melhorar, mesmo que pouco, a produtividade da sua equipe?

E o mesmo vale para funções, observe o código abaixo:

public List<object[]> RetornaLista(List<object[]> entrada)

   {

      List<object[]> lst = new List<object[]>();

  foreach (object[] i in entrada)

     {

       if (i[0].ToString() == “1”)

         lst.Add(i);

     }

      return lst;

   }

É possível afirmar o que faz o código sem ter que recorrer a quem o criou ou a algum comentário? Agora, veja o mesmo código escrito com uma nomenclatura mais clara:

public List<object[]> RetornaListaClientesPessoaJuridica(List<object[]> clientesSelecionados)

   {

      List<object[]> clientesPessoaJuridica = new List<object[]>();

      foreach (object[] cliente in clientesSelacionados)

     {

       if ((TipoCliente)cliente[0] == TipoCliente.PessoaJuridica)

         clientesPessoaJuridica.Add(cliente);

     }

      return clientesPessoaJuridica;

   }

Garanto que ainda existe um ponto que está incomodando bastante no exemplo acima. Provavelmente você está se perguntando o porquê de uma lista de arrays, isso é um array de quê?

É muito importante para um sistema uma definição clara dos tipos presentes no nosso código. E é crucial lembrar que sempre podemos contar com os conceitos da orientação a objetos e fazer uso deles (lembrar e deixar de lado não adianta). As linguagens de programação evoluíram para tornar possível o desenvolvedor melhorar a sua forma de apresentar o código, e prover condições de executar o desenvolvimento de forma clara e objetiva. Veja abaixo:

public List<Cliente> RetornaListaClientesPessoaJuridica(List<Cliente> clientesSelacionados)

   {

      List<Cliente> clientesPessoaJuridica = new List<Cliente>();

      foreach (Cliente clienteAtual in clientesSelacionados)

     {

       if (clienteAtual.Tipo == TipoCliente.PessoaJuridica)

         clientesPessoaJuridica.Add(clienteAtual);

     }

      return clientesPessoaJuridica;

   }

Agora sim! Compare os códigos. O último não é mais claro que o primeiro? Observe que sem nenhum contexto ou comentário, podemos afirmar que a função RetornaListaClientesPessoaJuridica lista todas as empresas que constam na lista de clientes selecionados. A única coisa que fizemos foi utilizar nomes claros e significativos.

Existem também, outros tópicos que são primordiais para uma boa nomenclatura, são eles:

  • Evite informações irrelevantes – colocar nomes que se relacionem com itens conhecidos para a TI (como por exemplo, hp ou aix que são nomes de plataformas ou variantes). Isso passa para o responsável pela manutenção uma dica falsa, que somente o confundirá.
  • Use nomes pronunciáveis – muito do seu cérebro é responsável pelo conceito das palavras e elas são pronunciáveis. Imagine uma variável chamada faz33kjsdhi e durante uma daily meeting você vai falar sobre o contexto da variável, como pronunciá-la?
  • Evite abreviações – você já tem que se preocupar com a linguagem, não é justo que seja necessário aprender as abreviações das variáveis.
  • Não faça trocadilhos – evite utilizar a mesma palavra para dois propósitos diferentes.
  • Adicione contextos significativos – existem poucos nomes significativos por si só, para a grande maioria que não é. Adicione contextos significativos como primeiroNome, ultimoNome, rua, logradouro e numeroResidencia. Lembre-se: nunca adicione um contexto desnecessário.
  • Faça distinções significativas – usar números sequenciais em nomes (telefone1, telefone2, telefone3…) é o oposto de nomes expressivos.
  • Codifique para o seu público-alvo – não adianta você escrever uma lambda expression para uma equipe que não entende o que aquilo significa. Mas isso não quer dizer que você não pode usar linq (é um acrônimo para Language Integrated Query, ou Consulta Integrada à Linguagem) ou uma nova tecnologia em um código e limitar o seu conhecimento por causa da sua equipe. Isso significa que sua equipe precisa ser treinada, e esta é uma boa oportunidade para você se destacar e treiná-los.

Entender a importância da nomenclatura de todo o seu contexto de código é o primeiro passo para trabalharmos de forma mais clara em nossos projetos. Sempre que pensar em criar uma classe, função ou variável com nomes aleatórios, lembre-se do exemplo da função que retorna as empresas de uma lista de clientes e reflita se aquele fragmento de código é auto explicável e objetivo.

Funções

Funções são a primeira linha de organização em qualquer programa. Um sistema onde as funções são feitas com desleixo pode ser funcional no início do projeto, mas o tempo irá provar que toda aquela confusão tem um preço, e que pode ser muito caro (como a perda de um cliente importante).

Já falamos da importância dos nomes significativos, no exemplo mostrei que o nome da função é importantíssimo, pois através dele já começamos a estruturar em nossas cabeças do que se trata esta função. Um bom nome significa muito para uma função.

Quando lemos uma função, geralmente acompanhamos todo o seu raciocínio passo a passo. Logo o principal é que ao lermos uma função, transformamos aquele código em um capítulo de um livro, e a partir daí buscamos entendê-lo melhor. Observe a seguinte função:

private void PreencheProjeto(Projeto projetoCorrente)  

    {

      projetoCorrente.Nome = tbNomeProjeto.Text;

      projetoCorrente.Endereco = tbEndereco.Text;    

      projetoCorrente.DiasExecutadosProjeto = diasExecutadosProjeto;

      projetoCorrente.DiasPrevistosProjeto = diasPrevistoProjeto;

      projetoCorrente.Observacoes = tbObservacao.Text;

      projetoCorrente.EmailResponsavelProjeto = tbEmailResponsavelProjeto.Text;

      if (diasExecutadosProjeto > diasPrevistoProjeto)

      {

        projetoCorrente.MotivoAtraso = tbMotivoAtraso.Text;

        projetoCorrente.Responsavel = tbResponsavel.Text;

        projetoCorrente.ExisteMulta = cbPossuiMulta.Checked;

        projetoCorrente.ValorMulta = Convert.ToDouble(tbValorMulta.Text);

        projetoCorrente.CustoAtualizado = projetoCorrente.CustoAtualizado + projetoCorrente.ValorMulta;

        if (cbEnviaEmailResponsavel.Checked)

        {

          StringBuilder mensagemEmail = new StringBuilder();

          mensagemEmail.Append(“Prezado Sr(a) “);

          mensagemEmail.Append(projetoCorrente.Responsavel);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“O projeto “);

          mensagemEmail.Append(projetoCorrente.Nome);

          mensagemEmail.Append(” foi gravado com sucesso. Favor verificar suas informações a aprovar o fluxo.”);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“Importante: As interações executadas no projeto superam a previsão inicial.”);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“Att.”);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“Administração de Projetos”);

        }

      }

      else {

        projetoCorrente.Responsavel = tbResponsavel.Text;

        projetoCorrente.ExisteMulta = cbPossuiMulta.Checked;

        projetoCorrente.CustoAtualizado = Convert.ToDouble(tbCustoAtualizado.Text);

        if (cbEnviaEmailResponsavel.Checked)

        {

          StringBuilder mensagemEmail = new StringBuilder();

          mensagemEmail.Append(“Prezado Sr(a) “);

          mensagemEmail.Append(projetoCorrente.Responsavel);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“O projeto “);

          mensagemEmail.Append(projetoCorrente.Nome);

          mensagemEmail.Append(” foi gravado com sucesso. Favor verificar suas informações a aprovar o fluxo.”);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“Att.”);

          mensagemEmail.Append(“<br>”);

          mensagemEmail.Append(“Administração de Projetos”);

        }

      }

    }

Perceba que se trata de uma função “trivial”, que faz o preenchimento dos dados de um projeto, baseado no que foi preenchido no cadastro.

O que há de errado nesta função?

Veja bem, os nomes estão claros, conseguimos entender do que se trata sem a necessidade de um comentário e sem ter que recorrer ao requisito. Mas ela ainda pode ser melhorada.

Precisamos entender sobre funções e que elas precisam ser pequenas! Funções pequenas favorecem o entendimento e a percepção de um problema para as pessoas que irão dar manutenção no seu código.

Mas como simplificar uma função tão objetiva? Para responder esta pergunta vamos analisar passo-a-passo a função acima:

No início da função, os dados principais do projeto são inseridos no objeto projetoCorrente.

Após uma validação que verifica se os dias de execução do projeto são superiores aos dias previstos, a função preenche os dados de atraso do projeto.

Caso os dias de execução do projeto não sejam superiores aos dias previstos, a função preenche os dados de execução do projeto.

Em cada uma das validações, um e-mail é enviado para o responsável do projeto informando que os dados foram gravados.

Quando analisamos a função, passo-a-passo, identificamos pontos onde é possível agrupar os dados e com isso simplificar a leitura da função. Veja o exemplo:

private void PreencheProjeto(Projeto projetoCorrente)

    {

      PreencheDadosPrincipaisProjeto(projetoCorrente);

      if (diasExecutadosProjeto > diasPrevistoProjeto)

      {

        PreencheDadosAtrasoProjeto(projetoCorrente);

      }

      else

      {

        PreencheDadosExecucaoNormalProjeto(projetoCorrente);

      }

    }

    private void PreencheDadosPrincipaisProjeto(Projeto projetoCorrente)

    {

      projetoCorrente.Nome = tbNomeProjeto.Text;

      projetoCorrente.Endereco = tbEndereco.Text;

      projetoCorrente.DiasExecutadosProjeto = diasExecutadosProjeto;

      projetoCorrente.DiasPrevistosProjeto = diasPrevistoProjeto;

      projetoCorrente.Observacoes = tbObservacao.Text;

      projetoCorrente.EmailResponsavelProjeto = tbEmailResponsavelProjeto.Text;

    }

    private void PreencheDadosAtrasoProjeto(Projeto projetoCorrente)

    {

      projetoCorrente.MotivoAtraso = tbMotivoAtraso.Text;

      projetoCorrente.Responsavel = tbResponsavel.Text;

      projetoCorrente.ExisteMulta = cbPossuiMulta.Checked;

      projetoCorrente.ValorMulta = Convert.ToDouble(tbValorMulta.Text);

      projetoCorrente.CustoAtualizado = projetoCorrente.CustoAtualizado + projetoCorrente.ValorMulta;

      if (cbEnviaEmailResponsavel.Checked)

      {

        StringBuilder mensagemEmail = new StringBuilder();

        mensagemEmail.Append(“Prezado Sr(a) “);

        …

      }

      Email.EnviarEmail(projetoCorrente.EmailResponsavelProjeto, mensagemEmail);

    }

    private void PreencheDadosExecucaoNormalProjeto(Projeto projetoCorrente)

    {

      projetoCorrente.Responsavel = tbResponsavel.Text;

      projetoCorrente.ExisteMulta = cbPossuiMulta.Checked;

      projetoCorrente.CustoAtualizado = Convert.ToDouble(tbCustoAtualizado.Text);

      if (cbEnviaEmailResponsavel.Checked)

      {

        StringBuilder mensagemEmail = new StringBuilder();

        mensagemEmail.Append(“Prezado Sr(a) “);

        …

      }

      Email.EnviarEmail(projetoCorrente.EmailResponsavelProjeto, mensagemEmail);

    }

Transformamos uma função grande em várias funções pequenas. Repare como a leitura melhorou bastante. Mas ainda falta alguma coisa, não acham?

Outro ponto importante sobre funções, algo tão simples que infelizmente na maioria das vezes deixamos de lado, uma função deve fazer apenas uma coisa, deve fazê-la bem e deve fazer apenas ela.

Observe as funções PreencheDadosAtrasoProjeto e PreencheDadosExecucaoNormalProjeto, elas deveriam apenas preencher os dados, mas elas também enviam um e-mail para o responsável pelo projeto. Considere o código abaixo:

private void PreencheDadosPrincipaisProjeto(Projeto projetoCorrente)

    {

      projetoCorrente.Nome = tbNomeProjeto.Text;

      projetoCorrente.Endereco = tbEndereco.Text;

      projetoCorrente.DiasExecutadosProjeto = diasExecutadosProjeto;

      projetoCorrente.DiasPrevistosProjeto = diasPrevistoProjeto;

      projetoCorrente.Observacoes = tbObservacao.Text;

      projetoCorrente.EmailResponsavelProjeto = tbEmailResponsavelProjeto.Text;

    }

    private void PreencheDadosAtrasoProjeto(Projeto projetoCorrente)

    {

      projetoCorrente.MotivoAtraso = tbMotivoAtraso.Text;

      projetoCorrente.Responsavel = tbResponsavel.Text;

      projetoCorrente.ExisteMulta = cbPossuiMulta.Checked;

      projetoCorrente.ValorMulta = Convert.ToDouble(tbValorMulta.Text);

      projetoCorrente.CustoAtualizado = projetoCorrente.CustoAtualizado + projetoCorrente.ValorMulta;

    }

    private void PreencheDadosExecucaoNormalProjeto(Projeto projetoCorrente)

    {

      projetoCorrente.Responsavel = tbResponsavel.Text;

      projetoCorrente.ExisteMulta = cbPossuiMulta.Checked;

      projetoCorrente.CustoAtualizado = Convert.ToDouble(tbCustoAtualizado.Text);

    }

    private void EnviaEmailExecucaoNormal(Projeto projetoCorrente) {

      if (cbEnviaEmailResponsavel.Checked)

      {

        StringBuilder mensagemEmail = new StringBuilder();

          mensagemEmail.Append(“Prezado Sr(a) “);

          …

      }

      Email.EnviarEmail(projetoCorrente.EmailResponsavelProjeto, mensagemEmail);

    }

    private void EnviaEmailExecucaoComAtraso(Projeto projetoCorrente)

    {

      if (cbEnviaEmailResponsavel.Checked)

      {

        StringBuilder mensagemEmail = new StringBuilder();

        mensagemEmail.Append(“Prezado Sr(a) “);

        …

      }

      Email.EnviarEmail(projetoCorrente.EmailResponsavelProjeto, mensagemEmail);

    }

    private void btSalvarProjeto_Click(object sender, EventArgs e)

    {

      PreencheDadosPrincipaisProjeto();

      if (diasExecutadosProjeto > diasPrevistoProjeto)

      {

        PreencheDadosAtrasoProjeto();

        EnviaEmailExecucaoComAtraso();

      }

      else

      {

        PreencheDadosExecucaoNormalProjeto();

        EnviaEmailExecucaoNormal();

      }

      projetoCorrente.SalvarProjeto();

    }

Reparou que uma das funções declaradas anteriormente desapareceu? A função foi descontinuada porque é melhor para a visualização do código executar o envio de e-mail, onde ele realmente é necessário (neste exemplo, no momento de salvar o registro). Não se preocupe em reescrever ou descartar partes do código (ou até mesmo a classe inteira), o impacto positivo que uma boa estruturação de funções tem no código, com certeza compensa o tempo que você perdeu reescrevendo o código que não estava bom.

Lembre-se, você também é um leitor de sua própria obra, e fatalmente terá que colocar a mão na massa para fazer algum ajuste.

Parâmetros

A nomenclatura é extremamente importante para um sistema. Não tenha medo de usar nomes extensos que irão demonstrar de forma clara seu objetivo. E não se esqueça dos parâmetros, eles também devem receber sua atenção.

Quando criamos uma função, é comum enxergarmos a necessidade de fornecer parâmetros para ela. Quando falamos de funções com parâmetros, essas podem ser nômades (com apenas um parâmetro), díades (com dois parâmetros), tríades (com três parâmetros) e por aí vai. Muitos autores afirmam que o ideal é trabalharmos com funções sem parâmetros, mas sabemos que nem sempre isso é possível. O que não podemos deixar acontecer é um método com muitos parâmetros, como podemos ver:

public bool SalvarProjeto(string observacoes, string motivoAtraso, string responsavel, bool existeMulta, double valorMulta, string nome, double custoAtualizado, string endereco, int diasPrevistosProjeto, int diasExecutadosProjeto, int quantidadeFuncionariosAlocados, string emailResponsavelProjeto)

    {

      …

    }

Sempre que possível, utilize objetos para diminuir a quantidade de parâmetros que são passados para uma função. Crie objetos, listas e faça com que a leitura não fique algo pesado e complicado, o intellisense (no Visual Studio) traz ao desenvolvedor todos os overloads da função que está sendo utilizada e seus parâmetros, facilite a leitura para todos! Veja como fica melhor:

public bool SalvarProjeto (Projeto projetoParaSalvar)

    {

      …

    }

Lembre-se também de que o código que você escreve é como uma narrativa, e sendo assim, será lido de cima para baixo. Escreva o seu programa como uma série de parágrafos, cada uma descrevendo o nível atual e, se necessário, fazendo referências aos próximos parágrafos.

Evite retornar códigos de erro, prefira tratar as exceções e apresentar ao usuário de forma mais amigável. Pensando (e fazendo) assim, você terá o código de tratamento de erro separado e simplificado. É importante lembrar, a função faz uma coisa só, o tratamento de erro também deve seguir essa premissa.

Evite (e se possível elimine) duplicidade em suas funções! Essa pode ser a raiz de todos os males de um sistema. Quem nunca corrigiu um erro em uma função X e viu o analista de qualidade reportar que o erro continua acontecendo e, ao analisar novamente o código, você percebe que existem outras três ou quatro funções que fazem exatamente a mesma coisa?

Funções devem ser curtas, bem nomeadas e organizadas. Não se esqueça de que o objetivo principal é contar, de forma clara e objetiva, qual é a história do sistema.

Comentários

Comentários são textos descritivos não interpretados pelo compilador, ou seja, são textos soltos no código geralmente utilizados para explicar ou facilitar o entendimento de algum ponto do código.

O melhor comentário é o que não precisa ser feito, quando o seu código é autoexplicativo. Mas não há como negar que eles são como a Lista de Schindler, não são o “bom puro”, mas sim um mal necessário. É inegável dizer que um bom comentário quando bem colocado é extremamente útil, mas igualmente podemos dizer que nada consegue amontoar mais o módulo do que um comentário supérfluo, enganador ou em um lugar indevido.

Robert Martin em seu livro diz:

“O uso adequado de um comentário é compensar nosso fracasso em nos expressar no código.”

Explique-se no código! Lembre-se o que vimos anteriormente, é possível afirmar o comportamento de um bloco de código sem a necessidade de recorrer a especificação ou a algum comentário.

Mas nem todos os comentários são “ruins”, existem casos em que sua aplicação é indispensável, como:

  • Legais – muitas vezes, os padrões das empresas nos forçam a escrever blocos de comentários por questões legais (como direitos autorais). Mas ter em todo início classe um comentário de 20 páginas antes do código também já é demais. Faça referências a documentos externos e a licenças padrão sempre que possível.
  • Informativos – fornecer informações básicas em um comentário pode ser muito útil, como, por exemplo, comentar em uma função explicando o que ela faz. Mas sempre que possível, prefira um nome significativo.
  • Esclarecimento – às vezes podemos explicar pontos confusos com um comentário, assim a pessoa responsável pela manutenção não perde tanto tempo tentando entender como aquilo realmente funciona.
  • Alertar sobre consequências – existem trechos de código críticos onde o impacto de uma alteração pode ser catastrófico, nestes pontos um comentário de alerta é mais que bem-vindo.

Evite ao máximo utilizar em seu código comentários ruins. Lamentações, comentários redundantes, longos demais, de código que não está sendo utilizado mais (para que guardar isso no código? Caso seja necessário, recupere uma versão no source control) e, principalmente, comentários enganadores.

Referências

[Clean Code]

Clean Code: A Handbook of Agile Software Craftsmanship (English Edition) – Martin, Robert Cecil

 

Comment Area

  1. Tagata13/07/2021

    Muito boa a matéria Henrique, parabéns.

    Recomendo apenas uma correção ortográfica “clientesSelacionados”.

    Uma questão também, sempre busquei usar o idioma inglês quando codifico, você vê isso um como um problema quando estamos atendendo demandas de software no Brasil?

  2. Bruno Oliveira22/07/2021

    Muito bom o conteúdo, parabéns Henrique! E concordo com você, realmente é muito importante pensar em escrever um bom código, porque isso possibilita as empresas a contratarem mais desenvolvedores junior, devido ao fato de que o código bem escrito permite aprender muito mais, diminuindo a curva de aprendizado.