Estoy buscando una recomendación aquí. Estoy luchando con si es mejor devolver NULL o un valor vacío de un método cuando el valor de retorno no está presente o no se puede determinar.
Tome los siguientes dos métodos como ejemplos:
string ReverseString(string stringToReverse) // takes a string and reverses it.
Person FindPerson(int personID) // finds a Person with a matching personID.
En ReverseString()
, diría que devuelve una cadena vacía porque el tipo de retorno es una cadena, por lo que la persona que llama está esperando eso. Además, de esta manera, la persona que llama no tendría que verificar si se devolvió un NULL.
En FindPerson()
, regresar NULL parece un mejor ajuste. Independientemente de si new Person()
se devuelve o no NULL o un Objeto de persona vacío ( ), la persona que llama tendrá que verificar si el Objeto de persona está NULO o vacío antes de hacerle algo (como llamar UpdateName()
). Entonces, ¿por qué no simplemente devolver NULL aquí y luego la persona que llama solo tiene que verificar NULL?
¿Alguien más lucha con esto? Cualquier ayuda o idea es apreciada.
Respuestas:
StackOverflow tiene una buena discusión sobre este tema exacto en estas preguntas y respuestas . En la pregunta mejor calificada, kronoz señala:
Personalmente, me gusta devolver cadenas vacías para funciones que devuelven cadenas para minimizar la cantidad de manejo de errores que debe implementarse. Sin embargo, deberá asegurarse de que el grupo con el que está trabajando seguirá la misma convención; de lo contrario, no se lograrán los beneficios de esta decisión.
Sin embargo, como señaló el póster en la respuesta SO, los valores nulos probablemente deberían devolverse si se espera un objeto para que no haya dudas sobre si se están devolviendo datos.
Al final, no hay una mejor manera de hacer las cosas. Construir un consenso de equipo conducirá en última instancia las mejores prácticas de su equipo.
fuente
for x in list_of_things() {...}
podría decirse que es más rápido que asimilarlol = list_of_things(); if l != null {...}
emit(user.hometown)
queif user.hometown == null { emit("") else {emit(user.hometown)}
En todo el código que escribo, evito regresar
null
de una función. Lo leí en Clean Code .El problema con el uso
null
es que la persona que usa la interfaz no sabe sinull
es un posible resultado y si tiene que verificarlo, porque no hay unnot null
tipo de referencia.En F # puede devolver un
option
tipo, que puede sersome(Person)
onone
, por lo que es obvio para la persona que llama que tienen que verificar.El patrón análogo de C # (anti-) es el
Try...
método:Ahora sé que las personas han dicho que odian el
Try...
patrón porque tener un parámetro de salida rompe las ideas de una función pura, pero en realidad no es diferente de:... y para ser sincero, puede suponer que todos los programadores .NET conocen el
Try...
patrón porque el marco .NET lo utiliza internamente. Eso significa que no tienen que leer la documentación para comprender lo que hace, lo cual es más importante para mí que apegarse a la visión de las funciones de algunos puristas (entender queresult
es unout
parámetro, no unref
parámetro).Así que iría
TryFindPerson
porque pareces indicar que es perfectamente normal no poder encontrarlo.Si, por otro lado, no hay una razón lógica para que la persona que llama alguna vez proporcione un mensaje
personId
que no existe, probablemente haría esto:... y luego lanzaría una excepción si no fuera válida. El
Get...
prefijo implica que la persona que llama sabe que debe tener éxito.fuente
Puede probar el patrón de casos especiales de Martin Fowler , de Paterns of Enterprise Application Architecture :
fuente
Creo
ReverseString()
que devolvería la cadena invertida y arrojaría unIllegalArgumentException
si se pasara en aNull
.Creo que
FindPerson()
debería seguir el Patrón NullObject o generar una excepción no verificada sobre no encontrar algo si siempre debe poder encontrar algo.Tener que lidiar
Null
es algo que debe evitarse. ¡TenerNull
en idiomas ha sido llamado un error de mil millones de dólares por su inventor!fuente
Devolver una opción . Todos los beneficios de devolver un valor no válido diferente (como poder tener valores vacíos en sus colecciones) sin ningún riesgo de NullPointerException.
fuente
boost::optional<T>
Veo ambos lados de este argumento, y me doy cuenta de que algunas voces bastante influyentes (por ejemplo, Fowler) abogan por no devolver nulos para mantener el código limpio, evitar bloques adicionales de manejo de errores, etc.
Sin embargo, tiendo a ponerme del lado de los defensores de volver nulo. Encuentro que hay una distinción importante al invocar un método y al responder con no tengo ningún dato y al responder con esta cadena vacía .
Como he visto parte de la discusión que hace referencia a una clase de Persona, considere el escenario en el que intenta buscar una instancia de la clase. Si pasa algún atributo del buscador (por ejemplo, una ID), un cliente puede verificar inmediatamente si hay valores nulos para ver si no se encontró ningún valor. Esto no es necesariamente excepcional (por lo tanto, no necesita excepciones), pero también debe documentarse claramente. Sí, esto requiere un poco de rigor por parte del cliente, y no, no creo que sea algo malo en absoluto.
Ahora considere la alternativa donde devuelve un objeto Persona válido ... que no tiene nada. ¿Pones nulos en todos sus valores (nombre, dirección, bebida favorita), o ahora llenas esos con objetos válidos pero vacíos? ¿Cómo puede determinar ahora su cliente que no se encontró ninguna persona real? ¿Necesitan verificar si el nombre es una cadena vacía en lugar de nula? ¿Acaso este tipo de cosas en realidad no conducirá a un desorden de código o más declaraciones condicionales que si simplemente hubiéramos verificado si era nulo y continuamos?
Una vez más, hay puntos a cada lado de este argumento con los que podría estar de acuerdo, pero creo que esto tiene más sentido para la mayoría de las personas (haciendo que el código sea más fácil de mantener).
fuente
FindPerson
, oGetPerson
devolver un valor nulo o una excepción si no existe la clave?" Como usuario de la API simplemente no lo sé y tengo que verificar la documentación. Por otro lado,bool TryGetPerson(Guid key, out Person person)
no requiere que verifique la documentación y sé que no se produce una excepción en lo que equivale a una circunstancia no excepcional. Ese es el punto que estoy tratando de hacer en mi respuesta.findPerson
), es absolutamente normal no obtener ningún resultado. Esto no debería conducir a una excepción.Devuelva nulo si necesita saber si el artículo existe o no. De lo contrario, devuelva el tipo de datos esperado. Esto es especialmente cierto si devuelve una lista de elementos. Por lo general, es seguro asumir que si la persona que llama desea una lista, querrá iterar sobre la lista. Muchos (¿la mayoría? ¿Todos?) Fallan si intenta iterar sobre nulo pero no cuando itera sobre una lista que está vacía.
Me resulta frustrante intentar usar un código como este, solo para que falle:
No debería tener que agregar un caso especial para el caso cuando no hay cosas .
fuente
Depende de la semántica del método. Posiblemente podría especificar, su método solo acepta "No nulo". En Java puede declarar que usando algunas anotaciones de metadatos:
De alguna manera, debe especificar el valor de retorno. Si declara, acepta solo valores NotNull, está obligado a devolver una cadena vacía (si la entrada también era una cadena vacía).
El segundo caso es un poco complicado en su semántica. Si este método es devolver a alguna persona por su clave principal (y se espera que la persona exista), la mejor manera es lanzar una excepción.
Si busca a una persona por alguna identificación adivinada (lo cual me parece extraño), será mejor que declare ese método como @Nullable.
fuente
Recomendaría usar el patrón de objeto nulo siempre que sea posible. Simplifica el código al llamar a sus métodos, y no puede leer código feo como
if (someObject != null && someObject.someMethod () != whateverValue)
Por ejemplo, si un método devuelve una colección de objetos que se ajustan a algún patrón, entonces devolver una colección vacía tiene más sentido que regresar
null
, e iterar sobre esta colección vacía prácticamente no tendrá penalización de rendimiento. Otro caso sería un método que devuelve la instancia de clase utilizada para registrar datos, devolviendo un objeto nulo en lugar denull
preferible en mi opinión, ya que no obliga a los usuarios a verificar siempre si la referencia devuelta esnull
.En los casos en que regresar
null
tiene sentido (llamando alfindPerson ()
método, por ejemplo), trataría de proporcionar al menos un método que devuelva si el objeto está presente (personExists (int personId)
por ejemplo), otro ejemplo sería elcontainsKey ()
método en aMap
en Java). Limpia el código de las personas que llaman, ya que puede ver fácilmente que existe la posibilidad de que el objeto deseado no esté disponible (la persona no existe, la clave no está presente en el mapa). Comprobar constantemente si una referencia estánull
ofusca el código en mi opinión.fuente
nulo es lo mejor para devolver si y solo si se aplican las siguientes condiciones:
Además, asegúrese de documentar el hecho de que la función puede devolver nulo.
Si sigue estas reglas, los nulos son en su mayoría inofensivos. Tenga en cuenta que si olvida verificar nulo, normalmente obtendrá una NullPointerException inmediatamente después, que generalmente es un error bastante fácil de solucionar. Este enfoque de falla rápida es mucho mejor que tener un valor de retorno falso (por ejemplo, una cadena vacía) que se propaga silenciosamente por su sistema, posiblemente corrompiendo datos, sin lanzar una excepción.
Finalmente, si aplica estas reglas a las dos funciones enumeradas en la pregunta:
fuente
En mi opinión, hay una diferencia entre devolver NULL, devolver algún resultado vacío (por ejemplo, la cadena vacía o una lista vacía) y lanzar una excepción.
Normalmente tomo el siguiente enfoque. Considero una función o método llamada f (v1, ..., vn) como la aplicación de una función
donde S it el "estado del mundo" T1, ..., Tn son los tipos de parámetros de entrada y T es el tipo de retorno.
Primero trato de definir esta función. Si la función es parcial (es decir, hay algunos valores de entrada para los que no está definida), devuelvo NULL para señalar esto. Esto se debe a que quiero que el cálculo finalice normalmente y me diga que la función que solicité no está definida en las entradas dadas. Usar, por ejemplo, una cadena vacía como valor de retorno es ambiguo porque podría ser que la función se define en las entradas y que la cadena vacía es el resultado correcto.
Creo que la verificación adicional de un puntero NULL en el código de llamada es necesaria porque está aplicando una función parcial y es la tarea del método llamado decirle si la función no está definida para la entrada dada.
Prefiero utilizar excepciones para errores que no permiten realizar el cálculo (es decir, no fue posible encontrar ninguna respuesta).
Por ejemplo, supongamos que tengo una clase Cliente y quiero implementar un método
buscar un cliente en la base de datos de la aplicación por su código. En este método, lo haría
Las comprobaciones adicionales para nulo, por ejemplo
son parte de la semántica de lo que estoy haciendo y no solo los "omitiría" para que el código se lea mejor. No creo que sea una buena práctica simplificar la semántica del problema en cuestión solo para simplificar el código.
Por supuesto, dado que la comprobación de nulo ocurre muy a menudo es bueno si el lenguaje admite alguna sintaxis especial para ello.
También consideraría usar el patrón de objeto nulo (como lo sugiere Laf) siempre que pueda distinguir el objeto nulo de una clase de todos los demás objetos.
fuente
Los métodos que devuelven colecciones deberían devolverse vacíos, pero otros podrían devolver nulo, porque para las colecciones puede suceder que tenga un objeto pero no haya elementos, por lo que el llamante validará solo el tamaño en lugar de ambos.
fuente
Para mí hay dos casos aquí. Si devuelve algún tipo de lista, siempre debe devolver la lista, pase lo que pase, vacía si no tiene nada que agregar.
El único caso en el que veo algún debate es cuando devuelve un solo artículo. Me encuentro prefiriendo volver nulo en caso de falla en tal situación sobre la base de hacer que las cosas fallen rápidamente.
Si devuelve un objeto nulo y la persona que llama necesita un objeto real, puede seguir adelante e intentar usar el objeto nulo produciendo un comportamiento inesperado. Creo que es mejor que la rutina aumente si se olvidan de lidiar con la situación. Si algo está mal, quiero una excepción lo antes posible. Es menos probable que envíe un error de esta manera.
fuente
Agregando a lo que la gente ya dijo sobre el
Maybe
constructor de tipos:Otra gran ganancia, además de no arriesgar NPE, es la semántica. En el mundo funcional, donde tratamos con funciones puras, nos gusta tratar de hacer funciones que, cuando se aplican, sean exactamente equivalentes a su valor de retorno . Considere el caso de
String.reverse()
: Esta es una función que dada una determinada cadena representa la versión invertida de esa cadena. Sabemos que esta cadena existe, porque cada cadena se puede revertir (las cadenas son básicamente conjuntos de caracteres ordenados).Ahora, ¿qué pasa
findCustomer(int id)
? Esta función representa al cliente que tiene la ID dada. Esto es algo que puede o no existir. Pero recuerde, las funciones tienen un solo valor de retorno, por lo que realmente no puede decir "esta función devuelve a un cliente con la ID dada O devuelvenull
.Este es mi problema principal al devolver
null
y devolver un objeto nulo. Son engañososnull
no es un cliente, y tampoco es realmente "la ausencia de un cliente". Es la ausencia de NADA. Es solo un puntero sin tipo a NADA. Muy bajo nivel y muy feo y muy propenso a errores. Un objeto nulo también es bastante engañoso aquí, creo. Un cliente nulo ES un cliente. No es la ausencia de uno, no es el cliente con la identificación solicitada, por lo que devolverlo es simplemente INCORRECTO. Este NO es el cliente que solicité, pero aún así afirma que devolvió un cliente. Tiene la identificación incorrecta, claramente esto es un error. Rompe el contrato sugerido por el nombre del método y la firma. Requiere que el código del cliente asuma cosas sobre su diseño o que lea la documentación.Ambos problemas son resueltos por a
Maybe Customer
. De repente, no estamos diciendo "esta función representa al cliente con la ID dada". Estamos diciendo "esta función representa al cliente con la identificación dada si existe . Ahora es muy claro y explícito sobre lo que hace. Si solicita al cliente con una identificación no existente, recibiráNothing
. ¿Cómo es esto mejor? quenull
? no sólo por el riesgo NPE. Pero también porque el tipo deNothing
no es sóloMaybe
. esMaybe Customer
. tiene un significado semántico. Esto significa específicamente "la ausencia de un cliente", lo que indica que un cliente tal no existe.Otro problema con nulo es, por supuesto, que es ambiguo. ¿Es que no encontró al cliente, o hubo un error de conexión de base de datos o simplemente no puedo ver a ese cliente?
Si tiene que manejar varios casos de error como este, puede lanzar excepciones para esas versiones o (y lo prefiero de esta manera) podría devolver un
Either CustomerLoadError Customer
, que representa algo que salió mal o el cliente devuelto. Tiene todas las ventajas deMaybe Customer
al mismo tiempo que le permite especificar qué puede salir mal.Lo principal que busco es capturar el contrato de la función en su firma. No asumas cosas, no confíes en convenciones fugaces, sé explícito.
fuente
1) Si la función semántica es que no puede devolver nada, la persona que llama DEBE probarlo, de lo contrario, por ejemplo. tome su dinero y no se lo dé a nadie.
2) Es bueno hacer funciones que semánticamente siempre devuelven algo (o tirar).
Con un registrador , tiene sentido devolver un registrador que no se registra si no se definió ningún registrador. Al devolver la colección, casi nunca tiene sentido (excepto en un "nivel lo suficientemente bajo", donde la colección en sí es LOS datos, no lo que contiene) para no devolver nada, porque un conjunto vacío es un conjunto, nada.
Con un ejemplo de persona , iría de manera "hibernada" (get vs load, IIRC, pero allí se complica por los objetos proxy para la carga diferida) de tener dos funciones, una que devuelve nulo y la segunda que arroja.
fuente
NULL debe devolverse si la aplicación espera que haya datos disponibles pero los datos no están disponibles. Por ejemplo, un servicio que devuelve CITY basado en código postal debe devolver nulo si no se encuentra la ciudad. La persona que llama puede decidir manejar el nulo o explotar.
Se debe devolver una lista vacía si hay dos posibilidades. Datos disponibles o NO hay datos disponibles. Por ejemplo, un servicio que devuelve las CIUDAD (s) si la población es mayor que cierto número. Puede devolver una lista vacía si no hay datos que satisfagan los criterios dados.
fuente
Devolver NULL es un diseño terrible , en un mundo orientado a objetos. En pocas palabras, el uso NULL conduce a:
Consulte esta publicación de blog para obtener una explicación detallada: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
fuente