[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!

Anúncios

4 comentários sobre “[Refatorando tudo!] Não retorne nulo. Nunca.

  1. Olá Marcos,

    Excelente post, só uma pergunta sobre a parte de exceção: você acredita que é melhor usar Exceptions ou RuntimeExceptions? Eu acho chato ter que ficar colocando try/catch ou throws no método, mas RuntimeExceptions podem gerar comportamentos inesperados.

    O html ferrou com os caracteres especiais na parte de histórico de compras nulo.

    • Oi Jonathan, essa parte das exceções é muito específica da linguagem. Eu não sou a pessoa mais indicada pra te falar qual delas é melhor em Java 🙂

      Mas se você acha chato ter que ficar colocando try/catch, imagine ter que lembrar de checar se a variável é nula ou não em todo lugar?

      E valeu pelo aviso ai do html, vou dar uma olhada!

  2. Augusto Romeiro

    Oi Marcos,
    Muito bom seu post, me fez pensar.
    Até entendo quando for retornar uma lista e faz todo o sentindo retornar uma lista vazia e não null.
    Mas e quando for um objeto com atributos que não são lista.

    Ex:
    Classe ContaBancaria e o atributo saldo que é um decimal, qualquer valor que eu retorne pode causar um problema, você vê alguma solução para este caso?

    Gosto bastante dos conteúdos que você posta aqui, são de excelente qualidade, parabéns!

    • Oi Augusto, obrigado pelo comentário!
      O exemplo da ContaBancaria faz total sentido, qualquer valor retornado vai causar problemas.
      A solução ai é lançar uma exceção, pois fica mais explícito que aquilo é um problema do que retornar um valor nulo. Nesse caso não faz sentido criar um NullObject, o lance é lançar uma exceção mesmo.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s