quarta-feira, 23 de novembro de 2011

Seu código é legível?

Não sei como isso funciona para as outras pessoas, mas sempre que volto para um código que escrevi há algum tempo, tenho dificuldade em lembrar o propósito ou o funcionamento desse código. Pior que isso só quando tenho que mostrar pra alguém e esse alguém percebe que nem eu estou entendendo o que eu escrevi. Isso porque no começo da minha jornada no mundo da programação, dava pouco valor a alguns detalhes que podem fazer uma enorme diferença na hora de ler e entender o código.

Imaginando o cenário de uma locadora, vamos analisar o seguinte trecho de código:

    Cliente cliente = new Cliente();
    cliente.setNome("Geraldo Ferraz");
 
    Filme filme = new Filme();
    filme.setNome("Star Wars");
 
    Locadora locadora = new Locadora();
 
    locadora.getFilmes().add(filme);
    locadora.getClientes().add(cliente);
 
    Locacao locacao = new Locacao();
    locacao.setCliente(cliente);
    locacao.setFilme(filme);
    locacao.setData(Calendar.getInstance());
 
    locadora.getLocacoes().add(locacao);
    locadora.getLocacoes().remove(locacao);

O que esse código faz?
Se você concluiu rapidamente que esse código está cadastrando o cliente "Geraldo" e imediatamente fazendo a locação do filme "Star Wars" é porque provavelmente você está familiarizado com essa  implementação. Pra quem não está e mesmo assim chegou na mesma conclusão, onde sentiu mais dificuldade?

Teria sido mais fácil se o código estivesse assim:

    Cliente geraldo = new Cliente("Geraldo Ferraz");
    Filme starWars = new Filme("Star Wars");

    Locadora locadora = new Locadora();
    locadora.associar(geraldo);
    locadora.adicionarAoAcervo(starWars);
 
    locadora.locarFilme(starWars).para(geraldo);
    geraldo.devolverPara(locadora).oFilme(starWars);

Não importa o nível de experiência que você tenha com programação, o segundo código é mais legível até por quem não é desenvolvedor. Isso porque o segundo código é muito mais expressivo e semântico.

Antes de continuarmos, vamos definir o que é expressividade e semântica.

Semântica:
É o estudo do significado. A semântica linguística estuda o significado usado por seres humanos para se expressarem através da linguagem.

Expressividade:
Claro, significativo, que dá a entender, enérgico.

Então se aplicarmos o conceito de semântica e expressividade nos nossos códigos, teremos códigos que são compreensíveis, claros e sem qualquer tipo de dupla interpretação.
Para conseguirmos um código expressivo e semântico precisamos ser críticos com o que escrevemos e temos sempre que fazer a seguinte pergunta: "será que outros entenderão o que escrevi?".

Ainda dentro do mesmo cenário (locadora), vamos analisar o seguinte código:

 public void executar(Locacao loc) {
     long dif = 0;
     
     if (loc.getDataEntrega().getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) {
      
      long hj = Calendar.getInstance().getTimeInMillis();
      long dtLoc = loc.getDataEntrega().getTimeInMillis();
      dif = (hj-dtLoc)/1000/60/60/24;
      
     }
     loc.setValorPago(loc.getFilme().getCategoria().getPreco() * (dif+1));
     
     filmes.add(loc.getFilme());
 }

O código acima não possui expressividade ou semântica alguma. Podemos notar isso claramente quando demoramos alguns minutos para saber que esse código está fazendo a devolução de um filme para a locadora.

Para tornar esse código mais expressivo e semântico, precisamos ser críticos com pequenos detalhes.

"executar" é algo muito genérico para o nome de um método, precisamos deixá-lo com um significado mais forte, algo que deixe claro qual é a verdadeira intenção do método. Vamos experimentar "efetuarDevolucao".

Muitos desenvolvedores estão acostumado a abreviar os nomes das variáveis. Nunca entendi essa prática, pensava que fosse por hábito ou alguma convenção, mas logo percebi que era por pura preguiça. Com todo o poder que as IDEs têm hoje em dia (auto-complete), não justifica abreviar os nomes das variáveis. Algumas pessoas podem até defender a Notação Húngara, mas eu acredito que se o nome for expressivo o suficiente chegaremos à mesma conclusão que a Notação Húngara propõe.
Vamos mudar "loc" para "locacao", "dif" para "diferencaDeDias", "dtLoc" para "dataLocacao" e "hj" para "hoje".

Vejamos como ficou nosso código depois dessas pequenas modificações:

public void efetuarDevolucao(Locacao locacao) {
     long diferencaDeDias = 0;
     
     if (locacao.getDataEntrega().getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) {
      
      long hoje = Calendar.getInstance().getTimeInMillis();
      long dataLocacao = locacao.getDataEntrega().getTimeInMillis();
      diferencaDeDias = (hoje-dataLocacao)/1000/60/60/24;
      
     }
     locacao.setValorPago(locacao.getFilme().getCategoria().getPreco() * (diferencaDeDias+1));
     
     filmes.add(locacao.getFilme());
 }

Bem melhor, mas ainda está longe do ideal. Vamos analisar o primeiro "if", que pega a data da entrega e verifica se a mesma é menor que a data de hoje. O que, em muitas palavras, está perguntando se a devolução está atrasada. Essa pergunta está relacionada diretamente ao estado da locação, então devemos fazer essa pergunta diretamente pra a locação. Vejamos o resultado:

public void efetuarDevolucao(Locacao locacao) {
     long diferencaDeDias = 0;
     
     if (locacao.estaAtrasada()) {
      
      long hoje = Calendar.getInstance().getTimeInMillis();
      long dataLocacao = locacao.getDataEntrega().getTimeInMillis();
      diferencaDeDias = (hoje-dataLocacao)/1000/60/60/24;
      
     }
     locacao.setValorPago(locacao.getFilme().getCategoria().getPreco() * (diferencaDeDias+1));
     
     filmes.add(locacao.getFilme());
 }

Repare que até aqui não houve mudança na estrutura, apenas modificações simples nos nomes dos métodos e variáveis e mesmo assim já conseguimos ter uma leitura muito melhor do código.

Robert C. Martin, autor do livro Agile Software Development, Principles, Patterns, and Practices e Clean Code (um dos melhores livros que já li), fala sobre um princípio muito importante: Single responsability principle. O princípio explica que um módulo deve ter uma única responsabilidade, e no livro ele descreve a "responsabilidade" como "um motivo para mudar", e cita o exemplo de um gerador de relatório. Esse gerador tem dois  motivos para mudar, primeiro, o conteúdo exibido, segundo, o layout. Esses dois motivos bem definidos são considerados motivos diferentes, portanto responsabilidades diferentes.
Com base nesse conceito, podemos perceber que o método "efetuarDevolucao" está com mais responsabilidades do que uma simples devolução. Para resolver isso temos que entender qual é o fluxo da devolução:
  1. Efetuar o pagamento
  2. Devolver o filme para o acervo.
Então devemos isolar a regra do pagamento na própria classe Locacao, resultando no seguinte código:

public void efetuarDevolucao(Locacao locacao) {
 locacao.efetuarPagamento();
 devolverFilmeParaOAcervo(locacao.getFilme());
}

private void devolverFilmeParaOAcervo(Filme filme) {
 filmes.add(filme);
}

Resolvemos a primeira parte, agora vamos ver como ficou a classe locação.

public void efetuarPagamento(){
  long diferencaDeDias = 0;
      
      if (dataEntrega.getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) {
       
       long hoje = Calendar.getInstance().getTimeInMillis();
       long dataLocacao = dataEntrega.getTimeInMillis();
       diferencaDeDias = (hoje-dataLocacao)/1000/60/60/24;
       
      }
      valorPago = filme.getCategoria().getPreco() * (diferencaDeDias+1);
 }

Bem, parece que apenas empurramos o problema pra dentro da classe Locacao. Ainda não está claro como o método efetuarPagamento funciona. Para melhorar esse código devemos seguir os mesmos passos anteriores. Veja que as variáveis já possuem nomes significativos, então vamos definir quais são os passos para efetuar o pagamento e identificarmos se o método está com mais responsabilidade do que deveria.
  1. Verificar se já está pago.
  2. Verificar se está atrasado.
  3. Se estiver atrasado, cobrar multa.
  4. Se não estiver atrasado, fazer cobrança normal.
Com um pouco de trabalho, chegamos ao seguinte resultado:

public class Locacao {
 
 private DateUtil util = new  DateUtil();
 
 public void efetuarPagamento() {
  if(!estaPago()){
   if (estaAtrasada()) {
    efetuarPagametnoAtrasado();
   }else{
    efetuarPagamentoPontual();
   }
  }
 }
 
 private boolean estaPago(){
  return valorPago > 0;
 }
 
 private boolean estaAtrasada() {
  return util.ehDepoisDeHoje(dataEntrega);
 }

 private void efetuarPagametnoAtrasado() {
  valorPago = filme.getCategoria().getPreco() * quantidadeDeDiasAtrasados();
 }
 
 private void efetuarPagamentoPontual() {
  valorPago = filme.getCategoria().getPreco();
 }

 private long quantidadeDeDiasAtrasados() {
  return util.diasEntre(dataEntrega, DateUtil.HOJE);
 }
}
Agora conseguimos entender facilmente o que o "efetuarPagamento" está fazendo. Para uma leitura rápida, não temos a necessidade de olhar o conteúdo de cada método, faremos isso apenas se realmente houver a necessidade de modificarmos algum deles especificamente.

O blog VidaGeek.net recentemente fez um post falando sobre expressividade e nele o autor tenta mostrar a importância da expressividade em contextos do mundo real, como o de um escritor.
O autor fala também que um escritor tem à sua disposição vários recursos dentro de sua ferramenta que é a linguagem. Cabe a cada escritor escolher qual recurso é mais recomendado no momento ou para o público alvo.

Discutir semântica não é uma tarefa fácil, talvez por ser algo subjetivo, o que é mais significativo pra mim talvez não seja para outra pessoa. Em termos técnicos, acredito que a melhor opção é tentar ser o mais simples e breve possível e lembrar sempre que um código legível não facilita a manutenção apenas para os outros, mas pra si mesmo também.


Referências:
Semantica
Separation of concern
Single Responsability
Cohesion

5 comentários:

  1. Legal Geraldo, e isso ai, código legível facilita a vida de todo mundo além tornar a programação uma coisa mais "humana". Tem muita gente que "quer ver funcionando" e ta valendo, sou totalmente contra. Codigo legivel gera inclusive uma API de mais facil utilizacao e ajuda a nao ter "codigo duplicado que faz a mesma coisa" hahaha Abraco!!! To no aguardo do proximo!

    ResponderExcluir
  2. Quem já programou com você sabe o valor que vc dá para os nomes de métodos e variáveis.
    No começo eu estranhava esse "costume" que você tinha em corrigir os nomes, mas depois comecei a perceber a diferença que isso faz.
    Gostei muito do post. Parabéns.

    ResponderExcluir
  3. Ficou muito bem escrito! parabéns Geraldo.

    ResponderExcluir
  4. Meus sinceros parabéns pelo post, ficou excelente. Está Claro e objetivo!

    Vou me esforçar para adaptar esse conceito no meu dia-a-dia.

    ResponderExcluir