Refatoração não é só mudar o código!

Você provavelmente já ouviu alguém falando em refatoração, mas será que a pessoa realmente estava falando de refatoração? Qual a diferença entre refatorar e apenas modificar o código? Se quiser ir mais afundo no tema e entender como Padrões de Projeto podem ajudar, leia o livro Refatorando com Padrões de Projeto.

É importante entender a diferença, apesar de parecer algo mais conceitual, pois a mentalidade ao refatorar o código é diferente de quando implementamos uma funcionalidade nova ou estamos corrigindo um problema.

Ao refatorar buscamos maneiras de (1) melhorar o design existente, (2) aplicando mudanças em pequenos passos e (3) evitando deixar o sistema quebrado. Vamos falar sobre essas três partes em mais detalhes.

Ao buscar maneiras de melhorar o design existente devemos pensar em novas soluções que podem melhorar o código (um pensamento bem diferente de quando estamos corrigindo um bug).

A segunda parte é muito importante pois ao aplicar mudanças em pequenos passos garantimos a consistência e ritmo de desenvolvimento. Quanto menos mudanças fizermos mais fácil é evitar problemas.

E a terceira parte é bem ligada a segunda pois, ao fazer mudanças pequenas, podemos executar todos os testes e validar o que está sendo feito. Se o tempo entre ter testes verdes for muito grande, é um sinal de que talvez as mudanças devam ser menores.

As técnicas de refatoração descritas por Martin Fowler seguem esse modelo pois descrevem um passo-a-passo onde aplicamos uma mudança pequena e executamos os testes até alcançar o objetivo final da refatoração.

Se você quer refatorar o código o primeiro passo deve ser identificar o code smell e qual ação quer tomar.

Digamos que você encontrou um método que tem muitos argumentos e quer pensar numa maneira de melhorá-lo. Você pode criar um novo objeto para armazenar esses dados e passar apenas esse objeto, outra opção seria quebrar o método e dividir a lógica e parâmetros, ou ainda mover alguns desses parâmetros para um construtor.

Existem várias opções ao refatorar um código então é muito importante entender o contexto do código e decidir o caminho antes de começar as modificações.

Em seguida vamos aplicar a refatoração, sempre lembrando de fazer mudanças pequenas e garantir que os testes passem. Se no meio de uma refatoração você encontrar uma oportunidade de corrigir um problema, anote em algum lugar e continue. Lembre-se que queremos diminuir a quantidade de mudanças.

Uma vez que a refatoração foi aplicada podemos criar um commit pois deixamos o sistema com todos os testes passando. Agora podemos voltar ao modo de corrigir bugs ou implementar funcionalidades.

Nos próximos posts vamos ver como Padrões de Projeto podem nos ajudar na hora de decidir o caminho das refatorações. Até lá.

Anúncios

[Refatorando tudo!] Não retorne nulo. Nunca.

Considere um exemplo bem simples: precisamos exibir a lista com as últimas compras que um usuário fez.

public HistoricoDeCompras buscaHistoricoDeCompras(String email) { ... }

Para complicar um pouco mais, imagine que existe um serviço cuja responsabilidade é gerenciar compras. Esse serviço expõe uma interface que retorna o histórico de compras de um usuário, dado o seu email.

public HistoricoDeCompras buscaHistoricoDeCompras(String email) {
  RespostaHttp resposta = servicoDeCompras.obterHistorico(email);
  if (resposta.getStatus().sucesso() ) {
    return converterParaHistoricoDeCompras(resposta.getConteudo());
  } else {
    return null;
  }
}

Esse código funciona bem, mas tem um grande problema (que pelo título do post você já deve ter percebido): ele nem sempre retorna o histórico de compras.

Não me obrigue a saber de coisas!

Como Rob Looby explica nesse post fantástico, existem milhares de coisas que nós desenvolvedores precisamos manter na cabeça: a estrutura do programa, o novo design que queremos seguir, boas práticas do time, regras de negócio etc. Os possíveis retornos de uma função/método não deveriam ser uma dessas coisas.

Para quem escreveu o código acima, retornar nulo pode parecer como uma boa decisão. Não tenho nenhum histórico de compras para exibir, então não retorno nada. Como se representa nada? Com um valor nulo.

Se você também acha que null é a melhor maneira de representar nada, sugiro assistir a apresentação da Sandi Metz, Nothing is Something (na verdade assista a palestra independente do que você acha, vale muito a pena :D).

