É melhor retornar valores NULL ou vazios de funções / métodos onde o valor de retorno não está presente?

127

Estou procurando uma recomendação aqui. Eu estou lutando com se é melhor retornar NULL ou um valor vazio de um método quando o valor de retorno não está presente ou não pode ser determinado.

Use os dois métodos a seguir como exemplos:

string ReverseString(string stringToReverse) // takes a string and reverses it.
Person FindPerson(int personID)    // finds a Person with a matching personID.

Em ReverseString() , eu diria retornar uma string vazia, porque o tipo de retorno é string, então o chamador está esperando isso. Além disso, dessa forma, o chamador não precisaria verificar se um NULL foi retornado.

Em FindPerson() , retornar NULL parece ser um ajuste melhor. Independentemente de haver ou não NULL ou um objeto Person vazio ( new Person() ) retornado, o chamador precisará verificar se o Objeto da pessoa é NULL ou vazio antes de fazer qualquer coisa (como chamar UpdateName() ). Então, por que não apenas retornar NULL aqui e, em seguida, o chamador só precisa verificar NULL.

Alguém mais luta com isso? Qualquer ajuda ou insight é apreciado.

    
por P B 17.11.2011 / 19:34
fonte

17 respostas

70

O StackOverflow tem uma boa discussão sobre esse tópico exato neste P & A Na questão mais bem avaliada, kronoz observa:

Returning null is usually the best idea if you intend to indicate that no data is available.

An empty object implies data has been returned, whereas returning null clearly indicates that nothing has been returned.

Additionally, returning a null will result in a null exception if you attempt to access members in the object, which can be useful for highlighting buggy code - attempting to access a member of nothing makes no sense. Accessing members of an empty object will not fail meaning bugs can go undiscovered.

Pessoalmente, gosto de retornar strings vazias para funções que retornam strings para minimizar a quantidade de tratamento de erros que precisa ser colocada em prática. No entanto, você precisa ter certeza de que o grupo com quem você está trabalhando seguirá a mesma convenção - caso contrário, os benefícios dessa decisão não serão alcançados.

No entanto, como o pôster na resposta do SO observou, os valores nulos provavelmente devem ser retornados se um objeto for esperado, de modo que não haja dúvidas sobre se os dados estão sendo retornados.

No final, não há melhor maneira de fazer as coisas. Construir um consenso de equipe irá, em última instância, direcionar as melhores práticas de sua equipe.

    
por 17.11.2011 / 19:42
fonte
77

Em todo o código que escrevo, evito retornar null de uma função. Eu li isso em Clean Code .

O problema com o uso de null é que a pessoa que usa a interface não sabe se null é um resultado possível e se precisa verificar isso, porque não há nenhum tipo de referência not null .

Em F # você pode retornar um tipo option , que pode ser some(Person) ou none , então é óbvio para o chamador que eles precisam verificar.

O padrão C # (anti-) análogo é o método Try... :

public bool TryFindPerson(int personId, out Person result);

Agora eu sei que as pessoas disseram que odeiam o padrão Try... porque ter um parâmetro de saída quebra as ideias de uma função pura, mas não é diferente de:

class FindResult<T>
{
   public FindResult(bool found, T result)
   {
       this.Found = found;
       this.Result = result;
   }

