O que um programador faz?

A vida de um programador não é fácil. A tecnologia é algo que não para de evoluir e a cada dia surge uma forma diferente de escrever um código. A carreira de um profissional de informática é algo de sua responsabilidade.

O código produzido por esse profissional também é de sua responsabilidade. Um bom código não é somente aquele que é funcional, mas também aquele que não tem valores exorbitantes para ser mantido. A maior parte dos programadores não gostam de alterar códigos mal escritos. Isso é algo que traz muita frustração e muitas vezes um retrabalho desnecessário.

Infográfico - o que é programação
Infográfico 1. O que é programação.

Clean Code

Um código limpo deve ser:

  • Simples: código fácil de entender;
  • Direto: vai direto ao ponto, não dá “voltas" para atingir seu objetivo;
  • Eficiente: código que faz o que é proposto;
  • Sem duplicidade: não faz o que outra parte do código já faz;
  • Elegante: porque é diferente dos outros códigos;
  • Feito com cuidado: quem fez teve preocupação em produzir aquele código.

Antes de falarmos sobre como fazer para atingir esse nível de qualidade, vamos falar um pouco sobre testes.

Importância dos testes

Construir um software não é somente escrever código e vê-lo funcionar, é você saber que aquele código será manutenível e que outras pessoas vão alterá-lo.

Para isso, teste é fundamental! Você tem que ser responsável por aquilo que escreve e saber que seu sistema tem que continuar funcionando. Neste contexto, temos a primeira dica sobre um código limpo: Toda linha que você escrever deve estar testada e ponto final !

Muitas empresas veem testes como gastos maiores no projeto, o que de fato acontece, porém a qualidade do software produzido é algo significante. Quando não se produz teste automatizado, a quantidade de testes manuais são maiores e muitas vezes o custo desses testes também é maior.

Escrevendo um bom código

Nomes significativos

Métodos, nomes de variáveis e etc. devem possuir nomes que significam alguma coisa em relação ao seu objetivo. Os nomes utilizados devem responder todas as questões a seguir:

  • Porque existem?
  • O que fazem?
  • Como são usadas?

Vamos imaginar que um sistema de um motor de um carro tenha um método com o nome de “run” ao invés de “acelerar”. Se você pegar um código com esse nome você terá que estudar todo o método para saber o que ele faz.

Algo muito comum encontrado nos códigos é o tipo de declaração apresentado na Listagem 1.

Listagem 1. Exemplo de declaração.

  public class NotaFiscal {
      private Date d1;//Data da compra
      private Date d2;//Data de vencimento
     private boolean validaDatas(){
       //Valida se data do vencimento é maior que a data de compra
       if(d1.after(d2))){
            return true;
       }
        return false;
     }
      //getters e setters
  }

Se um nome de classe, método ou atributo requer um comentário, ele não está revelando sua real intenção.

Quando colocamos uma linha em nosso código com um comentário ao lado não estamos dando o nome correto ao atributo ou método. O código quando bem escrito deve ser algo que seja de fácil leitura, algo que uma pessoa leiga conseguiria ao menos saber o que o mesmo faz. Os nomes utilizados devem ser pronunciáveis, algo que você entenda.

Observe no exemplo da Listagem 2 como essa prática torna o código mais fácil de ser entendido.

Listagem 2. Exemplo de declaração considerando boas práticas.

  public class NotaFiscal {
   
      private Date dataCompra;
      private Date dataVencimento;
   
     private boolean isDataVencimentoMaiorDataCompra(){
   
       return dataCompra.after(dataVencimento);  
   
     }
   
      //getters e setters
  }

Evite notação húngara

A notação Húngara visa facilitar o reconhecimento do tipo de variável em um programa colocando em seu nome um sufixo descrevendo seu tipo (ver Listagem 3). Entretanto, com o advento de novas linguagens, técnicas mostradas aqui e testes automatizados, a notação húngara se mostra desnecessária.

Existe uma certa tendência para a criação de classes e métodos menores de modo que as pessoas possam ver onde cada variável que estão usando foi declarada. Além disso, os testes indicam os tipos e maneiras de usar, validando o comportamento esperado do método.

Listagem 3. Exemplo de uso de notação húngara.

  public class Pessoa {
   
      private String nomeString;
   
     // Não existe aqui a necessidade de se colocar a palavra 'String', pode-se somente ficar 'nome' 
   
      //getters e setters
  }

Classes e métodos

Nome de classes devem ser substantivos e não conter verbos. Já nomes de métodos devem conter verbos pois eles indicam ações.

A regra para métodos é: “A primeira regra dos métodos é que eles devem ser pequenos. A segunda regra é que eles devem ser menores ainda.”

Métodos e classes menores são mais fáceis de ler e entender, além de manter é claro. Segundo o livro, podemos considerar as seguintes métricas:

  • Métodos <= 20 linhas;
  • Linha <= 100 caracteres;
  • Classe = 200 a 500 linhas.

Claro que toda regra tem sua exceção. Se você tem uma classe que vai precisar de mais linhas, um método que também precise de mais linhas, isso não é um problema.

“Métodos e funções devem fazer somente uma coisa, fazê-la certa e somente fazê-la”.

Poderíamos analisar essa frase como um princípio da coesão no seu código. Muitas vezes não é fácil saber se aquele método está fazendo somente uma coisa. Uma dica para isso é: você deve tentar extrair parte do seu código para um método, se você conseguir é porque aquele seu método realmente não está tendo uma função apenas.

Imagine que você tenha um método onde quiséssemos mostrar os detalhes de um usuário:


   private void mostrarDadosUsuario(Usuario usuario){
    mostrarCabecalhoUsuario();
    System.out.print(“Nome: “, usuario.getNome());
    System.out.print(“Sobrenome: “, usuario.getSobrenome());
   }

Neste exemplo, as linhas do System.out.print são os detalhes do usuário. Mas será que isso não ficaria melhor escrito se estivesse de acordo com o código da Listagem 4?

Listagem 4. Separando métodos

  private void mostrarDadosUsuario(Usuario usuario){
     
       mostrarCabecalhoUsuario();
       mostrarDetalhesUsuario();
   
  } 
   
    private void mostrarDetalhesUsuario{
       System.out.print(“Nome: “, usuario.getNome());
       System.out.print(“Sobrenome: “, usuario.getSobrenome());
    }

Se um dia você quiser apenas listar os dados de um usuário ficará mais fácil. Agora temos os métodos separados. Essa prática também é um bom exemplo do tipo de refatoração chamada “Extract Method”.

Um outro item que deve ser observado é a quantidade de parâmetros de um método. Você deve ter uma justificativa muito boa para ter uma quantidade tão grande de parâmetros em um método.

Um agravante de um método com vários parâmetros é a dificuldade de se testar uma vez que você deverá testar todas as combinações possíveis.

Outra situação a que você deve estar atento é com um método que informa que irá fazer uma determinada ação e faz outra. Observe a Listagem 5.

Listagem 5. Métodos com objetivos mal definidos.

  public boolean verificarSenha(String senha){
       if(senha.equals(“zzz”)){
           Session.initialize();
           return true;
       }
       return false;
  }

O objetivo do método é verificar a senha, porém, se a senha estiver correta o mesmo inicia uma sessão, ou seja, o método já não tem a coesão esperada, pois possui duas responsabilidades.

Uma solução melhor para esse cenário pode ser observada na Listagem 6.

