Olá, pessoal. Queria discutir aqui sobre uma situação muito corriqueira em qualquer programação, que é a tentativa de manipular uma referência nula.
Quem nunca tomou um NullPointerException na cara?? Pois é, justamente por ser algo muito comum, acho que é bem importante nos atentarmos sobre como lidar com essas situações.
Suponha um método qualquer que recebe um objeto qualquer como paramêtro e faz algo com ele. Por exemplo:
public void algumaCoisa(Qualquer o)
{
o.metodo();
}
Algo realmente simples, que temos aos montes em qualquer projeto. Infelizmente, é possível que esse método receba null como parâmetro e, quando isso ocorrer, o maldito vai aparacer na hora! Claro que seria uma “burrice evitável” alguém chamar o método algumaCoisa passando null explicitamente como parâmetro, mas lembrem-se que a passagem de parâmetro pode ser dinâmica, e aí o erro fica mais difícil de detectar…
Qualquer q = null;
// Aqui tem um código que tenta setar a variável q, mas pode não conseguir.
algumaCoisa(q);
Existe alguma regra geral de programação defensiva? Em cada método que criarmos devemos checar nossos parâmetros? Ou jamais devemos setar variáveis com null? O que vocês acham dessa situação?
Uma outra situação igualmente comum e perigosa é a seguinte:
Um método tipo get que retorna um tipo específico pode simplesmente retornar o valor null. Essa possibilidade, se passar despercebida por quem invocar o método, vai levar a um NullPointer na hora. Exemplo:
public Ball getBall(int id)
{
Ball retorno = null;
retorno = BallDAO.recuperaBola(id); // Se não existir bola com esse id no banco, recuperaBola(int) retorna null.
return retorno;
}
E aí? É melhor retornar uma bola vazia do que null? Ou é melhor ainda sempre levantarmos uma exceção nessas horas?
Comos vocês fazem?
Nesse caso, realmente, é inútil testar se a é ou não nulo, pois o nullpointerexception e a exceção farão a mesma coisa. Mas nesse caso, não:
[code]
public void setA(String a) {
if (a == null) throw new IllegalArgumentException(“A is null!”);
this.a = a;
}[/code]
A diferença aqui é que o atributo a, do this, também irá conter o valor nulo. A exceção não ocorrerá nesse método, e sim na hora que outro método que depende de A for usado. Sem essa verificação, teremos muito mais dificuldade de chegar a origem do problema quando a exceção ocorrer.
O resto da página é realmente ótima. Eu acho a abordagem comentada um pouco purista demais, mas uma coisa é fato:
É muito importante pensarmos no contrato das classes, método por método.
Nesse caso, realmente, é inútil testar se a é ou não nulo, pois o nullpointerexception e a exceção farão a mesma coisa. Mas nesse caso, não:
[code]
public void setA(String a) {
if (a == null) throw new IllegalArgumentException(“A is null!”);
this.a = a;
}[/code]
A diferença aqui é que o atributo a, do this, também irá conter o valor nulo. A exceção não ocorrerá nesse método, e sim na hora que outro método que depende de A for usado. Sem essa verificação, teremos muito mais dificuldade de chegar a origem do problema.
O resto da página é realmente ótima. [/quote]
Aparentemente você não leu o texto por completo. Vou reproduzir abaixo a parte que você mencionou:
[quote]O grande problema é que geralmente as pessoas fazem assim:
[code]public static void fazerAlgo(String a){
if(a==null) throw IssoNaoEhUmaNullPointerExceptionException(“a deve ser definido”);
a.split(" ");
}[/code]
Ou seja: fazem a mesma coisa que uma NPE faz, mas usando IllegalArgumentException ou outra exceção qualquer. [/quote]
E é sempre bom manter o estado do seu objeto válido, certo? Se você possui um atributo nulo, onde ele não deveria ser nulo, o erro é seu, e não de quem invoca o método…
Li o texto todo e, me corrija se eu estiver errado, ele prega que não devemos hesitar em abusar do uso de exceções.
Acho que o trecho que resume isso é o seguinte: [i]“Quase sempre (na prática, diria que sempre) é melhor você interromper o processamento com uma exceção do que retornar um valor que não cumpre a pós-condição.”[/i]
Eu concordo que sempre fazer as checagens (respeitar o contrato) e usar as exceções é a forma mais robusta. Exceções servem justamente para lidarmos com esse tipo de situação, resultados inesperados, ERROS.
O que eu questiono é se vale a pena tomar isso como regra geral. Veja que os exemplos que eu citei são MUITO comuns. Fazer checagens de parâmetros o tempo todo, por exemplo, pode ser algo bem desgastante e, além disso, acredito que grande parte (senão a maioria) dos nosso métodos vai ter pelo menos uma cláusula throws. Será que vale a pena?
Acho que não tem muito o que discutir né… Como o bom senso é algo bem subjetivo, você tem que optar mesmo entre ter algo realmente robusto ou confiar nos clientes das suas classes…
E métodos private, eu geralmente prefiro usar asserções. Tem menos boilerplate e são muito menos desgastantes e desempenham o mesmo papel. Fora que, o tempo de execução delas é eliminado em runtime.
Será que é viajar demais se eu pensar que seria melhor que Java não permitisse passar valores nulos como parâmetros e, além disso, não permitisse que um método que deve retornar algum tipo qualquer retorne null?
Enfim, será que seria bom que referências a objetos fossem tratadas assim como os tipos primitivos nessas situações?
Verificar se uma referência qualquer pode não ter sido inicializada sempre pode ser feito em tempo de compilação.
Afinal, Java abstraiu a noção de ponteiros, embora tenha permanecido o maior perigo deles: Referências inválidas. Não acho logicamente limpo eu passar um valor nulo para um parâmetro que aceita um tipo específico como argumento. A referência nula não pode ser de quase todos os tipos que existem ao mesmo tempo! Percebam que só os tipos primitivos escapam disso. Por exemplo, não posso passar um parâmetro nulo para um método que recebe int! Não deveria ser feito o mesmo para os tipos criados? Isso abstrairia de vez a noção de ponteiros…
Claro que poderia ser feito algum tipo de palavra-chave que pudesse abrir explicitamente essa possibilidade, forçando o programador a levar em conta essa possibilidade.
Por exemplo, alguns atributos das classes muitas vezes não precisam assumir um valor padrão, inicializam-se vazias.
Acho que o importante é cercar em tempo de compilação as possibilidades desse tipo de erro. Será que é viajar demais? Seria um mundo muito melhor
[quote=leandrocm86]Será que é viajar demais se eu pensar que seria melhor que Java não permitisse passar valores nulos como parâmetros e, além disso, não permitisse que um método que deve retornar algum tipo qualquer retorne null?
Enfim, será que seria bom que referências a objetos fossem tratadas assim como os tipos primitivos nessas situações?
Verificar se uma referência qualquer pode não ter sido inicializada sempre pode ser feito em tempo de compilação.
Afinal, Java abstraiu a noção de ponteiros, embora tenha permanecido o maior perigo deles: Referências inválidas. Não acho logicamente limpo eu passar um valor nulo para um parâmetro que aceita um tipo específico como argumento. A referência nula não pode ser de quase todos os tipos que existem ao mesmo tempo! Percebam que só os tipos primitivos escapam disso. Por exemplo, não posso passar um parâmetro nulo para um método que recebe int! Não deveria ser feito o mesmo para os tipos criados? Isso abstrairia de vez a noção de ponteiros…[/quote]
Ai vc estaria escondendo o sol com a peneira.
Os efeitos desastrosos de um NullPointerException são evitados com estes unitarios, respeito a contrato e patterns adequados como Null Object (eleger um objeto para ser o “null” daquela classe).
Eu tenho um projeto (que uso como biblioteca) que evita NullPointerExceptions em classes com atributos encapsulados… (GET e SET)
Afinal ficar escrevendo a mesma coisa pra todos os atributos é um saco. Acredito que hoje a tarde eu irei criar um tópico para mostrar a minha idéia e tb irei disponibilizar os fontes…
Agora, não creio que esse problema seja tão grave assim para merecer tanta preocupação. O java eliminou a parte grave: dangling pointers não existem na linguagem.
[quote=ViniGodoy]As referências do C++ não permitem o null.
Agora, não creio que esse problema seja tão grave assim para merecer tanta preocupação. O java eliminou a parte grave: dangling pointers não existem na linguagem.[/quote]
As referências de C++ não permitem null? Tem muito tempo que não programo em C++, mas não lembro disso.
Pelo contrário, lembro que o erro mais frequente lá era o Segmentation Fault, que seria uma espécie de avô do NullPointerException.
De qualquer forma, acho que isso gera problema suficiente para ser chamado de grave, basta ver a frequência com que ocorre. Seria uma coisa a melhorar, na minha muito humilde opinião.
Talvez em ambientes muito profissionais com projetos de altíssima qualidade isso realmente não seja um problema grande, mas eu com meu limitado conhecimento não vejo nenhuma outra coisa em Java que possa ser uma fonte maior de erros.
[quote=leandrocm86][quote=ViniGodoy]As referências do C++ não permitem o null.
Agora, não creio que esse problema seja tão grave assim para merecer tanta preocupação. O java eliminou a parte grave: dangling pointers não existem na linguagem.[/quote]
As referências de C++ não permitem null? Tem muito tempo que não programo em C++, mas não lembro disso.
Pelo contrário, lembro que o erro mais frequente lá era o Segmentation Fault, que seria uma espécie de avô do NullPointerException.
De qualquer forma, acho que isso gera problema suficiente para ser chamado de grave, basta ver a frequência em que ocorre. Seria uma coisa a melhorar, na minha muito humilde opinião.
Talvez em ambientes muito profissionais com projetos de altíssima qualidade isso realmente não seja um problema grande, mas eu com meu limitado conhecimento não vejo nenhuma outra coisa em Java que possa ser uma fonte maior de erros.
[/quote]
Segmentation Fault é erro de acesso a uma localidade de memória que vc não tem permissão de acesso… Esse erro ocorre normalmente quando não se aloca espaço correto para um objeto…
E é verdade que C++ não permite referência null, um ponteiro ‘null’ aponta para o endereço de memória 0.
Então… sei que já tá meio fugindo do escopo do assunto, mas agora fiquei curioso… rsrs
Compilei sem problemas o seguinte código em C++, usando Cygwin (com g++).
class Aluno
{
private:
int matricula;
public:
int getMatricula()
{
return matricula;
}
};
int main()
{
Aluno *a = 0;
cout << a -> getMatricula() << endl;
return 0;
}
Ele compila sem problema nenhum. Assim como Java, o erro só apareceu em tempo de execução, por tentar chamar um método através de um apontador nulo. Por sinal, foi um Segmentation Fault. Se o código acima fosse traduzido para Java, ocorreria a mesma coisa, só que com um NullPointerException, não?
[quote=leandrocm86]Então… sei que já tá meio fugindo do escopo do assunto, mas agora fiquei curioso… rsrs
Compilei sem problemas o seguinte código em C++, usando Cygwin (com g++).
class Aluno
{
private:
int matricula;
public:
int getMatricula()
{
return matricula;
}
};
int main()
{
Aluno *a = 0;
cout << a -> getMatricula() << endl;
return 0;
}
Ele compila sem problema nenhum. Assim como Java, o erro só apareceu em tempo de execução, por tentar chamar um método através de um apontador nulo. Por sinal, foi um Segmentation Fault. Se o código acima fosse traduzido para Java, ocorreria a mesma coisa, só que com um NullPointerException, não?[/quote]
Sim, mas perceba que a semântica do erro é diferente. Segmentation Fault é um acesso ilegal à memória, pode acontecer em várias ocasiões, por exemplo acessar uma posição de um array que não foi alocada. Já um NPE significa que você tentou enviar uma mensagem a um objeto que não existe.
[quote=leandrocm86]
Existe alguma regra geral de programação defensiva? Em cada método que criarmos devemos checar nossos parâmetros?
Ou jamais devemos setar variáveis com null? O que vocês acham dessa situação? [/quote]
Temos várias formas de programação defensiva. Programação defensiva, por definição, é passiva (não ha ifs).
Existe práticas que evitam o problema do null, mas não o contornam. Isso é simples programação, não programação defensiva.
O exemplo do set que passa null : às vezes null é um valor válido e o set tem que testar pro null e fazer diferente quando é null.
Mas isso está relacionado ao contrato do objeto e não a programação defensiva. Repare que a exeção é IllegalArgumentException ?
Porque "Illegal’ ? Porque embora sendo um valor possivel, null, não é aceitável segundo as regras do método. Violação de Regras => Ilegal.
Programação defensiva acontece quando vc escreve codigo que funciona mesmo quando a referencia é nula. Por exemplo:
"a".equals(b);
é programação defensiva ( funciona com null e sem ifs). Repare que é diferente de
b.equals("a");
que para funcionar seria
if( b!=null) {b.equals("a")};
Quando vc usa o if vc não está de defendendo, vc está atuando para comprovar e decidir o que fazer depois.