   public bool Found { get; private set; }
   // Only valid if Found is true
   public T Result { get; private set;
}

public FindResult<Person> FindPerson(int personId);

... e para ser honesto, você pode assumir que todo programador .NET sabe sobre o padrão Try... porque é usado internamente pelo .NET framework. Isso significa que eles não precisam ler a documentação para entender o que ela faz, o que é mais importante para mim do que manter a visão de funções de alguns puristas (entendendo que result é um parâmetro out , não um parâmetro ref ).

Então eu vou com TryFindPerson porque você parece indicar que é perfeitamente normal não conseguir encontrá-lo.

Se, por outro lado, não houver uma razão lógica para o chamador fornecer um personId que não existia, provavelmente o faria:

public Person GetPerson(int personId);

... e depois eu lançaria uma exceção se fosse inválida. O prefixo Get... implica que o chamador saiba que deve ser bem-sucedido.

    
por 17.11.2011 / 19:58
fonte
28

Você pode testar o padrão de Caso Especial de Martin Fowler, em Paternos da Arquitetura de Aplicativos Corporativos :

Nulls are awkward things in object-oriented programs because they defeat polymorphism. Usually you can invoke foo freely on a variable reference of a given type without worrying about whether the item is the exact type or a sub-class. With a strongly typed language you can even have the compiler check that the call is correct. However, since a variable can contain null, you may run into a runtime error by invoking a message on null, which will get you a nice, friendly stack trace.

If it's possible for a variable to be null, you have to remember to surround it with null test code so you'll do the right thing if a null is present. Often the right thing is same in many contexts, so you end up writing similar code in lots of places - committing the sin of code duplication.

Nulls are a common example of such problems and others crop up regularly. In number systems you have to deal with infinity, which has special rules for things like addition that break the usual invariants of real numbers. One of my earliest experiences in business software was with a utility customer who wasn't fully known, referred to as "occupant." All of these imply altering the usual behavior of the type.

Instead of returning null, or some odd value, return a Special Case that has the same interface as what the caller expects.

    
por 17.11.2011 / 19:48
fonte
14

Eu acho que ReverseString() retornaria a string invertida e ela lançaria um IllegalArgumentException se passado em Null .

Acho que FindPerson() deve seguir o Padrão NullObject ou gerar uma exceção não verificada sobre não encontrar algo se você sempre deve ser capaz de encontrar alguma coisa.

Ter que lidar com Null é algo que deve ser evitado. Ter Null em idiomas foi chamado de Erro de bilhões de dólares pelo inventor!

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W).

My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement.

This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

In recent years, a number of program analysers like PREfix and PREfast in Microsoft have been used to check references, and give warnings if there is a risk they may be non-null. More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965.

Tony Hoare

    
por 17.11.2011 / 21:24
fonte
11

Retorne uma Opção . Todos os benefícios de retornar um valor inválido diferente (como poder ter valores vazios em suas coleções) sem qualquer risco de NullPointerException.

    
por 18.11.2011 / 05:10
fonte
7

Eu vejo os dois lados desse argumento, e percebo que algumas vozes bastante influentes (por exemplo, Fowler) defendem não retornar nulos para manter o código limpo, evitar blocos extras de tratamento de erros, etc.

No entanto, tenho a tendência de concordar com os proponentes de retornar null. Eu acho que há uma distinção importante em invocar um método e ele está respondendo com Eu não tenho nenhum dado e ele está respondendo com Eu tenho essa String vazia .

Desde que vi algumas das discussões referenciando uma classe Person, considere o cenário em que você tenta procurar uma instância da classe. Se você passar em algum atributo localizador (por exemplo, um ID), um cliente poderá verificar imediatamente a existência de null para ver se nenhum valor foi encontrado. Isso não é necessariamente excepcional (portanto, não precisa de exceções), mas também deve ser documentado claramente. Sim, isso requer algum rigor por parte do cliente, e não, eu não acho que seja uma coisa ruim.

Agora considere a alternativa em que você retorna um objeto Person válido ... que não tem nada nele. Você coloca valores nulos em todos os seus valores (nome, endereço, favoriteDrink) ou agora preenche aqueles com objetos válidos, mas vazios? Como seu cliente agora determina que nenhuma Pessoa real foi encontrada? Eles precisam verificar se o nome é uma String vazia em vez de null? Esse tipo de coisa, na verdade, não levará tanto ou mais clutter de código e declarações condicionais do que se tivéssemos verificado a existência de null e movido?

Novamente, há pontos em ambos os lados deste argumento que eu poderia concordar, mas acho que isso faz mais sentido para a maioria das pessoas (tornando o código mais sustentável).

    
por 17.11.2011 / 22:12
fonte
6

Retorna null se você precisar saber se o item existe ou não. Caso contrário, retorne o tipo de dados esperado. Isso é especialmente verdadeiro se você estiver retornando uma lista de itens. Geralmente é seguro assumir se o chamador está querendo uma lista, eles vão querer iterar sobre a lista. Muitas (a maioria - todas?) Linguagens falham se você tentar iterar sobre null, mas não quando você iterar sobre uma lista que está vazia.

