Qual é a melhor prática ao retornar dados de funções. É melhor retornar um objeto nulo ou vazio? E por que um deveria fazer um sobre o outro?
Considere isto:
public UserEntity GetUserById(Guid userId)
{
//Imagine some code here to access database.....
//Check if data was returned and return a null if none found
if (!DataExists)
return null;
//Should I be doing this here instead?
//return new UserEntity();
else
return existingUserEntity;
}
Vamos fingir que haveria casos válidos neste programa que não haveriam informações do usuário no banco de dados com esse GUID. Eu imagino que não seria apropriado lançar uma exceção neste caso? Também tenho a impressão de que o tratamento de exceções pode prejudicar o desempenho.
c#
.net
function
return-value
7wp
fonte
fonte
if (!DataExists)
.Respostas:
Retornar nulo geralmente é a melhor ideia se você pretende indicar que nenhum dado está disponível.
Um objeto vazio indica que os dados foram retornados, enquanto o retorno nulo indica claramente que nada foi retornado.
Além disso, retornar um nulo resultará em uma exceção nula se você tentar acessar membros no objeto, o que pode ser útil para destacar o código de buggy - tentar acessar um membro do nada não faz sentido. O acesso a membros de um objeto vazio não falhará, o que significa que os erros podem não ser descobertos.
fonte
bool GetUserById(Guid userId, out UserEntity result)
- que eu preferiria com o valor de retorno "nulo" e que não é tão extremo quanto lançar uma exceção. Ele permite umnull
código livre bonito comoif(GetUserById(x,u)) { ... }
.Depende do que faz mais sentido para o seu caso.
Faz sentido retornar nulo, por exemplo, "esse usuário não existe"?
Ou faz sentido criar um usuário padrão? Isso faz mais sentido quando você pode assumir com segurança que, se um usuário NÃO EXISTE, o código de chamada pretende que exista um quando ele solicitar.
Ou faz sentido lançar uma exceção (à la "FileNotFound") se o código de chamada estiver exigindo um usuário com um ID inválido?
No entanto - do ponto de vista da separação de preocupações / SRP, os dois primeiros estão mais corretos. E tecnicamente o primeiro é o mais correto (mas apenas por um fio de cabelo) - GetUserById deve ser o único responsável por uma coisa: obter o usuário. Manipular seu próprio caso "o usuário não existe" retornando outra coisa pode ser uma violação do SRP. Separar em uma verificação diferente -
bool DoesUserExist(id)
seria apropriado se você optar por lançar uma exceção.Com base nos extensos comentários abaixo : se essa é uma pergunta de design no nível da API, esse método pode ser análogo ao "OpenFile" ou "ReadEntireFile". Estamos "abrindo" um usuário de algum repositório e hidratando o objeto a partir dos dados resultantes. Uma exceção pode ser apropriada neste caso. Pode não ser, mas poderia ser.
Todas as abordagens são aceitáveis - depende apenas, com base no contexto maior da API / aplicativo.
fonte
Pessoalmente, eu uso NULL. Deixa claro que não há dados a serem retornados. Mas há casos em que um objeto nulo pode ser útil.
fonte
Se o seu tipo de retorno for uma matriz, retorne uma matriz vazia, caso contrário, retorne nulo.
fonte
null
. Ele permite que você o use emforeach
declarações e consultas linq sem se preocuparNullReferenceException
.Você deve lançar uma exceção (apenas) se um contrato específico for quebrado.
No seu exemplo específico, solicitando uma UserEntity com base em um ID conhecido, isso dependeria do fato de que usuários ausentes (excluídos) sejam um caso esperado. Se sim, retorne,
null
mas se não for um caso esperado, emita uma exceção.Observe que se a função fosse chamada
UserEntity GetUserByName(string name)
, provavelmente não seria lançada, mas retornaria nula. Em ambos os casos, retornar um UserEntity vazio seria inútil.Para strings, matrizes e coleções, a situação geralmente é diferente. Lembro-me de algumas diretrizes do MS que os métodos devem aceitar
null
como uma lista 'vazia', mas retornam coleções de comprimento zero em vez denull
. O mesmo para seqüências de caracteres. Observe que você pode declarar matrizes vazias:int[] arr = new int[0];
fonte
.Wher(p => p.Lastname == "qwerty")
deve retornar uma coleção vazia, nãonull
.Essa é uma pergunta comercial, dependendo de a existência de um usuário com um Guid Id específico ser um caso de uso normal esperado para esta função ou de uma anomalia que impedirá o aplicativo de concluir com êxito qualquer função que este método esteja fornecendo ao usuário objetar para ...
Se for uma "exceção", a ausência de um usuário com esse ID impedirá que o aplicativo conclua com êxito qualquer função que esteja executando (digamos que estamos criando uma fatura para um cliente ao qual enviamos o produto ... ), essa situação deve gerar uma ArgumentException (ou alguma outra exceção personalizada).
Se um usuário ausente estiver ok, (um dos possíveis resultados normais da chamada dessa função), retorne um valor nulo ....
EDIT: (para abordar o comentário de Adam em outra resposta)
Se o aplicativo contiver vários processos de negócios, um ou mais dos quais exigem que um Usuário seja concluído com êxito e um ou mais deles possam ser concluídos com sucesso sem um usuário, a exceção deverá ser lançada na pilha de chamadas, mais perto de onde os processos de negócios que requerem um usuário estão chamando esse encadeamento de execução. Os métodos entre esse método e esse ponto (onde a exceção está sendo lançada) devem apenas comunicar que não existe usuário (nulo, booleano, qualquer que seja - este é um detalhe de implementação).
Mas se todos os processos dentro do aplicativo exigirem um usuário, eu ainda lançaria a exceção nesse método ...
fonte
Pessoalmente, eu retornaria nulo, porque é assim que espero que a camada DAL / Repositório atue.
Se não existir, não retorne nada que possa ser interpretado como uma busca bem-sucedida de um objeto,
null
funciona lindamente aqui.O mais importante é ser consistente em toda a sua camada DAL / Repos, para que você não fique confuso sobre como usá-lo.
fonte
Eu tendem a
return null
se o ID do objeto não existir, quando não for conhecido previamente se ele deve existir.throw
se o ID do objeto não existir quando deveria existir.Eu diferencio esses dois cenários com esses três tipos de métodos. Primeiro:
Segundo:
Terceiro:
fonte
out
nãoref
Ainda outra abordagem envolve a transmissão de um objeto ou delegado de retorno de chamada que operará no valor. Se um valor não for encontrado, o retorno de chamada não será chamado.
Isso funciona bem quando você deseja evitar verificações nulas em todo o seu código e quando não encontrar um valor não é um erro. Você também pode fornecer um retorno de chamada para quando nenhum objeto for encontrado, se você precisar de algum processamento especial.
A mesma abordagem usando um único objeto pode ser semelhante a:
Do ponto de vista do design, eu realmente gosto dessa abordagem, mas tem a desvantagem de tornar o site de chamada mais volumoso em idiomas que não oferecem suporte imediato a funções de primeira classe.
fonte
Usamos o CSLA.NET e consideramos que uma busca de dados com falha deve retornar um objeto "vazio". Isso é realmente muito irritante, pois exige a convenção de verificar se
obj.IsNew
prefereobj == null
.Como um pôster anterior mencionado, os valores de retorno nulos farão com que o código falhe imediatamente, reduzindo a probabilidade de problemas furtivos causados por objetos vazios.
Pessoalmente, acho que
null
é mais elegante.É um caso muito comum, e estou surpreso que as pessoas aqui pareçam surpreendidas por ele: em qualquer aplicativo Web, os dados geralmente são buscados usando um parâmetro de string de consulta, que obviamente pode ser confundido, exigindo que o desenvolvedor lide com incidências de "não encontrado" "
Você pode lidar com isso da seguinte maneira:
... mas é sempre uma chamada extra para o banco de dados, o que pode ser um problema nas páginas de alto tráfego. Enquanto que:
... requer apenas uma chamada.
fonte
Eu prefiro
null
, pois é compatível com o operador de coalescência nula (??
).fonte
Eu diria que retorne nulo em vez de um objeto vazio.
Mas na instância específica que você mencionou aqui, você está procurando um usuário pelo ID do usuário, que é a chave para esse usuário. Nesse caso, eu provavelmente gostaria de lançar uma exceção se nenhuma instância da instância do usuário for encontrada .
Esta é a regra que geralmente sigo:
fonte
Isso varia de acordo com o contexto, mas geralmente retornarei nulo se estiver procurando por um objeto específico (como no seu exemplo) e retornarei uma coleção vazia se estiver procurando por um conjunto de objetos, mas não houver nenhum.
Se você cometeu um erro no seu código e retornar nulos leva a exceções de ponteiros nulos, quanto mais cedo você perceber isso, melhor. Se você retornar um objeto vazio, o uso inicial dele poderá funcionar, mas você poderá obter erros posteriormente.
fonte
O melhor nesse caso retorna "nulo", caso não exista esse usuário. Também torne seu método estático.
Editar:
Normalmente, métodos como este são membros de alguma classe "Usuário" e não têm acesso aos membros da instância. Nesse caso, o método deve ser estático; caso contrário, você deve criar uma instância de "Usuário" e chamar o método GetUserById que retornará outra instância de "Usuário". Concordo que isso é confuso. Mas se o método GetUserById for membro de alguma classe "DatabaseFactory" - não há problema em deixá-lo como membro da instância.
fonte
Eu pessoalmente retorno uma instância padrão do objeto. O motivo é que espero que o método retorne zero a muitos ou zero a um (dependendo da finalidade do método). A única razão pela qual seria um estado de erro de qualquer tipo, usando essa abordagem, é se o método não retornou nenhum objeto (s) e sempre foi esperado (em termos de retorno um para muitos ou singular).
Quanto à suposição de que essa é uma questão de domínio de negócios - simplesmente não a vejo desse lado da equação. A normalização de tipos de retorno é uma questão válida de arquitetura de aplicativo. No mínimo, está sujeito a padronização nas práticas de codificação. Duvido que haja um usuário corporativo que diga "no cenário X, apenas dê um nulo".
fonte
Nos nossos Objetos de Negócios, temos 2 métodos Get principais:
Para manter as coisas simples no contexto ou você questiona, elas seriam:
O primeiro método é usado ao obter entidades específicas, o segundo método é usado especificamente ao adicionar ou editar entidades em páginas da web.
Isso nos permite ter o melhor dos dois mundos no contexto em que são usados.
fonte
Eu sou um estudante de TI francês, então desculpe meu inglês ruim. Nas nossas aulas, somos informados de que esse método nunca deve retornar nulo, nem um objeto vazio. O usuário desse método deve verificar primeiro se o objeto que ele está procurando existe antes de tentar obtê-lo.
Usando Java, somos solicitados a adicionar um
assert exists(object) : "You shouldn't try to access an object that doesn't exist";
no início de qualquer método que possa retornar nulo, para expressar a "pré-condição" (não sei qual é a palavra em inglês).Na IMO, isso realmente não é fácil de usar, mas é isso que estou usando, esperando por algo melhor.
fonte
Se o caso do usuário não sendo encontrado surge muitas vezes o suficiente, e você quer lidar com isso de várias maneiras, dependendo da circunstância (às vezes jogando uma exceção, por vezes substituindo um usuário vazio) você também pode usar algo parecido com 's F #
Option
de ou HaskellMaybe
tipo , que separa explicitamente o caso 'sem valor' de 'encontrado algo!'. O código de acesso ao banco de dados pode ficar assim:E ser usado assim:
Infelizmente, todo mundo parece ter um tipo como esse próprio.
fonte
Eu normalmente retorno nulo. Ele fornece um mecanismo rápido e fácil para detectar se algo estragou tudo sem gerar exceções e usando toneladas de tentativa / captura em todo o lugar.
fonte
Para os tipos de coleção, eu retornaria uma coleção vazia, para todos os outros tipos, prefiro usar os padrões NullObject para retornar um objeto que implementa a mesma interface que a do tipo de retorno. para obter detalhes sobre o padrão, consulte o texto do link
Usando o padrão NullObject, isso seria: -
{// Imagine algum código aqui para acessar o banco de dados .....
}
fonte
Para colocar o que os outros disseram de maneira conciliatória ...
Exceções são para circunstâncias excepcionais
Se esse método for pura camada de acesso a dados, eu diria que, dado algum parâmetro incluído em uma instrução select, seria de esperar que eu não encontrasse nenhuma linha para construir um objeto e, portanto, retornar nulo seria aceitável, pois é lógica de acesso a dados.
Por outro lado, se eu esperasse que meu parâmetro refletisse uma chave primária e só devesse recuperar uma linha, se recuperasse mais de uma, lançaria uma exceção. 0 está ok para retornar nulo, 2 não está.
Agora, se eu tivesse algum código de login verificado em um provedor LDAP e em um banco de dados para obter mais detalhes e esperava que eles estivessem sincronizados o tempo todo, talvez eu lance a exceção. Como outros disseram, são regras de negócios.
Agora vou dizer que é uma regra geral . Há momentos em que você pode querer quebrar isso. No entanto, minha experiência e experimentos com C # (muito disso) e Java (um pouco disso) me ensinaram que é muito mais caro em termos de desempenho lidar com exceções do que lidar com problemas previsíveis por meio da lógica condicional. Estou falando da ordem de 2 ou 3 ordens de magnitude mais caras em alguns casos. Portanto, se for possível que seu código termine em um loop, eu recomendaria retornar nulo e testá-lo.
fonte
Perdoe meu pseudo-php / código.
Eu acho que realmente depende do uso pretendido do resultado.
Se você pretende editar / modificar o valor retornado e salvá-lo, retorne um objeto vazio. Dessa forma, você pode usar a mesma função para preencher dados em um objeto novo ou existente.
Digamos que eu tenho uma função que pega uma chave primária e uma matriz de dados, preenche a linha com dados e salva o registro resultante no banco de dados. Como pretendo preencher o objeto com meus dados de qualquer maneira, pode ser uma grande vantagem recuperar um objeto vazio do getter. Dessa forma, eu posso executar operações idênticas em ambos os casos. Você usa o resultado da função getter, não importa o quê.
Exemplo:
Aqui podemos ver que a mesma série de operações manipula todos os registros desse tipo.
No entanto, se a intenção final do valor de retorno for ler e fazer algo com os dados, retornarei nulo. Dessa forma, posso determinar rapidamente se não houve retorno de dados e exibir a mensagem apropriada para o usuário.
Normalmente, capturarei exceções em minha função que recuperam os dados (para que eu possa registrar mensagens de erro, etc ...) e depois retornarei nulo diretamente da captura. Geralmente, não importa para o usuário final qual é o problema; portanto, acho melhor encapsular meu registro / processamento de erros diretamente na função que obtém os dados. Se você estiver mantendo uma base de código compartilhada em qualquer empresa grande, isso é especialmente benéfico, porque você pode forçar o registro / tratamento de erros adequado até mesmo no programador mais preguiçoso.
Exemplo:
Essa é a minha regra geral. Até agora funcionou bem.
fonte
Pergunta interessante e acho que não há resposta "certa", pois sempre depende da responsabilidade do seu código. Seu método sabe se nenhum dado encontrado é um problema ou não? Na maioria dos casos, a resposta é "não" e é por isso que retornar nulo e permitir que o chamador lide com a situação é perfeita.
Talvez uma boa abordagem para distinguir métodos de lançamento de métodos de retorno nulo seja encontrar uma convenção em sua equipe: Métodos que dizem que "recebem" algo devem gerar uma exceção se não houver nada para obter. Os métodos que podem retornar nulos podem ter nomes diferentes, talvez "Localizar ...".
fonte
Se o objeto retornado for algo que pode ser iterado, eu retornaria um objeto vazio, para que não precise testar primeiro o nulo.
Exemplo:
fonte
Eu gosto de não retornar nulo de nenhum método, mas de usar o tipo funcional Option. Os métodos que não podem retornar resultado retornam uma opção vazia, em vez de nula.
Além disso, esses métodos que não podem retornar resultados devem indicar isso por meio de seus nomes. Normalmente, coloquei Try ou TryGet ou TryFind no início do nome do método para indicar que ele pode retornar um resultado vazio (por exemplo, TryFindCustomer, TryLoadFile, etc.).
Isso permite que o chamador aplique técnicas diferentes, como pipelining de coleção (consulte Pipeline de coleção de Martin Fowler ) no resultado.
Aqui está outro exemplo em que retornar Option em vez de null é usado para reduzir a complexidade do código: Como reduzir a complexidade ciclomática: Tipo funcional da opção
fonte
Mais carne para moer: digamos que meu DAL retorne um NULL para GetPersonByID, conforme recomendado por alguns. O que minha BLL (bastante fina) deve fazer se receber um NULL? Passar esse NULL para cima e deixar o consumidor final se preocupar com isso (nesse caso, uma página ASP.Net)? Que tal ter o BLL lançar uma exceção?
O BLL pode estar sendo usado pelo ASP.Net e Win App, ou outra biblioteca de classes - acho injusto esperar que o consumidor final "intrinsecamente" saiba que o método GetPersonByID retorna um valor nulo (a menos que tipos nulos sejam usados, eu acho )
Minha opinião (pelo que vale a pena) é que meu DAL retorne NULL se nada for encontrado. PARA ALGUNS OBJETOS, tudo bem - poderia ser uma lista de 0: muitas coisas, então não ter nada é bom (por exemplo, uma lista de livros favoritos). Nesse caso, minha BLL retorna uma lista vazia. Para a maioria das coisas de uma única entidade (por exemplo, usuário, conta, fatura), se eu não tiver uma, isso é definitivamente um problema e uma exceção cara. No entanto, como recuperar um usuário por um identificador exclusivo fornecido anteriormente pelo aplicativo sempre deve retornar um usuário, a exceção é uma exceção "adequada", como é excepcional. O consumidor final da BLL (ASP.Net, por exemplo) só espera que as coisas sejam exageradas; portanto, um Manipulador de Exceção Unhandled será usado em vez de agrupar todas as chamadas para GetPersonByID em um bloco try-catch.
Se houver um problema evidente em minha abordagem, informe-me, pois estou sempre interessado em aprender. Como outros pôsteres disseram, as exceções são caras e a abordagem de "verificar primeiro" é boa, mas as exceções devem ser exatamente isso - excepcionais.
Estou gostando deste post, muitas boas sugestões para cenários "depende" :-)
fonte
Eu acho que as funções não devem retornar nulas, para a saúde da sua base de código. Eu posso pensar em alguns motivos:
Haverá uma grande quantidade de cláusulas de guarda tratando referência nula
if (f() != null)
.O que é
null
, é uma resposta aceita ou um problema? Nulo é um estado válido para um objeto específico? (imagine que você é um cliente para o código). Quero dizer que todos os tipos de referência podem ser nulos, mas deveriam?Se
null
ficar por perto, quase sempre dará algumas exceções inesperadas ao NullRef de tempos em tempos, à medida que sua base de código cresce.Existem algumas soluções
tester-doer pattern
ou a implementaçãooption type
da programação funcional.fonte
Estou perplexo com o número de respostas (em toda a web) que dizem que você precisa de dois métodos: um método "IsItThere ()" e um método "GetItForMe ()", e isso leva a uma condição de corrida. O que há de errado com uma função que retorna nula, atribuindo-a a uma variável e verificando a variável como Nula, tudo em um teste? Meu antigo código C foi salpicado com
if (NULL! = (variável = função (argumentos ...))) {
Então você obtém o valor (ou nulo) em uma variável e o resultado de uma só vez. Este idioma foi esquecido? Por quê?
fonte
Eu concordo com a maioria das postagens aqui, que tendem para
null
.Meu raciocínio é que a geração de um objeto vazio com propriedades não anuláveis pode causar erros. Por exemplo, uma entidade com uma
int ID
propriedade teria um valor inicial deID = 0
, que é um valor totalmente válido. Se esse objeto, sob alguma circunstância, for salvo no banco de dados, seria uma coisa ruim.Para qualquer coisa com um iterador, eu sempre usaria a coleção vazia. Algo como
é o cheiro do código na minha opinião. As propriedades da coleção nunca devem ser nulas.
Um caso de ponta é
String
. Muitas pessoas dizem que issoString.IsNullOrEmpty
não é realmente necessário, mas nem sempre é possível distinguir entre uma string vazia e nula. Além disso, alguns sistemas de banco de dados (Oracle) não fazem distinção entre eles (''
são armazenados comoDBNULL
), então você é forçado a lidar com eles igualmente. A razão para isso é que a maioria dos valores de string é proveniente da entrada do usuário ou de sistemas externos, enquanto nem as caixas de texto nem a maioria dos formatos de troca têm representações diferentes para''
enull
. Portanto, mesmo que o usuário deseje remover um valor, ele não poderá fazer nada além de limpar o controle de entrada. Além disso, a distinção entrenvarchar
campos de banco de dados anuláveis e não anuláveis é mais do que questionável, se o seu DBMS não for Oracle - um campo obrigatório que permite''
é estranho, sua interface do usuário nunca permitiria isso; portanto, suas restrições não são mapeadas. Portanto, a resposta aqui, na minha opinião, é lidar com eles igualmente, sempre.Em relação à sua pergunta sobre exceções e desempenho: Se você lançar uma exceção que não pode lidar completamente na lógica do seu programa, precisará abortar, em algum momento, o que o seu programa estiver fazendo e pedir ao usuário que refaça o que acabou de fazer. Nesse caso, a penalidade de desempenho de a
catch
é realmente a menor das suas preocupações - ter que perguntar ao usuário é o elefante na sala (o que significa re-renderizar toda a interface do usuário ou enviar algum HTML pela Internet). Portanto, se você não seguir o antipadrão de " Fluxo de programa com exceções ", não se preocupe, basta lançar um se fizer sentido. Mesmo em casos limítrofes, como "Exceção de validação", o desempenho não é realmente um problema, pois é necessário perguntar novamente ao usuário, em qualquer caso.fonte
Um padrão TryGet assíncrono:
Para métodos síncronos, acredito que a resposta de @Johann Gerell é o padrão a ser usado em todos os casos.
No entanto, o padrão TryGet com o
out
parâmetro não funciona com métodos Async.Com as Tuplas Literais do C # 7, agora você pode fazer isso:
fonte