Java


O inferno de null

As tragédias gregas são famosas… O inferno de Dante também… Mas o que mais me aflige até hoje é o inferno de null. Estou eu lá programando bunitinhu e do nada, sem aviso, NullPointerException. Devido à problemas emocionais, daqui por diante usarei apenas NPE, pois esse erro é daqueles ‘que não se pode dizer o nome’…

O que é null?

Resposta: Nada! Simples assim. Mas e?????? Bem, o ‘pobrezinhu’ do null aparece sempre que se tem uma declaração de objeto mas nenhum objeto associado! Ou seja, tu reservou uma variável (pra colocar alguma coisa), mas como ainda não colocou, o Java (C#, php) define como null ou te obriga a declarar como algo e tu coloca como null. Aí começa o inferno.

Mas qual o problema?

Resposta normal: todo!

Resposta irônica: é só mais um if!

Como por padrão, todos usam null e o próprio compilador te faz esse “favor”, a gente tem que programar com o seguinte pensamento: o que eu estou recebendo pode ser null? E a resposta é quase sempre SIM! …… só respirando ….. me acalmei……

Bem, se cada vez que vou precisar do retorno de um método eu precisar checar se o retorno é null, quantos if ( foo == null) eu vou ter que ter? 1, 2, 3, …., 50, …, 100, …. 100000, ….. NaN! Deu pra entender…..

Além disso, o maior problema de todos: a NPE estoura onde tu recebe o null e não onde tu cria o null!!!!!! (só pra garantir a ênfase: !!!!!!!!!!!!!!!!!!!!)

Como posso ajudar a salvar o mundo?

Essa é a parte legal! Todos nós podemos ajudar a resolver o problema!

Exemplos:

Collections

Esse é o mais trivial. Primeiro a pergunta: o que é uma colection? Algum tipo de lista ou algo parecido. Pensemos em semântica (significado das coisas): se tenho uma lista sem itens, então eu tenho?

  • [ ] Nada
  • [ ] Lista vazia

Pense um pouco antes de responder! ….. ….. ….. ….. A resposta certa é …… ….. ….. ……. ….. UMA LISTA VAZIA!!!! Então eu me pergunto: porquê alguém devolveria null (nada) ao invés de uma lista vazia? Não tem sentido… mas pode ser por causa do uso histórico do null.

Observemos:

List<String> lista = new LinkedList<>();

List<String> retornaLista(){
    if(lista.isEmpty()){
       return null;
    }
    return lista;
}

Essa aí em cima doeu nos rins, mas é só pra dar a idéia do que me parece quando recebo null ao invés de uma lista vazia. Observer também que listas possuem um método pra verificar se a lista é vazia. O mais natural (e lógico) seria algo:

List<String> lista = new LinkedList<>();

List<String> retornaLista(){
    return lista;
}

E é isso que eu esperaria de qualquer método que me prometesse uma lista! Promessa é dívida (programação por contrato). Se o método promete, a API promete, tem que cumprir. Claro que dá pra colocar um adendo no contrato pra dizer que uma lista vazia vai ser retornada como null, mas fica a dúvia: pra quê?

Objetos

No caso de objetos o problema é um pouco mais difícil, mas o null é também mais aceitável!

Vou direto a um exemplo:


String nome; String getNome(){ return nome; }

O que temos acima é um simples getter. Como o getter retorna o valor da variável nome, que não foi explicitamente inicializada, o Java garante que nome = null! Então, se eu não colocar nada na variável nome, o método getNome retornará null.

Os casos de objetos nulos devem ser avaliados sempre de forma semântica, o que exige entender o problema antes de vomitar o código! (Desculpe o modo). Não adiata sair escrevendo código pra resolver um problema que eu não entendo e não tenho a solução dele em mente!

Voltando ao exemplo do nome: existe algum nome que seja igual a uma String vazia, ou seja, “” (o que está entre as aspas)? Acho meio difícil (impossível). Então, semanticamente, null singifica “”, e existe um método na classe String chamado isEmpty. Dessa forma, posso testar se um nome existe ou não somente quando precisar que tenha um nome em si. Afinal, esse nome pode ser o nome próprio que alguém deu para o seu carro…. desnecessário né (homenagem a uma estagiária)! Neste caso, seria totalmente opcional!!

Para objetos customizados (criados por você), você pode adicionar um método do tipo isNull ou isEmpty ouisValid que saberá quando o objeto representa algo nulo, vazio ou inválido. Assim fica mais fácil de depurar os erros, pois lembre-se: a NPE estoura onde tu recebe o null e não onde tu cria o null!!

Ahhh, mas a bilioteca|framework|API que eu uso me devolve nulo!

Como ela é “boba, feia, chata e má”…….

Então, crie uma classe responsável por fazer a ponte entre essa bilioteca|framework|API e te devolver algo não nulo e semânticamente equivalente!

Dica: dê uma olhada no post Java/JPA: evitando null no Mapeamento de Banco

Considerações finais

Minha opinião: eu não gosto de null, odeio depurar NullPointerException e adoraria que isso deixasse de existir. Mas o mundo é cruel conosco…..

Se todos nos unirmos, poderemos livrar o mundo de tamanho mal!

Ps 1.: Mande suas dúvidas, críticas, sugestões pra mim (meus dados de contato estão na opção “Contato”, no menu =D).

Ps 2.: Pode comentar também, o registro é obrigatório só pra evitar spans nos comentários……

Ao Infinito e Além!

Ps 3.: Desnecessário, né!


Java/JPA: evitando null no Mapeamento de Banco

Olá Pessoal

Várias vezes já passei por várias dificuldades no mapeamento de banco de dados em Java, usando tanto Hibernate puro como JPA. Os maiores problemas não são com o uso das anotações ou configurações em XML, pois estas estão documentadas. Os maiores problemas são com pequenos detalhes que podem sair causando NullPointerExceptions a varrer. E depois que apareceu o erro, fica difícil depurar, pois o maior problema desse tipo de exceção é que ela é disparada onde deveria haver um objeto não nulo, e não onde se deveria colocar o valor não nulo no objeto enviado. É quase o problema do ovo ou a galinha.

O problema do null

O Java tem suporte a referência nula, o famigerado null. Isso quer dizer que quando você escreve String str = null, você está dizendo guarda o nome str pra quando eu quiser uma String, mas não coloca nada lá ainda. O problema é que quando você for passar a variável str para algum método, ele terá que testar se o valor é null, caso contrário ele lançará uma NullPointerException quando tentar acessar qualquer método da classe String. O problema vai parecer ser o método que usa a variável, ao invés do método que criou a variável. O Java tem suporte a validação de parâmetros de métodos usando anotações (ex.: @NotNull), mas isso não impede de criar uma variável com valor nulo, só faz a validação dos parâmetros de entrada de um método em que a String vai ser usada no futuro.

Voltemos ao caso String str = null. Pense no seguinte: o que significa null? Em princípio, que a String str está vazia. Não seria o mesmo que String str = ""? Caso a resposta seja sim (e é sim!), ao invés de testar em tudo que é lugar se a String str é null, podemos simplesmente usar o método isEmpty().

Colocando agora com mais código:

// usando null
String str = null;

///

void metodoQueUsaString(String str){
    if(str != null) {
        // faz o processamento
    } else {
       // reporta algum tipo de erro, ou não faz nada
    }
}
// sem usar null
String str = "";

void metodoQueUsaString(String str){
        // faz o processamento
 }

Viu como é mais simples? (SIM!)

Sempre que possível, garanta um retorno não nulo!

O que isso tem com mapeamento de banco?

Simples! JPA/Hibernate retornam null sempre que não há resultado encontrado! E agora?

Primeiramente vamos avaliar o seguinte: sempre (quase sempre) que usamos JPA e Hibernate, usamos algum tipo de padrão, como o DAO, para encapsular as chamadas ao JPA/Hibernate e agrupar os métodos relevantes para cada tipo de entidade mapeada. Por exemplo:

class MinhaEntidadeDAO {

    public MinhaEntidade buscaPorId(Number id){
        // busca uma entidade no banco por id
    }

    public List<MinhaEntidade> listar(){
        // busca todas as entidades no banco
    }

    public void salvar(MinhaEntidade me){
        // salva/atualizar no banco
    }
}

O problema é que estamos tão acostumados com o null que não percebemos que ele permeia tudo e muitas vezes sem necessidade. Vejamos:

buscaPorId:
o método retorna a entidade encontrada ou null
listar:
o método retorna a lista de entidades, ou null

Para tudo!!!!

listar()

Primeiro: por que causa, motivo, razão ou circunstância retornar null no método listar ao invés de retornar uma lista vazia? Retornando uma lista vazia não teremos NullPointerException. Mas como saber se existe alguma coisa lá? Simples: isEmpty().

Imagine um método em que não importe quantos resultados você recupera, mas o que importa é que uma operação qualquer seja efetuada em todos os valores retornados. Por exemplo, tem um campo com valor 1 na minha tabela, e quero que todos passem para 2:


@Entity @Table(name = "minha_entidade") class MinhaEntidade { // mepeamento de id omitido por simplicidade int valor; int getValor(){ return valor; } void setValor(int valor){ this.valor = valor } }

Para mudar todos os valores de 1 para 2:


MinhaEntidadeDAO dao = new MinhaEntidadeDAO(); for(MinhaEntidade me: dao.listar()){ if(me.getValor() == 1){ me.setValor(2); dao.salvar(me); } }

Neste caso acima, se o méodo listar retornar null só porque não tem nenhum registro, então vou ter que adicionar uma checagem de nulo:

List<MinhaEntidade> lista = dao.listar();
if (lista != null){
     // código anterior
}

Teremos então um aumento de complexidade! Mas se ao invés de retornar nulo, eu retorna uma lista vazia, não preciso mais checar por nulo, e simplifico meu código.

Mas se eu precisar de uma lista NÃO vazia? Somente nestes casos testo com lista.isEmpty() e pronto!

List<MinhaEntidade> lista = dao.listar();
if (!lista.isEmpty()){
    // código que exige lista com pelo menos 1 elemento
}

buscaPorId(Number id)

Segundo: como resolver o problema de quando não encontrar um valor para uma busca por 1 elemento, como o buscaPorId, retornar algo diferente de null?

Podemos criar uma interface, por exemplo EntidadePersistida, com métodos comuns a todas as entidades:


interface EntidadePersistida { Number getId(); // minhas entidades sempre tem id numérica (int ou long) boolean isNull(); }

Ao implementar o mapeamento, devemos implementar a interface EntidadePersistida e preencher o método isNull com uma verificação de campos que indique que a entidade não está preenchida. Isso pode ser feito até por uma variável booleana!

class MinhaEntidade implements EntidadePersistida {
    // algumas omissões por simplicidade

    boolean nula = false;

    MinhaEntidade(boolean nula){
        this.nula = nula;
    }

    boolean isNull(){
        return nula;
    }
}

ou

class MinhaEntidade implements EntidadePersistida {
    // algumas omissões por simplicidade

    boolean isNull(){
        // codigo que avalia campos para ver se é nulo
        // ATENÇÃO: id da entidade é nula sempre que ela ainda não foi salva, o que não serve para o teste de isNull!!!!!
    }
}

Dessa forma, sempre que fosse ser retornado null para uma entidade, retornamos uma entidade que retorne true para o método isNull, evitando o NullPointerException.

Pode parecer mais trabalhoso criar assim, mas é muito pior tem que encontrar o maldito código que retorna nulo e não deveria, ou checar em tudo que é lugar se o maldito valor é null!!!!!!!

Vida longa e próspera!