Acho frustrante tentar usar código como esse, apenas para que ele falhe:

for thing in get_list_of_things() {
    do_something_clever(thing)
}

Eu não deveria ter que adicionar um caso especial para o caso quando não há coisas .

    
por 17.11.2011 / 19:43
fonte
5

Depende da semântica do método. Você poderia especificar, seu método aceita apenas "Não nulo". Em Java, você pode declarar isso usando algumas anotações de metadados:

string ReverseString(@NotNull String stringToReverse)

Você deve, de alguma forma, especificar o valor de retorno. Se você declara, você aceita apenas valores NotNull, você está obrigado a retornar uma string vazia (se a entrada for uma string vazia também).

O segundo caso é um pouco complicado em sua semântica. Se este método retornar alguma pessoa por sua chave primária (e se espera que a pessoa exista), a melhor maneira é lançar uma exceção.

Person FindPerson(int personID)

Se você pesquisar alguém por algum ID (que me parece estranho), é melhor declarar esse método como @Nullable.

    
por 17.11.2011 / 19:44
fonte
3

Eu recomendaria usar o padrão Null Object sempre que possível. Ele simplifica o código ao chamar seus métodos e você não consegue ler código feio como

if (someObject != null && someObject.someMethod () != whateverValue)

Por exemplo, se um método retornar uma coleção de objetos que se encaixam em algum padrão, retornar uma coleção vazia faz mais sentido do que retornar null e a iteração nessa coleção vazia praticamente não terá nenhuma penalidade de desempenho. Outro caso seria um método que retorna a instância de classe usada para registrar dados, retornando um objeto Nulo em vez de null é preferível na minha opinião, já que não força os usuários a sempre verificar se a referência retornada é null . / p>

Nos casos em que retornar null faz sentido (chamando o método findPerson () , por exemplo), tentaria pelo menos fornecer um método que retorna se o objeto estiver presente ( personExists (int personId) , por exemplo), outro exemplo seria o método containsKey () em um Map em Java). Ele torna o código dos chamadores mais limpo, pois você pode facilmente ver que existe a possibilidade de que o objeto desejado não esteja disponível (a pessoa não existe, a chave não está presente no mapa). Verificar constantemente se uma referência é null ofusca o código na minha opinião.

    
por 17.11.2011 / 23:49
fonte
3

null é a melhor coisa para retornar se e somente se as seguintes condições se aplicarem:

  • o resultado nulo é esperado na operação normal . Pode-se esperar que você não consiga encontrar uma pessoa em algumas circunstâncias razoáveis, portanto findPerson () retornando null é bom. No entanto, se for uma falha genuinamente inesperada (por exemplo, em uma função chamada saveMyImportantData ()), você deverá lançar uma exceção. Há uma sugestão no nome - exceções são para circunstâncias excepcionais!
  • null é usado para indicar "não encontrado / sem valor" . Se você quer dizer outra coisa, então devolva outra coisa! Bons exemplos seriam operações de ponto flutuante que retornam Infinity ou NaN - estes são valores com significados específicos, então ficaria muito confuso se você retornasse null aqui.
  • A função é destinada a retornar um único valor , como findPerson (). Se foi concebido para devolver uma coleção, por ex. findAllOldPeople (), então uma coleção vazia é a melhor. Um corolário disso é que uma função que retorna uma coleção nunca deve retornar null .

Além disso, certifique-se de documentar o fato de que a função pode retornar nulo.

Se você seguir essas regras, os nulos serão praticamente inofensivos. Observe que, se você esquecer de verificar null, normalmente obterá uma NullPointerException imediatamente depois, o que geralmente é um bug bastante fácil de corrigir. Essa abordagem de falha rápida é muito melhor do que ter um valor de retorno falso (por exemplo, uma string vazia) que é propagada discretamente pelo sistema, possivelmente corrompendo dados, sem gerar uma exceção.

Finalmente, se você aplicar essas regras às duas funções listadas na pergunta:

  • FindPerson - seria apropriado para essa função retornar null se a pessoa não fosse encontrada
  • ReverseString - parece que nunca falharia em encontrar um resultado se passasse uma string (já que todas as strings podem ser revertidas, incluindo a string vazia). Portanto, nunca deve retornar null. Se algo der errado (falta de memória?), Então deve lançar uma exceção.
