¿Cuál es la mejor práctica al devolver datos de funciones? ¿Es mejor devolver un objeto nulo o vacío? ¿Y por qué debería uno hacer uno sobre el otro?
Considera esto:
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;
}
Supongamos que habría casos válidos en este programa y que no habría información de usuario en la base de datos con ese GUID. Me imagino que no sería apropiado lanzar una excepción en este caso. También tengo la impresión de que el manejo de excepciones puede afectar el rendimiento.
c#
.net
function
return-value
7wp
fuente
fuente
if (!DataExists)
.Respuestas:
Por lo general, la mejor idea es devolver nulo si tiene la intención de indicar que no hay datos disponibles.
Un objeto vacío implica que se han devuelto datos, mientras que devolver nulo indica claramente que no se ha devuelto nada.
Además, devolver un valor nulo dará como resultado una excepción nula si intenta acceder a los miembros del objeto, lo que puede ser útil para resaltar el código defectuoso; intentar acceder a un miembro de la nada no tiene sentido. El acceso a los miembros de un objeto vacío no fallará, lo que significa que los errores pueden quedar sin descubrir.
fuente
bool GetUserById(Guid userId, out UserEntity result)
, lo que preferiría al valor de retorno "nulo", y que no es tan extremo como lanzar una excepción. Permitenull
código hermoso como libreif(GetUserById(x,u)) { ... }
.Depende de lo que tenga más sentido para su caso.
¿Tiene sentido devolver nulo, por ejemplo, "no existe tal usuario"?
¿O tiene sentido crear un usuario predeterminado? Esto tiene más sentido cuando puede asumir con seguridad que si un usuario NO existe, el código de llamada intenta que exista uno cuando lo solicite.
¿O tiene sentido lanzar una excepción (a la "FileNotFound") si el código de llamada exige un usuario con una identificación no válida?
Sin embargo, desde una separación de preocupaciones / punto de vista de SRP, los dos primeros son más correctos. Y técnicamente, el primero es el más correcto (pero solo por un pelo): GetUserById solo debe ser responsable de una cosa: obtener al usuario. Manejar su propio caso de "usuario no existe" devolviendo algo más podría ser una violación de SRP. Separarse en un cheque diferente:
bool DoesUserExist(id)
sería apropiado si elige lanzar una excepción.Según los extensos comentarios a continuación : si se trata de una pregunta de diseño de nivel API, este método podría ser análogo a "OpenFile" o "ReadEntireFile". Estamos "abriendo" a un usuario desde algún repositorio e hidratando el objeto de los datos resultantes. Una excepción podría ser apropiada en este caso. Puede que no sea, pero podría ser.
Todos los enfoques son aceptables, solo depende, en función del contexto más amplio de la API / aplicación.
fuente
Personalmente, uso NULL. Deja en claro que no hay datos para devolver. Pero hay casos en que un objeto nulo puede ser útil.
fuente
Si su tipo de retorno es una matriz, devuelva una matriz vacía; de lo contrario, devuelva nulo.
fuente
null
. Le permite usarlo enforeach
declaraciones y consultas linq sin preocuparseNullReferenceException
.Debe lanzar una excepción (solo) si se rompe un contrato específico.
En su ejemplo específico, al solicitar una entidad de usuario basada en un ID conocido, dependería del hecho si los usuarios faltantes (eliminados) son un caso esperado. Si es así, regrese,
null
pero si no es un caso esperado, arroje una excepción.Tenga en cuenta que si se llamó a la función
UserEntity GetUserByName(string name)
, probablemente no arrojaría sino que devolvería nulo. En ambos casos, devolver una UserEntity vacía sería inútil.Para cadenas, matrices y colecciones, la situación suele ser diferente. Recuerdo algunas pautas de MS que los métodos deberían aceptar
null
como una lista 'vacía' pero devolver colecciones de longitud cero en lugar denull
. Lo mismo para las cuerdas. Tenga en cuenta que puede declarar matrices vacías:int[] arr = new int[0];
fuente
.Wher(p => p.Lastname == "qwerty")
debería devolver una colección vacía, nonull
.Esta es una pregunta comercial, que depende de si la existencia de un usuario con un Id. De Guid específico es un caso de uso normal esperado para esta función, o es una anomalía que evitará que la aplicación complete con éxito cualquier función que este método esté proporcionando al usuario oponerse a...
Si se trata de una "excepción", ya que la ausencia de un usuario con ese Id evitará que la aplicación complete con éxito cualquier función que esté haciendo (supongamos que estamos creando una factura para un cliente al que hemos enviado el producto ... ), esta situación debería generar una excepción ArgumentException (o alguna otra excepción personalizada).
Si un usuario perdido está bien (uno de los posibles resultados normales de llamar a esta función), entonces devuelve un valor nulo ...
EDITAR: (para abordar el comentario de Adam en otra respuesta)
Si la aplicación contiene múltiples procesos de negocios, uno o más de los cuales requieren un Usuario para completar con éxito, y uno o más de los cuales pueden completar con éxito sin un usuario, entonces la excepción se debe lanzar más arriba en la pila de llamadas, más cerca de donde Los procesos de negocio que requieren un Usuario están llamando a este hilo de ejecución. Los métodos entre este método y ese punto (donde se está lanzando la excepción) solo deben comunicar que no existe ningún usuario (nulo, booleano, lo que sea; este es un detalle de implementación).
Pero si todos los procesos dentro de la aplicación requieren un usuario, aún arrojaría la excepción en este método ...
fuente
Personalmente, devolvería nulo, porque así es como esperaría que actúe la capa DAL / Repository.
Si no existe, no devuelva nada que pueda interpretarse como la obtención exitosa de un objeto,
null
funciona muy bien aquí.Lo más importante es ser consistente en su capa DAL / Repos, de esa manera no se confunda sobre cómo usarlo.
fuente
tiendo a
return null
si la identificación del objeto no existe cuando no se sabe de antemano si debería existir.throw
si la identificación del objeto no existe cuando debería existir.Distingo estos dos escenarios con estos tres tipos de métodos. Primero:
Segundo:
Tercero:
fuente
out
noref
Otro enfoque implica pasar un objeto de devolución de llamada o delegado que operará en el valor. Si no se encuentra un valor, no se llama la devolución de llamada.
Esto funciona bien cuando desea evitar verificaciones nulas en todo su código, y cuando no encuentra un valor no es un error. También puede proporcionar una devolución de llamada para cuando no se encuentran objetos si necesita un procesamiento especial.
El mismo enfoque con un solo objeto podría verse así:
Desde una perspectiva de diseño, me gusta mucho este enfoque, pero tiene la desventaja de hacer que el sitio de llamadas sea más voluminoso en idiomas que no admiten funciones de primera clase.
fuente
Usamos CSLA.NET, y considera que una recuperación de datos fallida debería devolver un objeto "vacío". En realidad, esto es bastante molesto, ya que exige la convención de verificar si
obj.IsNew
es más que noobj == null
.Como se mencionó en un póster anterior, los valores de retorno nulos harán que el código falle de inmediato, reduciendo la probabilidad de problemas de sigilo causados por objetos vacíos.
Personalmente, creo que
null
es más elegante.Es un caso muy común, y me sorprende que la gente aquí parezca sorprendida por él: en cualquier aplicación web, los datos a menudo se obtienen utilizando un parámetro de cadena de consulta, que obviamente puede ser maltratado, por lo que requiere que el desarrollador maneje las incidencias de "no encontrado ".
Podrías manejar esto de la siguiente manera:
... pero esa es una llamada adicional a la base de datos cada vez, lo que puede ser un problema en las páginas de alto tráfico. Mientras:
... requiere solo una llamada.
fuente
Prefiero
null
, ya que es compatible con el operador de fusión nula (??
).fuente
Yo diría que devuelva nulo en lugar de un objeto vacío.
Pero en la instancia específica que ha mencionado aquí, está buscando un usuario por ID de usuario, que es una especie de clave para ese usuario, en ese caso probablemente me gustaría lanzar una excepción si no se encuentra una instancia de instancia de usuario .
Esta es la regla que generalmente sigo:
fuente
Varía según el contexto, pero generalmente devolveré nulo si busco un objeto en particular (como en su ejemplo) y devolveré una colección vacía si busco un conjunto de objetos pero no hay ninguno.
Si ha cometido un error en su código y devuelve nulo conduce a excepciones de puntero nulo, entonces cuanto antes lo detecte, mejor. Si devuelve un objeto vacío, su uso inicial puede funcionar, pero puede obtener errores más adelante.
fuente
Lo mejor en este caso devuelve "nulo" en el caso de que no exista dicho usuario. También haga que su método sea estático.
Editar:
Por lo general, métodos como este son miembros de alguna clase de "Usuario" y no tienen acceso a los miembros de su instancia. En este caso, el método debe ser estático; de lo contrario, debe crear una instancia de "Usuario" y luego llamar al método GetUserById que devolverá otra instancia de "Usuario". De acuerdo, esto es confuso. Pero si el método GetUserById es miembro de alguna clase "DatabaseFactory", no hay problema para dejarlo como miembro de instancia.
fuente
Yo personalmente devuelvo una instancia predeterminada del objeto. La razón es que espero que el método devuelva cero a muchos o cero a uno (dependiendo del propósito del método). La única razón por la que sería un estado de error de cualquier tipo, utilizando este enfoque, es si el método no devolvió ningún objeto (s) y siempre se esperaba que (en términos de un retorno de uno a muchos o singular).
En cuanto a la suposición de que esta es una pregunta de dominio comercial, simplemente no la veo desde ese lado de la ecuación. La normalización de los tipos de retorno es una pregunta válida de arquitectura de la aplicación. Como mínimo, está sujeto a estandarización en las prácticas de codificación. Dudo que haya un usuario comercial que diga "en el escenario X, simplemente déle un valor nulo".
fuente
En nuestros Business Objects tenemos 2 métodos principales de obtención:
Para mantener las cosas simples en el contexto o si cuestionas, serían:
El primer método se usa al obtener entidades específicas, el segundo método se usa específicamente al agregar o editar entidades en páginas web.
Esto nos permite tener lo mejor de ambos mundos en el contexto donde se usan.
fuente
Soy un estudiante francés de informática, así que perdona mi pobre inglés. En nuestras clases se nos dice que dicho método nunca debe devolver nulo, ni un objeto vacío. Se supone que el usuario de este método debe verificar primero que el objeto que está buscando existe antes de intentar obtenerlo.
Al usar Java, se nos pide que agreguemos un
assert exists(object) : "You shouldn't try to access an object that doesn't exist";
al principio de cualquier método que pueda devolver nulo, para expresar la "condición previa" (no sé cuál es la palabra en inglés).En mi opinión, esto no es realmente fácil de usar, pero eso es lo que estoy usando, esperando algo mejor.
fuente
Si el caso de que el usuario no ser descubierto se acerca bastante a menudo, y desea que lidiar con eso de varias maneras dependiendo de las circunstancias (a veces lanzando una excepción, a veces la sustitución de un usuario vacío) también se podría utilizar algo cercano a F # 's
Option
o Haskell deMaybe
tipo , que separa explícitamente el caso 'sin valor' de '¡encontró algo!'. El código de acceso a la base de datos podría verse así:Y se usa así:
Desafortunadamente, todo el mundo parece tener un tipo como este.
fuente
Normalmente vuelvo nulo. Proporciona un mecanismo rápido y fácil para detectar si algo se estropeó sin lanzar excepciones y usar toneladas de try / catch en todo el lugar.
fuente
Para los tipos de colección, devolvería una Colección vacía, para todos los demás tipos, prefiero usar los patrones de NullObject para devolver un objeto que implemente la misma interfaz que la del tipo de retorno. para obtener detalles sobre el patrón, consulte el texto del enlace
Usando el patrón NullObject esto sería: -
{// Imagina un código aquí para acceder a la base de datos .....
}
fuente
Para decir lo que otros han dicho de una manera más concisa ...
Las excepciones son por circunstancias excepcionales
Si este método es pura capa de acceso a datos, diría que dado algún parámetro que se incluye en una instrucción select, esperaría que no encuentre ninguna fila desde la cual construir un objeto y, por lo tanto, devolver nulo sería aceptable como esto es la lógica de acceso a datos.
Por otro lado, si esperaba que mi parámetro reflejara una clave primaria y solo recuperase una fila, si obtuviera más de una, arrojaría una excepción. 0 está bien para devolver nulo, 2 no lo está.
Ahora, si tuviera algún código de inicio de sesión que verificara con un proveedor LDAP y luego con una base de datos para obtener más detalles y esperaba que deberían estar sincronizados en todo momento, entonces podría lanzar la excepción. Como otros dijeron, son las reglas de negocios.
Ahora diré que es una regla general . Hay momentos en los que es posible que desee romper eso. Sin embargo, mi experiencia y experimentos con C # (mucho de eso) y Java (un poco de eso) me han enseñado que es mucho más costoso manejar las excepciones que manejar problemas predecibles a través de la lógica condicional. Estoy hablando de la melodía de 2 o 3 órdenes de magnitud más cara en algunos casos. Entonces, si es posible, su código podría terminar en un bucle, entonces recomendaría devolver nulo y probarlo.
fuente
Perdona mi pseudo-php / code.
Creo que realmente depende del uso previsto del resultado.
Si tiene la intención de editar / modificar el valor de retorno y guardarlo, devuelva un objeto vacío. De esa manera, puede usar la misma función para llenar datos en un objeto nuevo o existente.
Digamos que tengo una función que toma una clave primaria y una matriz de datos, llena la fila con datos y luego guarda el registro resultante en la base de datos. Como tengo la intención de llenar el objeto con mis datos de cualquier manera, puede ser una gran ventaja recuperar un objeto vacío del getter. De esa manera, puedo realizar operaciones idénticas en cualquier caso. Utiliza el resultado de la función getter sin importar qué.
Ejemplo:
Aquí podemos ver que la misma serie de operaciones manipula todos los registros de este tipo.
Sin embargo, si la intención final del valor de retorno es leer y hacer algo con los datos, entonces devolvería nulo. De esta manera, puedo determinar rápidamente si no se devolvieron datos y mostrar el mensaje apropiado al usuario.
Por lo general, detectaré excepciones en mi función que recupera los datos (para poder registrar mensajes de error, etc.) y luego devuelvo nulo directamente desde la captura. Por lo general, no le importa al usuario final cuál es el problema, por lo que creo que es mejor encapsular mi registro / procesamiento de errores directamente en la función que obtiene los datos. Si mantiene una base de código compartida en cualquier empresa grande, esto es especialmente beneficioso porque puede forzar el registro / manejo de errores adecuado incluso en el programador más vago.
Ejemplo:
Esa es mi regla general. Ha funcionado bien hasta ahora.
fuente
Pregunta interesante y creo que no hay una respuesta "correcta", ya que siempre depende de la responsabilidad de su código. ¿Su método sabe si los datos encontrados no son un problema o no? En la mayoría de los casos, la respuesta es "no" y es por eso que volver nulo y dejar que la persona que llama maneje su situación es perfecta.
Quizás un buen enfoque para distinguir los métodos de lanzamiento de los métodos de retorno nulo es encontrar una convención en su equipo: los métodos que dicen que "obtienen" algo deberían generar una excepción si no hay nada que obtener. Los métodos que pueden devolver nulo podrían denominarse de manera diferente, quizás "Buscar ..." en su lugar.
fuente
Si el objeto devuelto es algo que se puede iterar, devolvería un objeto vacío, para que no tenga que probar nulo primero.
Ejemplo:
fuente
Me gusta no devolver nulo de ningún método, sino usar el tipo funcional Opción en su lugar. Los métodos que no pueden devolver ningún resultado devuelven una Opción vacía, en lugar de nula.
Además, los métodos que no pueden devolver ningún resultado deben indicarlo a través de su nombre. Normalmente pongo Try o TryGet o TryFind al comienzo del nombre del método para indicar que puede devolver un resultado vacío (por ejemplo, TryFindCustomer, TryLoadFile, etc.).
Eso le permite a la persona que llama aplicar diferentes técnicas, como la canalización de la colección (consulte la tubería de la colección de Martin Fowler ) en el resultado.
Aquí hay otro ejemplo en el que se usa la opción Devolución en lugar de nula para reducir la complejidad del código: Cómo reducir la complejidad ciclomática: Tipo funcional de la opción
fuente
Más carne para moler: digamos que mi DAL devuelve un NULL para GetPersonByID según lo aconsejado por algunos. ¿Qué debe hacer mi BLL (más bien delgado) si recibe un NULL? ¿Pasar ese NULL y dejar que el consumidor final se preocupe por ello (en este caso, una página ASP.Net)? ¿Qué tal si el BLL lanza una excepción?
ASP.Net y Win App, u otra biblioteca de clase, pueden estar utilizando BLL. Creo que es injusto esperar que el consumidor final "sepa" intrínsecamente que el método GetPersonByID devuelve un valor nulo (a menos que se utilicen tipos nulos, supongo )
Mi opinión (por lo que vale) es que mi DAL devuelve NULL si no se encuentra nada. PARA ALGUNOS OBJETOS, está bien, podría ser un 0: muchas listas de cosas, por lo que no tener nada está bien (por ejemplo, una lista de libros favoritos). En este caso, mi BLL devuelve una lista vacía. Para la mayoría de las cosas de una sola entidad (por ejemplo, usuario, cuenta, factura) si no tengo una, entonces definitivamente es un problema y una excepción costosa. Sin embargo, dado que la recuperación de un usuario mediante un identificador único que la aplicación ha proporcionado previamente siempre debe devolver un usuario, la excepción es una excepción "adecuada", ya que es excepcional. El consumidor final de BLL (ASP.Net, f'rinstance) solo espera que las cosas sean muy buenas, por lo que se usará un controlador de excepciones no manejadas en lugar de ajustar cada llamada a GetPersonByID en un bloque try-catch.
Si hay un problema evidente en mi enfoque, avíseme ya que siempre estoy ansioso por aprender. Como han dicho otros carteles, las excepciones son costosas, y el enfoque de "comprobar primero" es bueno, pero las excepciones deberían ser solo eso: excepcional.
Estoy disfrutando de esta publicación, muchas buenas sugerencias para escenarios de "depende" :-)
fuente
Creo que las funciones no deberían devolver nulo, por la salud de su base de código. Se me ocurren algunas razones:
Habrá una gran cantidad de cláusulas de protección que tratan la referencia nula
if (f() != null)
.Que es
null
, es una respuesta aceptada o un problema? ¿Es nulo un estado válido para un objeto específico? (imagina que eres un cliente para el código). Quiero decir que todos los tipos de referencia pueden ser nulos, pero ¿deberían?Tener que
null
pasar el rato casi siempre dará algunas excepciones inesperadas de NullRef de vez en cuando a medida que crece su base de código.Hay algunas soluciones,
tester-doer pattern
o la implementación de laoption type
programación funcional.fuente
Estoy perplejo por la cantidad de respuestas (en toda la web) que dicen que necesita dos métodos: un método "IsItThere ()" y un método "GetItForMe ()" y esto conduce a una condición de carrera. ¿Qué tiene de malo una función que devuelve un valor nulo, que se asigna a una variable y que verifica la variable para obtener un valor nulo en una sola prueba? Mi antiguo código C estaba salpicado de
if (NULL! = (variable = función (argumentos ...))) {
Entonces obtienes el valor (o nulo) en una variable y el resultado de una vez. ¿Se ha olvidado este idioma? ¿Por qué?
fuente
Estoy de acuerdo con la mayoría de las publicaciones aquí, que tienden hacia
null
.Mi razonamiento es que generar un objeto vacío con propiedades no anulables puede causar errores. Por ejemplo, una entidad con una
int ID
propiedad tendría un valor inicial deID = 0
, que es un valor completamente válido. Si ese objeto, bajo alguna circunstancia, se guarda en la base de datos, sería algo malo.Para cualquier cosa con un iterador, siempre usaría la colección vacía. Algo como
es código olor en mi opinión. Las propiedades de la colección no deben ser nulas, nunca.
Un caso de borde es
String
. Mucha gente diceString.IsNullOrEmpty
que no es realmente necesario, pero no siempre se puede distinguir entre una cadena vacía y una nula. Además, algunos sistemas de bases de datos (Oracle) no distinguen entre ellos (''
se almacenan comoDBNULL
), por lo que se ve obligado a manejarlos por igual. La razón de esto es que la mayoría de los valores de cadena provienen de la entrada del usuario o de sistemas externos, mientras que ni los cuadros de texto ni la mayoría de los formatos de intercambio tienen representaciones diferentes para''
ynull
. Entonces, incluso si el usuario desea eliminar un valor, no puede hacer nada más que borrar el control de entrada. También la distinción de anulable y no anulablenvarchar
campos de bases de datos es más que cuestionable, si su DBMS no es oráculo, un campo obligatorio que permite''
es extraño, su IU nunca lo permitiría, por lo que sus restricciones no se asignan. Entonces la respuesta aquí, en mi opinión es, manejarlos por igual, siempre.Con respecto a su pregunta con respecto a las excepciones y el rendimiento: si lanza una excepción que no puede manejar completamente en la lógica de su programa, debe cancelar, en algún momento, lo que sea que esté haciendo su programa, y pedirle al usuario que vuelva a hacer lo que acaba de hacer. En ese caso, la penalización de rendimiento de a
catch
es realmente la menor de sus preocupaciones: tener que preguntar al usuario es el elefante en la sala (lo que significa volver a representar toda la interfaz de usuario o enviar algo de HTML a través de Internet). Por lo tanto, si no sigue el antipatrón de " Flujo de programa con excepciones ", no se moleste, simplemente tire uno si tiene sentido. Incluso en casos límite, como "Excepción de validación", el rendimiento realmente no es un problema, ya que debe preguntar al usuario nuevamente, en cualquier caso.fuente
Un patrón asincrónico TryGet:
Para los métodos sincrónicos, creo que la respuesta de @Johann Gerell es el patrón a utilizar en todos los casos.
Sin embargo, el patrón TryGet con el
out
parámetro no funciona con los métodos asíncronos.Con Tuple Literals de C # 7 ahora puedes hacer esto:
fuente