Uma abordagem para evitar NullPointerException

Invocar métodos que podem ou não retornar o objeto esperado é algo bem comum no nosso dia-a-dia de programação. Abaixo segue um exemplo bem simples:

public class Agenda {

private Map contatos = new HashMap();

public void adiciona(Contato contato) {
contatos.put(contato.getEmail(), contato);
}

public Contato buscaPor(String email){
return contatos.get(email);
}
}

O método que realiza a busca retorna o contato encontrado ou, no meu caso, retorna null caso não encontre nada. Agora imagine que temos uma classe que faz uso dessa agenda. Por exemplo a que segue abaixo:

public class TesteAgenda {

public static void main(String[] args) {
Agenda agenda = new Agenda();
agenda.adiciona(new Contato("alberto","teste@teste.com.br"));

Contato contato = agenda.buscaPor("teste2@teste.com.br");
System.out.println(contato.getNome());
}
}

Mesmo sendo um exemplo muito simples, caimos num problema bem comum. O método buscaPor(email) vai retornar null porque não encontrou nada e o cliente que está usando o código toma o velho NullPointerException. No caso exposto, o código que utiliza este tipo de método, e eles se espalham que nem uma praga pelo sistema, começa a defender-se utilizando as velhas checagens com ifs para verificar se o objeto retornado é null ou não. Abaixo o exemplo:

if(contato!=null){
System.out.println(contato.getNome());
}
else{
System.out.println("Não encontrado");
}

O grande mal dessa abordagem é que você tem que ficar lembrando o tempo todo de ter que verificar isso. Exato, o problema não é colocar os ifs, if ou não, algum código vai ter de ser colocado para verificar isso.

Uma abordagem para tentar minimizar o problema da lembrança é usar o pattern Null Object, onde um objeto com semântica Nula é retornado para o invocador do método. É uma maneira um pouco mais elegante de resolver o problema.  Por exemplo, para o caso da agenda, poderia fazer uma classe chamada ContatoNaoEncontrado. O exemplo segue abaixo:

public class ContatoNaoEncontrado extends Contato {

@Override
public String getNome() {
return "Não encontrado";
}

@Override
public String getEmail() {
return "Não encontrado";
}

}

Fariamos uma alteração no método buscaPor(email) para que faça a verificação, ficando com ele da seguinte forma:

public Contato buscaPor(String email){
Contato contato = contatos.get(email);
if(contato!=null){
return contato;
}
return new ContatoNaoEncontrado();
}

E no código que utiliza este método podemos substituir os ifs pelo classico polimorfismo:

Contato contato = agenda.buscaPor("teste2@teste.com.br");
System.out.println(contato.getNome());
System.out.println(contato.getEmail());

Caso a checagem ainda seja necessária, pode-se adicionar um método na classe mãe indicando se foi encontrado ou não. Dessa forma o Null Object sempre retornaria false. Aplicando essa alteração ficamos com o seguinte código:

if(contato.foiAchado()){
System.out.println("Achamos seu contato... => "+contato.getNome());
}
else{
System.out.println("Não foi possivel...");
}

O ponto, um tanto quanto questionavel, é o fato de usar herança para implementar o Null Object. Poderia ser utilizado a mesma classe setando tudo para os valores apropriados, mas aí seria perdido a idéia da semântica do objeto nulo.

Pelo menos na minha opinião, não existe uma regra certinha para desenvolver, a que tento seguir é a de sempre deixar o código mais auto-explicativo possivel e de fácil manutenção. Caso você tenha apenas um lugar para fazer a checagem do objeto nulo, é claro que não precisa gastar seu tempo implementando isso. Agora, se essa checagem começar a se espalhar muito, essa abordagem pode salvar você de tomar uns NPE pelo caminho. No próximo post vou mostrar a mesma abordagem utilizando a linguagem Scala, a qual já possui implementado uma abordagem para resolver este mesmo problema.

Advertisements

7 thoughts on “Uma abordagem para evitar NullPointerException

  1. Muito boa a explicação, é algo tão simples, porém, muita gente não se atenta no desenvolvimento.

  2. pois bem, passei recentemente por esta situação e vi que poderia seguir por 3 caminhos:

    a) lançar uma XPTONotFoundException // que é feia as vezes
    b) a usa abordagem
    c) ainda na sua abordagem, nao vejo pq criar uma nova classe. Poderia simplesmente ter retornado um Contato com nome e email em branco. Poderia expor como

    class Contato{
    public static final Contato Null = new Contato(false);
    private /* ou public */ Contato(boolean encontrado){ ...}
    }

    uma vantagem dessa abordagem é ter apenas uma classe (a mesma pode ser até final) e vc definindo o equals vc pode fazer coisas como (nada impede de vc retornar um clone desse contatoNaoEncontrado). De qualquer forma podemos cair em uma quarta forma de resolver isto:

    d) Contato é uma interface. Uma das implementações pode ser um Contato “normal” e a outra pode ser um ContatoNaoEncontrado.

    ou ainda

    e) o metodo buscaPor pode retornar um objeto Result , que possui 2 metodos

    T get()
    boolean achou()

    O uso de um Result pode ser util se vc tem muitos objetos assim.

    e, por fim, poderiamos fazer

    f) o metodo buscarPor poderia receber um objeto default para retornar caso não encontre nada.

    Acho que uma ultima alternativa seria vc criar um proxy dinamico e retornar algo realmente inocuo, que retorne strings vazias ou coisas do genero (como um Stub). Não consigo imaginar onde vc gostaria de usar isto mas… se vc anotasse os metodos com algo do tipo @Default(valor=”not set”) e uma cacetada de classes pudesse fazer uso disso… talvez… mas tenho medo de fazer isso um dia.

    É bom termos umas alternativas na manga. As vezes o dominio do problema pode aceitar um usuario “Nulo” (no caso de um logger) mas outras vezes pode trazer problema. Nessas horas eu sinto falta de um Nullable em java e geralmente só checo null quando vem de objetos cujas classes são de terceiros.

    E NullPointerException é muito feio. Doi.

  3. Oi Tiago, entendo seu ponto. Poderia realmente ser retornado um objeto da classe Contato mesmo, mas preferi herdar para ficar bem claro que o “objeto nulo” tem que tá com a semântica definida. A abordagem do Result é uma boa e ficaria realmente mais genérico. Vou comentar sobre essa no próximo post, no qual vou pegar o mesmo problema e resolver em Scala.

  4. Com scala.Option fica muito mais elegante né?

    Pois é, o null cria esse tipo de situacao pra gente. Uma parte mais basica desse assunto é o que o Effective Java nos diz para nunca devolvermos null quando utilizar array e collections, sempre preferir new Tipo[0] e Collections.emptySet(), por exemplo.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s