Listagem 6. Ajuste do objetivo do método.

  if(verificaSenha(“zzz”){
       Session.initialize();
  }
   
  public boolean verificarSenha(String senha){
       if(senha.equals(“zzz”)){
           return true;
       }
       return false;
  }

Comentários nos códigos

Comentários, apesar de importantes, podem trazer desinformação. Por que podemos afirmar isso? Alguém conhece programadores que atualizam comentários? Há vários códigos com vários comentários que não serviam para nada e, pior, confundiam. Se um método ou uma classe estiver bem escrito, a importância do comentário é minimizada.

Outro ponto importante, um comentário não irá esconder um código ruim. Observe o exemplo a seguir:

Date d1;

Esse código já está ruim, de nada adianta mudarmos para:

Date d1; //dia da semana

Esse comentário não irá se propagar para todo o código e sempre que você se deparar com uma linha como “d1.after(d2);" você vai continuar não entendendo o propósito do código.

Podemos tentar colocar uma regra nisso. Muitas vezes quando se comenta um código, pode ser que o mesmo precise ser refatorado. Lembra dos exemplos anteriores onde “d1” passou a ser “dataCompra” ? Com essa mudança seu código pode ser entendido por todos e se fizermos essa refatoração o código passa a não precisar mais de comentário.

Observe agora o exemplo a seguir:


//Verifica se o usuário tem direito ao benefício
  if(usuario.getIdade() > 10 && usuario.getIdade() < 20){
    ….
  }

Observe agora o exemplo ajustado na Listagem 7.

Listagem 7. Eliminando o comentário do código.

  if(isUsuarioTemDireitoAoBeneficio(usuario)){
       ….
  }
   
  private boolean isUsuarioTemDireitoAoBeneficio(Usuario usuario){
       if(usuario.getIdade() > 10 && usuario.getIdade() < 20){
           return true;
       }
       return false;
  }

Note que tiramos o comentário, melhoramos o código e o tornamos mais legível. Agora a leitura do código é suficiente para saber o que ele realmente faz.

Outro tipo de comentário que deve ser evitado é apresentado no exemplo a seguir:


private boolean isUsuarioTemDireitoAoBeneficio(Usuario usuario){    if(usuario.getIdade() > 10 && usuario.getIdade() < 20){
      return true; //Retorna verdadeiro
    }
    return false; //Retorna falso
  }

O return do método é lógico, não há necessidade de indicar o que o mesmo está retornando.

Em relação a comentários, podemos dizer que: “Qualquer comentário que faça você olhar para outras partes do seu código para entendê-lo não valem os bits que consomem.”

Por outro lado, existem momentos em que o comentário é importante. Digamos que você tenha um trecho em seu código que vai demandar um tempo de processamento alto ou a disponibilidade de um recurso. Nesses casos, comentários acabam sendo úteis.

Algumas vezes também não se consegue colocar um nome em um método que explique o porquê o desenvolvedor tomou aquela decisão.

Formatação

“Formatação é importante, pois se trata de comunicação.”

Temos que considerar que o código é a maneira que a equipe de desenvolvimento vai se comunicar. Uma pessoa não gostaria de receber uma carta cifrada onde tivesse que interpretar o que está escrito nela, podemos pensar assim na hora de escrever um código.

Outra ponto importante é que se você pega um código bem estruturado, você vai querer mantê-lo bem estruturado. É ruim para qualquer desenvolvedor ter acesso a um código sem formatação, sem endentação e ter que fazer sua leitura como se fosse um texto sem qualquer pontuação.

Além disso, métodos com conceitos relacionados devem ficar verticalmente próximos e a ordem dos métodos deve criar um fluxo de leitura melhorando a legibilidade do código.

Uma boa endentação é fundamental, mas não podemos ter muitos níveis. Observe como o trecho a seguir poderia se tornar confuso caso a lógica implementada fosse complexa:


if(a>1){
    if(b>1){
      if(c>1){
        if(z>1){
          …
        }
       }
    }
  }

Tratamento de erros

"Quando estamos programando devemos tratar os possíveis erros que nossa aplicação poderá lançar, as coisas podem dar errado e temos que estar certos que nosso código fará o que deve fazer."

Tratamento de erro é de responsabilidade do desenvolvedor. É preciso garantir que o código vai ter um tratamento para cada situação. Prefira lançar uma exception ao invés de retornar um código de erro. Estes retornos desorganizam a chamada do método e pode-se facilmente esquecer de verificá-los.

Dentro do seu método você já pode ver o erro que está sendo retornado e tratá-lo ali. Defina o fluxo do método separando as regras de negócio de erros ou outras situações. Para seus erros, crie mensagens informativas mencionando a operação que falhou e o tipo de falha.

Procure utilizar exceptions para situações inesperadas, por exemplo: seu código está lendo um arquivo e a rede se tornou indisponível.

TDD

TDD nada mais é que o desenvolvimento guiado por testes. As três regras do TDD são:

  • Você não pode escrever um código até que tenha criado um teste falhando;
  • Você não pode escrever mais teste do que seja suficiente para falhar;
  • Você não pode escrever mais código do que o suficiente para passar no teste que está falhando.

Assim, se você tiver que testar se um CPF é válido, por exemplo, você deve criar alguns testes tais como:

  • se o CPF for em branco;
  • se o CPF estiver com menos caracteres.

Os testes devem considerar as características F.I.R.S.T:

  • *F (Fast): deve ser rápido. Testes demorados tiram a motivação dos profissionais responsáveis por sua execução;
  • *I (Independent): não podem depender um do outro pois se um falha o outro vai falhar também;
  • *R (Reapetable): executando mais de uma vez eles devem retornar sempre o mesmo resultado;
  • *S (Self-Validating): devem se autovalidar;
  • *T (Timely): devem ser feitos antes do código.

Refatoração

Escrever um bom código muitas vezes pode não parecer uma missão tão simples. Considere o trecho de código a seguir:


  private boolean isStringVazia(String texto){
    if (!StringUtils.isNullOrEmpty(texto) && !texto.equals("")) {
      //...
    }
  }

Concorda que o "!texto.equals("")" não serve para nada? Se fizermos a refatoração a seguir obteremos o mesmo resultado:


private boolean isStringVazia(String texto){
    if (!StringUtils.isNullOrEmpty(texto)) {
      //...
    }
  }

Agora, digamos que ainda assim estivéssemos com receio de fazer essa refatoração. Neste caso, a presença de um simples teste unitário poderia eliminar a dúvida referente ao fato da funcionalidade continuar desempenhando seu papel corretamente.

A refatoração é uma das melhores práticas para melhorarmos nosso código. Seu código pode ser eficaz, ou seja, fazer o que se deseja, mas também pode ser eficiente, fazer o que se deseja da melhor maneira possível.

Saiba mais sobre programação ;)

  • Introdução a Programação:
    Conheça nosso Guia de Referência de Introdução à Programação. Aprenda a programar na DevMedia e torne-se um profissional preparado para o mercado. Acesse!
  • DevCast: Programação: O que é uma variável?:
    Neste DevCast conversamos sobre o que é uma variável e qual o seu papel, permitindo assim que você crie o seu primeiro programa: um aplicativo que recebe um nome e imprime na tela uma mensagem de boas vindas.
  • Lógica de Programação:
    Neste curso veremos uma introdução a algoritmos, utilizando como linguagem de apoio o Portugol.

Confira também