por 12.01.2012 / 06:59
fonte
1

Na minha opinião, há uma diferença entre retornar NULL, retornar algum resultado vazio (por exemplo, a string vazia ou uma lista vazia) e lançar uma exceção.

Eu normalmente adoto a seguinte abordagem. Eu considero uma função ou método f (v1, ..., vn) chamada como a aplicação de uma função

f : S x T1 x ... x Tn -> T

onde S é o "estado do mundo" T1, ..., Tn são os tipos de parâmetros de entrada, e T é o tipo de retorno.

Eu primeiro tento definir essa função. Se a função é parcial (ou seja, existem alguns valores de entrada para os quais ela não está definida), eu retorno NULL para sinalizar isso. Isso porque eu quero que a computação termine normalmente e me diga que a função que solicitei não está definida nas entradas dadas. Usar, por exemplo, uma string vazia como valor de retorno é ambíguo porque pode ser que a função seja definida nas entradas e a string vazia seja o resultado correto.

Acho que a verificação extra para um ponteiro NULL no código de chamada é necessária porque você está aplicando uma função parcial e é a tarefa do método chamado informar se a função não está definida para a entrada dada.

Eu prefiro usar exceções para erros que não permitem realizar o cálculo (ou seja, não foi possível encontrar nenhuma resposta).

Por exemplo, suponha que eu tenha uma classe Customer e queira implementar um método

Customer findCustomer(String customerCode)

para procurar um cliente no banco de dados do aplicativo por seu código. Nesse método, eu faria

  • Retornar um objeto da classe Customer se a consulta for bem-sucedida,
  • Retorna null se a consulta não encontrar nenhum cliente.
  • Lança uma exceção se não for possível se conectar ao banco de dados.

As verificações extras para null, por exemplo

Customer customer = findCustomer("...");
if (customer != null && customer.getOrders() > 0)
{
    ...
}

fazem parte da semântica do que estou fazendo e eu não apenas "pulá-los" para tornar o código melhor. Eu não acho que seja uma boa prática simplificar a semântica do problema em questão apenas para simplificar o código.

Naturalmente, como a verificação de null ocorre com muita frequência, é bom que o idioma suporte alguma sintaxe especial para ele.

Eu também consideraria usar o padrão Null Object (como sugerido por Laf), desde que eu possa distinguir o objeto nulo de uma classe de todos os outros objetos.

    
por 18.11.2011 / 13:56
fonte
0

Os métodos que retornam coleções devem retornar vazios, mas outros podem retornar null, porque para as coleções pode acontecer que você tenha objetos, mas não há elementos, portanto, o chamador validará apenas o tamanho, em vez de ambos.

    
por 18.11.2011 / 10:50
fonte
0

Para mim, existem dois casos aqui. Se você está retornando algum tipo de lista, você deve sempre retornar a lista, não importa o que, vazio, se você não tem nada para colocar nele.

O único caso em que vejo qualquer debate é quando você está retornando um único item. Eu me vejo preferindo retornar null em caso de falha em tal situação com base em fazer as coisas falharem rapidamente.

Se você retornar um objeto nulo e o chamador precisar de um objeto real, ele poderá ir em frente e tentar usar o objeto nulo produzindo um comportamento inesperado. Eu acho que é melhor para a rotina crescer se eles esquecerem de lidar com a situação. Se algo está errado, eu quero uma exceção o mais rápido possível. Você tem menos chances de enviar um bug dessa maneira.

    
por 05.05.2012 / 22:37
fonte
0

Adicionando ao que as pessoas já disseram sobre o construtor de Maybe type:

Outro grande ganho, além de não arriscar NPEs, é o semântico. No mundo funcional, onde lidamos com funções puras, gostamos de tentar criar funções que, quando aplicadas, sejam exatamente equivalentes ao seu valor de retorno. Considere o caso de String.reverse() : esta é uma função que, dada uma certa string, representa a versão invertida dessa string. Sabemos que essa string existe, porque cada string pode ser invertida (strings são basicamente conjuntos ordenados de caracteres).

Agora, e quanto a findCustomer(int id) ? Esta função representa o cliente que possui o ID fornecido. Isso é algo que pode ou não existir. Mas lembre-se, funções têm um único valor de retorno, então você não pode dizer "essa função retorna um cliente com o ID dado OR ele retorna null .

Este é o meu principal problema com o retorno de null e a devolução de um objeto nulo. Eles são enganosos. null não é um cliente e também não é "a ausência de um cliente". É a ausência de qualquer coisa. É apenas um ponteiro sem nome para NADA. Muito baixo nível e muito feio e muito sujeito a erros. Um objeto nulo também é muito enganador, eu acho. Um cliente nulo é um cliente. Não é a ausência de um, não é o cliente com o ID solicitado, então retornar é simplesmente ERRADO. Este NÃO é o cliente que pedi, mas você ainda afirma ter devolvido um cliente. Tem o ID errado, claramente isso é um bug. Ele quebra o contrato sugerido pelo nome e assinatura do método. Requer que o código do cliente assuma as coisas sobre seu design ou leia a documentação.

Ambos os problemas são resolvidos por Maybe Customer . De repente, não estamos dizendo "essa função representa o cliente com o ID fornecido". Estamos dizendo "essa função representa o cliente com o ID fornecido, se existir. Agora é muito claro e explícito sobre o que faz. Se você pedir ao cliente um ID inexistente, vai receber Nothing .Como isso é melhor que null ? Não apenas para o risco do NPE.Mas também porque o tipo de Nothing não é apenas MaybeMaybe Customer .Tem significado semântico. especificamente significa "a ausência de um cliente", indicando que tal cliente não existe.

Outro problema com null é claro que é ambíguo. É porque você não encontrou o cliente ou houve um erro de conexão do banco de dados ou simplesmente não posso ver esse cliente?

Se você tiver vários casos de erro como este para manipular, você pode lançar exceções para essas versões ou (e eu prefiro assim) você pode retornar um Either CustomerLoadError Customer , representando algo que deu errado ou o cliente retornado. Ele tem todas as vantagens de Maybe Customer e também permite especificar o que pode dar errado.

A principal coisa que eu quero é capturar o contrato da função em sua assinatura. Não assuma as coisas, não confie em convenções fugazes, seja explícito.

    
por 29.05.2016 / 08:02
fonte
0

1) Se a função semântica é que ela não pode retornar nada, o chamador deve testá-la, caso contrário ele irá, por exemplo. pegue seu dinheiro e não dê para ninguém.

2) É bom fazer funções que semanticamente sempre retornem algo (ou lance).

Com um logger , faz sentido retornar o logger que não registra se nenhum logger foi definido. Ao retornar a coleção, quase nunca faz sentido (exceto em "baixo nível suficiente", onde a própria coleção é os dados, não o que ele contém) para retornar nada, porque um conjunto vazio é um conjunto, não nada.

Com um exemplo person , eu iria "hibernar" (get vs load, IIRC, mas lá é complicado por objetos proxy para carregamento lento) de ter duas funções, uma que retorna null e segundo que lança.

    
por 18.11.2011 / 10:30
fonte
-1
  • NULL deve ser retornado se o aplicativo esperar que os dados estejam disponíveis, mas os dados não estejam disponíveis. Por exemplo, um serviço que retorna o CITY com base no CEP deve retornar null se a cidade não for encontrada. O chamador pode então decidir manipular o nulo ou explodir.

  • A lista vazia deve ser retornada se houver duas possibilidades. Dados dados disponíveis ou NÃO disponíveis. Por exemplo, um serviço que retorna CITY (s) se a população for maior que determinado número. Pode retornar uma lista vazia se Nenhum dado satisfizer os critérios fornecidos.

por 04.05.2012 / 17:40
fonte
-2

Retornar NULL é um design terrível , no mundo orientado a objetos. Em poucas palavras, o uso de NULL leva a:

  • tratamento de erros ad-hoc (em vez de exceções)
  • semântica ambígua
  • lento em vez de falhar rapidamente
  • pensamento de computador vs. pensamento de objeto
  • objetos mutáveis e incompletos

Confira esta postagem do blog para uma explicação detalhada: link

    
por 13.05.2014 / 09:57
fonte