Na grande maioria das vezes, quando retornamos nulo na verdade não queremos retornar um nulo, mas sim dizer algo mais. No exemplo acima queremos dizer que não encontramos nenhum histórico! O que é bem diferente de nulo.

O grande problema é que ao definir uma função que retorna um valor especial, seja ele nulo, 0, -1 ou qualquer outro número mágico, estamos assumindo que quem utilizar esse código vai saber e lembrar disso sempre que usar a nossa função. Por exemplo:

[1,2,3].index(2)
# => 1
[1,2,3].index(5)
# => nil

Em ruby, ao chamar o método index com um valor inexistente, nil é retornado. Dependendo do contexto, pode até fazer sentido já que nil é considerado falso em uma checagem de booleano. Mas de novo, obrigamos a pessoa que utiliza o método a lembrar disso, sempre!

Nada é alguma coisa

Ao invés de retornar um valor especial, podemos ser mais explícitos e diminuir a responsabilidade de quem vai usar nosso código. Considerando o exemplo inicial, vamos imaginar agora dois cenários:

  1. Dado o email de um usuário, quando nenhum histórico for encontrado, então uma mensagem de erro deve ser exibida na tela.
  2. Dado o email de um usuário, quando nenhum histórico for encontrado, então nenhuma informação deve ser exibida.

No primeiro cenário podemos melhorar o nosso código utilizando uma exceção:

public HistoricoDeCompras buscaHistoricoDeCompras(String email) {
  RespostaHttp resposta = servicoDeCompras.obterHistorico(email);
  if (resposta.getStatus().sucesso() ) {
    return converterParaHistoricoDeCompras(resposta.getConteudo());
  } else {
    throw new HistoricoNaoEncontrado("Histórico inexistenete para o email: " + email);
  }
}

Assim, a pessoa que utilizar o código vai saber que precisa tratar a exceção HistoricoNaoEncontrado e, seguindo o cenário, vai exibir a mensagem de error quando ela for lançada.

Já no segundo cenário, quando basta não exibir nenhum informação na tela, podemos utilizar o Padrão de Projeto Null Object. Um Null Object é simplesmente uma representação de um objeto que não possui nenhuma informação, mas que também não retorna nulo ou lança exceções.

Por exemplo, supondo que a classe HistoricoDeCompras possua uma lista de Compras, podemos criar um Null Object para ela que possua o mesmo tipo, através de herança, e que tenha uma lista de Compras válida, mas vazia:

public class HistoricoDeCompras {
  private List<Compras> compras;
  ...
}

public class HistoricoDeComprasNulo extends HistoricoDeCompras {
  private List<Compras> compras = new ArrayList();
  ...
}

Assim, ao retornar o HistoricoDeComprasNulo, não precisamos obrigar a pessoa a lembrar nenhum detalhe de implementação, o código continua funcional.

public HistoricoDeCompras buscaHistoricoDeCompras(String email) {
  RespostaHttp resposta = servicoDeCompras.obterHistorico(email);
  if (resposta.getStatus().sucesso() ) {
    return converterParaHistoricoDeCompras(resposta.getConteudo());
  } else {
    return new HistoricoDeComprasNulo();
  }
}

Em Scala, Java 8 e várias outras linguagens temos a nossa disposição valores opcionais. Com eles deixamos a cargo de quem utiliza o método lidar com a falta de informação, mas sem precisar retornar um valor mágico.

var helloWorld: Option[String] = Some("Hello World!")
println(helloWorld.getOrElse("Hello who?"))
// Hello World!
var helloWorld: Option[String] = None
println(helloWorld.getOrElse("Hello who?"))
// Hello who?

Valores opcionais permitem definir o que fazer quando o dado não está presente. No exemplo acima, em Scala, utilizamos Some para definir um valor válido e None para um valor inexistente. Ao utilizar o método getOrElse podemos definir o que será retornado caso nada seja encontrado.

Esse post do Rodrigo Turini explica com mais detalhes como utilizar valores opcionais no Java 8: http://blog.caelum.com.br/chega-de-nullpointerexception-trabalhe-com-o-java-util-optional/.

Pode parecer besteira ter essa trabalheira a mais só para não retornar null, mas acredite, o seu “eu do futuro” vai te agradecer se você parar de fazer isso!

Não se esqueça de conferir o links do post para se aprofundar mais no assunto.

Compartilhe o texto com seus colegas para evitar que o “eu do futuro” deles também sofra com esses problemas e continue acompanhando os posts da série [Refatorando tudo!] para melhorar ainda mais seu código!