10 de mar. de 2010

Refactorings: Extrair método e substituir variável temporária por consulta

Salve todos,

Como desenvolvedor, penso que uma das competências necessárias é saber refatorar o código. Isto se aprende com o tempo, e ler ajuda bastante, li o livroRefatoração: Aperfeiçoando o projeto de código existente, do Martin, e ele é muito bom, claro e coeso, demonstrando algumas refatorações consolidadas.


Então, como tenho um tempo livre, resolvi compartilhar algumas partes e expor para discussão aqui na lista.

Primeiro, quando refatorar?

Pode ser ...

- Quando corrige um erro;
- Quando se está adicionando uma nova funcionalidade;
- Quando está se revisando o código;

Ou, como diz Martin, na edição de seu código, na terceira vez que tiver que mexer em algo, refatore.

Segundo, é necessário refatorar?

Depende, Kent Beck diz que deve-se refatorar quando detectar mau cheiro no código. Mau cheiro, não quer dizer que algo não funcione, mas sim aquele código em que tu olhas atravessado e pensa "Dá para melhorar isso".

Abaixo, uma lista resumida de alguns maus cheiros clássicos:

- Nomes obscuros de variáveis;
- Código duplicado;
- Método muito longo;
- Classe muito longa;
- Muitos parâmetros no método;
- Construtor com nulos;
- Muitas variáveis de instância;
- Campos temporários;
- Comentários no código (a.k.a. "desodorante")

Um dos princípios da refatoração é deixar o código legível e por consequência mais manutenível. Uma refatoração que aplico com frequência, é o extrair método, que uso para substituir comentários ou reusar parte de um método para outros métodos, e alterar o nome de variáveis.

Inventei uma classe 'mock' (hehehe), por exemplo:

class PagamentoService {

   private ContabilService cs;

   public void encaminhaPagamentosCartao(Integer id) {

      if (id == null || id <= 0) throw new IllegalArgumentException("ID invalido");

      Venda e = em.find(Venda.class, id);

      if (venda == null) throw new BusinessException("Venda nao encontrada para o ID:" + id);

      //Lista de pagamentos      
      List pc = new ArrayList();

      for (Item i : e.itens) {
         boolean quitado = true;
         for (Pagamento p : i.pagamentos) {
            if (!p.faturado) quitado = false;
         }
         if (quitado) {
            for (Pagamento p : i.pagamentos) {
               if (p.isCartao) {
                  pc.add(p);
               }
            }
         }
      }

      for (Pagamento p : pc) {
         cs.send(p);
      }

   }

}

Para ficar mais legível, poderíamos deixá-la assim:


class PagamentoService {

   private ContabilService contabilService;

   public void encaminhaPagamentosCartao(Integer idVenda) {

      validaParametro(idVenda);
      
      Venda venda = localizaVenda(idVenda);

      enviaPagamentoParaContabilidade(venda);      

   }

   private void validaParametro(Integer idVenda) {
      if (idVenda == null || idVenda <= 0) throw new IllegalArgumentException("ID da Venda é invalido");
   }

   private Venda localizaVenda(Integet idVenda) {
      Venda e = em.find(Venda.class, id);

      

      if (venda == null) throw new BusinessException("Venda nao encontrada para o ID:" + id);
   }

   private void enviaPagamentoParaContabilidade(Venda venda) {

      for (Item item : venda.itens) {
         if (estaQuitado(item)) {
            for (Pagamento pagamento : item.pagamentos) {
               if (pagamento.isCartao) {
                  enviaParaContabilidade(pagamento);
               }
            }
         }
      }
   }

   private boolean estaQuitado(Item item) {      

      for (Pagamento pagamento : item.pagamentos) {
         if (!pagamento.faturado) return false;
      }
      return true;
   }

   private void enviaPagamento(Pagamento pagamento) {
      contabilService.send(pagamento);
   }

}

Geralmente a refatoração adiciona indireção, obviamente, mas vai ao encontro do paradigma orientado a objetos, que prega a utilização de pequenas partes reutilizáveis de código, sempre pensando no futuro. Neste exemplo, os comentários foram substituídos por métodos  (extrair método) que falam por si, as variáveis foram renomeadas e uma delas trocada por uma consulta (substituir variável temporária por consulta).

Como benefício, futuros métodos públicos podem utilizar os métodos privados para operações semelhantes. 

O difícil é saber até onde refatorar, dividir em pequenas peças, mas isto vem com a experiência. Após acostumar-se a refatorar, passa a não se ter mais medo de alterar o código, mas deve ser feito com cuidado para não introduzir bug's, e aí entra a importância dos testes. Geralmente a refatoração é associada com alguma prática de TDD.

Em outro tópico lanço outro estudo de caso e refatorações.

Para os analistas e DBA's de plantão, sugiro este ótimo livro: http://www.agiledata.org/essays/databaseRefactoring.html
Li alguns pequenos trechos, já que estou mais ativo no código, e acho que vale a pena.

Nenhum comentário: