Estou procurando uma recomendação aqui. Estou lutando para saber se é melhor retornar um valor nulo ou vazio de um método quando o valor de retorno não está presente ou não pode ser determinado.
Tome os dois métodos a seguir como exemplo:
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 maneira, o chamador não precisaria verificar se um NULL foi retornado.
Em FindPerson()
, retornar NULL parece um ajuste melhor. Independentemente de new Person()
ser retornado NULL ou não um Objeto Pessoa ( ) vazio, o chamador terá que verificar se o Objeto Pessoa está NULL ou vazio antes de fazer alguma coisa (como chamar UpdateName()
). Então, por que não apenas retornar NULL aqui e, em seguida, o chamador precisa apenas verificar NULL.
Alguém mais luta com isso? Qualquer ajuda ou insight é apreciada.
Respostas:
O StackOverflow tem uma boa discussão sobre esse tópico exato nas perguntas e respostas . Na pergunta mais votada, a kronoz observa:
Pessoalmente, gosto de retornar cadeias vazias para funções que retornam cadeias para minimizar a quantidade de manipulação de erros que precisa ser implementada. No entanto, você precisará garantir que o grupo com o qual você trabalha siga a mesma convenção - caso contrário, os benefícios desta decisão não serão alcançados.
No entanto, como observou o pôster na resposta do SO, os nulos provavelmente devem ser retornados se um objeto for esperado, para que não haja dúvida sobre se os dados estão sendo retornados.
No final, não existe a melhor maneira de fazer as coisas. Construir um consenso de equipe acabará por impulsionar as melhores práticas de sua equipe.
fonte
for x in list_of_things() {...}
é sem dúvida mais rápido para Grokar entãol = list_of_things(); if l != null {...}
emit(user.hometown)
do queif user.hometown == null { emit("") else {emit(user.hometown)}
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
null
é que a pessoa que usa a interface não sabe senull
é um resultado possível e se precisa verificar se há umnot null
tipo de referência.No F #, você pode retornar um
option
tipo, que pode sersome(Person)
ounone
, portanto, é óbvio para o chamador que ele deve verificar.O padrão C # (anti-) análogo é o
Try...
método:Agora eu sei que as pessoas disseram que odeiam o
Try...
padrão porque ter um parâmetro de saída interrompe as idéias de uma função pura, mas na verdade não é diferente de:... e para ser sincero, você pode assumir que todo programador .NET conhece o
Try...
padrão porque é usado internamente pela estrutura .NET. Isso significa que eles não precisam ler a documentação para entender o que fazem, o que é mais importante para mim do que seguir a visão de funções de alguns puristas (entender que issoresult
é umout
parâmetro, não umref
parâmetro).Então, eu continuaria
TryFindPerson
porque você parece indicar que é perfeitamente normal não conseguir encontrá-lo.Se, por outro lado, não houver motivo lógico para o chamador fornecer um
personId
que não existe, eu provavelmente faria o seguinte:... e depois lançaria uma exceção se fosse inválida. O
Get...
prefixo implica que o chamador sabe que deve ter sucesso.fonte
Você pode experimentar o padrão de Caso Especial de Martin Fowler , de Paterns of Enterprise Application Architecture :
fonte
Eu acho
ReverseString()
que retornaria a string invertida e lançaria umIllegalArgumentException
se passado em aNull
.Eu acho que
FindPerson()
deveria seguir o padrão NullObject ou criar uma exceção desmarcada sobre não encontrar algo, se você sempre conseguir encontrar algo.Ter que lidar com isso
Null
é algo que deve ser evitado. TendoNull
em idiomas foi chamado de erro de bilhões de dólares por seu inventor!fonte
Retornar uma opção . Todos os benefícios de retornar um valor inválido diferente (como poder ter valores vazios em suas coleções) sem nenhum risco de NullPointerException.
fonte
boost::optional<T>
Eu vejo os dois lados desse argumento e percebo que algumas vozes bastante influentes (por exemplo, Fowler) advogam 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 apoiar os proponentes de retornar nulo. Acho que há uma distinção importante em invocar um método e ele responder com eu não tenho nenhum dado e responder com eu tenho essa String vazia .
Desde que eu 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 algum atributo do localizador (por exemplo, um ID), um cliente poderá verificar imediatamente se há nulo 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 um certo rigor por parte do cliente, e não, não acho que isso seja algo ruim.
Agora considere a alternativa em que você retorna um objeto Person válido ... que não contém nada. Você coloca 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 nula? Esse tipo de coisa não vai realmente levar a muito ou mais desordem de código e instruções condicionais do que se tivéssemos acabado de procurar por nulo e seguimos em frente?
Novamente, há pontos em ambos os lados desse argumento com os quais eu poderia concordar, mas acho que isso faz mais sentido para a maioria das pessoas (tornando o código mais sustentável).
fonte
FindPerson
ouGetPerson
nula ou lança uma exceção se a chave não existir?" Como usuário da API, simplesmente não sei e tenho que verificar a documentação. Por outro lado,bool TryGetPerson(Guid key, out Person person)
não exige que eu verifique a documentação e sei que uma exceção não é lançada, o que equivale a uma circunstância não excepcional. Esse é o ponto que estou tentando expressar na minha resposta.findPerson
), é absolutamente normal não obter nenhum resultado. Isso não deve levar a uma exceção.Retorne nulo se precisar saber se o item existe ou não. Caso contrário, retorne o tipo de dados esperado. Isto é especialmente verdade se você estiver retornando uma lista de itens. Geralmente, é seguro supor que, se o chamador estiver querendo uma lista, ele desejará repetir a lista. Muitos idiomas (a maioria? Todos?) Falham se você tentar iterar por nulo, mas não quando você iterar por uma lista que está vazia.
Acho frustrante tentar usar código como este, apenas para que ele falhe:
Eu não deveria ter que adicionar um caso especial para o caso quando não há coisas .
fonte
Depende da semântica do método. Você pode especificar, seu método aceita apenas "Não nulo". Em Java, você pode declarar isso usando algumas anotações de metadados:
De alguma forma, você deve especificar o valor de retorno. Se você declarar, aceita apenas valores NotNull, é obrigado a retornar uma sequência vazia (se a entrada também for uma sequência vazia).
O segundo caso é um pouco complicado em sua semântica. Se esse método é retornar uma pessoa por sua chave primária (e é esperado que a pessoa exista), a melhor maneira é lançar uma exceção.
Se você procurar uma pessoa por algum ID adivinhado (o que me parece estranho), é melhor declarar esse método como @Nullable.
fonte
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ódigos feios como
if (someObject != null && someObject.someMethod () != whateverValue)
Por exemplo, se um método retorna 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 sobre essa coleção vazia não terá praticamente nenhuma penalidade de desempenho. Outro caso seria um método que retorna a instância da classe usada para registrar dados, retornando um objeto Null em vez denull
preferível na minha opinião, pois não força os usuários a sempre verificar se a referência retornada énull
.Nos casos em que retornar
null
faz sentido (chamando ofindPerson ()
método por exemplo), eu tentaria pelo menos fornecer um método que retorne se o objeto estiver presente (personExists (int personId)
por exemplo), outro exemplo seria ocontainsKey ()
métodoMap
em Java. Isso torna o código dos chamadores mais limpo, pois você pode ver facilmente que existe a possibilidade de o objeto desejado não estar disponível (a pessoa não existe, a chave não está presente no mapa). Constantemente verificando se uma referêncianull
ofusca o código na minha opinião.fonte
null é a melhor coisa para retornar se e somente se as seguintes condições se aplicarem:
Além disso, certifique-se de documentar o fato de que a função pode retornar nula.
Se você seguir essas regras, os nulos serão inofensivos. Observe que, se você esquecer de procurar nulo, normalmente receberá uma NullPointerException imediatamente depois, o que geralmente é um bug muito 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 silenciosamente pelo sistema, possivelmente corrompendo dados, sem gerar uma exceção.
Por fim, se você aplicar essas regras às duas funções listadas na pergunta:
fonte
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 tomo a seguinte abordagem. Considero uma função ou método chamada f (v1, ..., vn) como a aplicação de uma função
onde S é o "estado do mundo" T1, ..., Tn são os tipos de parâmetros de entrada e T é o tipo de retorno.
Primeiro tento definir esta função. Se a função for parcial (ou seja, existem alguns valores de entrada para os quais não está definida), retorno NULL para sinalizar isso. Isso ocorre porque quero que a computação termine normalmente e me diga que a função que solicitei não está definida nas entradas fornecidas. O uso, por exemplo, de uma string vazia como valor de retorno é ambíguo, pois pode ser que a função esteja definida nas entradas e a string vazia seja o resultado correto.
Eu 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 é tarefa do método chamado informar se a função não está definida para a entrada especificada.
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
para procurar um cliente no banco de dados do aplicativo por seu código. Nesse método, eu
As verificações extras para nulo, por exemplo
fazem parte da semântica do que estou fazendo e não apenas os "pulo" para tornar o código melhor lido. Não acho que seja uma boa prática simplificar a semântica do problema em questão apenas para simplificar o código.
Obviamente, como a verificação de nulo ocorre com muita frequência, é bom que o idioma suporte alguma sintaxe especial para ele.
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.
fonte
Os métodos que retornam coleções devem retornar vazios, mas outros podem retornar nulos, porque, para as coleções, pode acontecer que você tenha um objeto, mas não haja elementos nele; portanto, o chamador validará apenas o tamanho e os dois.
fonte
Para mim, existem dois casos aqui. Se você estiver retornando algum tipo de lista, sempre a retorne, não importa o quê, vazia se não tiver nada para colocar nela.
O único caso em que vejo algum debate é quando você retorna um único item. Eu me pego preferindo retornar nulo 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, eles poderão prosseguir 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 estiver errado, quero uma exceção o mais rápido possível. É menos provável que você envie um bug dessa maneira.
fonte
Adicionando o que as pessoas já disseram sobre o
Maybe
construtor de tipos:Outro grande ganho, além de não arriscar as NPEs, é o semântico. No mundo funcional, onde lidamos com funções puras, gostamos de tentar criar funções que, quando aplicadas, são exatamente equivalentes ao seu valor de retorno . Considere o caso de
String.reverse()
: Esta é uma função que, dada uma determinada string, representa a versão invertida dessa string. Sabemos que essa string existe, porque cada string pode ser revertida (strings são basicamente conjuntos de caracteres ordenados).Agora que tal
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, as funções têm um único valor de retorno; portanto, você não pode realmente dizer "essa função retorna um cliente com o ID especificado OU retornanull
.Este é o meu principal problema ao retornar
null
e retornar 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 letra para NADA. Nível muito baixo, feio e propenso a erros. Um objeto nulo também é bastante enganador aqui, eu acho. Um cliente nulo É um cliente. Não é a ausência de um, não é o cliente com o ID solicitado, portanto, devolvê-lo é simplesmente ERRADO. Este NÃO é o cliente que solicitei, mas você ainda afirma que retornou um cliente. Ele 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 coisas sobre seu design ou leia a documentação.Ambos os problemas são resolvidos por a
Maybe Customer
. De repente, não estamos dizendo "esta função representa o cliente com o ID fornecido". Estamos dizendo que "essa função representa o cliente com o ID fornecido, se ele existir . Agora, é muito claro e explícito sobre o que ele faz. Se você solicitar um cliente com um ID inexistente, receberáNothing
. Como isso é melhor quenull
? não apenas para o risco NPE. Mas também porque o tipo deNothing
não é sóMaybe
. éMaybe Customer
. Ele tem significado semântico. Ele especificamente significa "a ausência de um cliente", indicando que tal cliente não existe.Outro problema com null é, obviamente, que é ambíguo. Será que você não encontrou o cliente ou houve um erro de conexão de banco de dados ou simplesmente não tenho permissão para vê-lo?
Se você tiver vários casos de erro como esse para tratar, poderá lançar exceções para essas versões ou (e eu prefiro dessa maneira) retornar um
Either CustomerLoadError Customer
, representando algo que deu errado ou o cliente retornado. Ele tem todas as vantagens deMaybe Customer
permitir que você especifique o que pode dar errado.A principal coisa que procuro é capturar o contrato da função em sua assinatura. Não assuma as coisas, não confie em convenções passageiras, seja explícito.
fonte
1) Se a semântica da função é 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 entregue a ninguém.
2) É bom criar funções que sempre semanticamente retornam algo (ou jogam).
Com um criador de logs , faz sentido retornar o criador de logs que não registra se nenhum criador de logs foi definido. Ao retornar a coleção, quase nunca faz sentido (exceto em "nível baixo o suficiente", em que a própria coleção é os dados, e não o que ela contém) para retornar nada, porque um conjunto vazio é um conjunto, não nada.
Com uma pessoa exemplo, eu iria "hibernação" caminho (ficar vs carga, IIRC, mas não é complicado por objetos de proxy para o carregamento lento) de ter duas funções, que retorna nulo e segundo que joga.
fonte
NULL deve ser retornado se o aplicativo espera que um dado esteja disponível, mas ele não esteja disponível. Por exemplo, um serviço que retorna a CITY com base no CEP deve retornar nulo se a cidade não for encontrada. O chamador pode então decidir lidar com o nulo ou explodir.
A lista vazia deve ser retornada se houver duas possibilidades. Dados disponíveis ou NÃO há dados disponíveis. Por exemplo, um serviço que retorna a (s) CIDADE (s) se a população for maior que determinado número. Ele pode retornar uma lista vazia se Nenhum dado satisfizer os critérios fornecidos.
fonte
Retornar NULL é um design terrível , no mundo orientado a objetos. Em poucas palavras, o uso NULL leva a:
Verifique esta publicação no blog para obter uma explicação detalhada: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
fonte