¿Es mejor devolver valores NULL o vacíos de funciones / métodos donde el valor de retorno no está presente?

127

Estoy buscando una recomendación aquí. Estoy teniendo problemas 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() , yo diría que devolver una cadena vacía porque el tipo de devolución 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 para ver si se devolvió un NULL.

En FindPerson() , devolver NULL parece ser un mejor ajuste. Independientemente de si se devuelve o no NULL o un objeto de persona vacío ( new Person() ), la persona que llama tendrá que verificar si el objeto de persona es NULL o está vacío antes de hacerle algo (como llamar a UpdateName() ). Entonces, ¿por qué no devolver NULL aquí y luego la persona que llama solo tiene que verificar NULL?

¿Alguien más lucha con esto? Cualquier ayuda o conocimiento es apreciado.

    
pregunta P B 17.11.2011 - 19:34
fuente

17 respuestas

70

StackOverflow tiene una buena discusión sobre este tema exacto en este Q & A . En la pregunta más valorada, kronoz señala:

  

Devolver el valor nulo suele ser la mejor idea si pretende indicar que   no hay datos disponibles.

     

Un objeto vacío implica que se han devuelto datos, mientras que se devuelve nulo   indica claramente que no se ha devuelto nada.

     

Además, devolver un valor nulo resultará en una excepción nula si   intentar acceder a los miembros en el objeto, lo que puede ser útil para   resaltar el código de buggy: 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 pasar desapercibidos.

Personalmente, me gusta devolver cadenas vacías para funciones que devuelven cadenas para minimizar la cantidad de manejo de errores que se debe implementar. Sin embargo, deberá asegurarse de que el grupo con el que trabaja seguirá la misma convención; de lo contrario, no se lograrán los beneficios de esta decisión.

Sin embargo, como se indicó en el póster de la respuesta SO, es probable que se devuelvan nulos 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. Crear un consenso de equipo en última instancia impulsará las mejores prácticas de su equipo.

    
respondido por el JW8 17.11.2011 - 19:42
fuente
77

En todo el código que escribo, evito devolver null desde una función. Lo leí en Clean Code .

El problema con el uso de null es que la persona que usa la interfaz no sabe si null es un resultado posible, y si tienen que verificarlo, porque no hay un tipo de referencia not null .

En F #, puedes devolver un tipo option , que puede ser some(Person) o none , por lo que es obvio para la persona que llama que deben verificar.

El patrón C # (anti-) análogo es el método Try... :

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

Ahora sé que la gente ha dicho que odian el patrón Try... porque tener un parámetro de salida rompe las ideas de una función pura, pero en realidad no es diferente a:

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);

... y para ser honesto, puede suponer que cada .NET programador conoce el patrón Try... porque se usa internamente en el marco .NET. Eso significa que no tienen que leer la documentación para entender lo que hace, lo que es más importante para mí que seguir la visión de las funciones de algunos puristas (entender que result es un parámetro out , no un parámetro ref ).

Así que iría con TryFindPerson porque parece indicar que es perfectamente normal no poder encontrarlo.

Si, por otro lado, no hay una razón lógica por la que la persona que llama proporcione un personId que no existiera, probablemente haría esto:

public Person GetPerson(int personId);

... y luego lanzaría una excepción si no fuera válida. El prefijo Get... implica que la persona que llama sabe que debería tener éxito.

    
respondido por el Scott Whitlock 17.11.2011 - 19:58
fuente
28

Puede probar el patrón de casos especiales de Martin Fowler, de Paterns of Enterprise Application Architecture :

  

Los nulos son cosas incómodas en los programas orientados a objetos porque   derrotar al polimorfismo. Usualmente puedes invocar foo libremente en una variable   referencia de un tipo dado sin preocuparse de si el artículo es   el tipo exacto o una subclase. Con un lenguaje fuertemente tipado puedes   incluso hacer que el compilador compruebe que la llamada es correcta. Sin embargo, desde   una variable puede contener un valor nulo, puede encontrarse con un error de tiempo de ejecución al   invocar un mensaje en nulo, lo que te dará una pila agradable y amigable   traza.

     

Si es posible que una variable sea nula, debes recordar   Rodéelo con un código de prueba nulo, por lo que hará lo correcto si un nulo   está presente. A menudo, lo correcto es lo mismo en muchos contextos, así que terminas   escribiendo código similar en muchos lugares - cometiendo el pecado del código   duplicación.

     

Los nulos son un ejemplo común de tales problemas y otros surgen   regularmente. En los sistemas numéricos tienes que lidiar con el infinito, que tiene   Reglas especiales para cosas como la adición que rompen los invariantes habituales.   de numeros reales. Una de mis primeras experiencias en software empresarial.   fue con un cliente de servicios públicos que no se conocía completamente, conocido como   "ocupante." Todo esto implica alterar el comportamiento habitual de la   tipo.

     

En lugar de devolver un valor nulo, o un valor impar, devuelve un caso especial   que tiene la misma interfaz que espera la persona que llama.

    
respondido por el Thomas Owens 17.11.2011 - 19:48
fuente
14

Pensaría que ReverseString() devolvería la cadena invertida y arrojaría un IllegalArgumentException si se pasara en un Null .

Creo que FindPerson() debería seguir el NullObject Pattern o lanzar una excepción no comprobada sobre no encontrar algo si siempre debe ser capaz de encontrar algo

Tener que lidiar con Null es algo que debe evitarse. Tener Null en idiomas ha sido denominado Error de mil millones de dólares por su inventor!

  

Lo llamo mi error de mil millones de dólares. Fue la invención de la nula.   Referencia en 1965. En ese momento, estaba diseñando el primer   Sistema de tipografía integral para referencias en un objeto orientado.   Idioma (ALGOL W).

     

Mi objetivo era garantizar que todo uso de referencias debería ser absolutamente   Seguro, con verificación realizada automáticamente por el compilador. Pero yo   No pude resistir la tentación de poner una referencia nula, simplemente   Porque era tan fácil de implementar.

     

Esto ha llevado a innumerables errores, vulnerabilidades y sistemas.   choques, que probablemente han causado un billón de dólares de dolor y   Daños en los últimos cuarenta años.

     

En los últimos años, una serie de analizadores de programas como PREfix y PREfast   en Microsoft se han utilizado para verificar referencias y advertencias si   Existe el riesgo de que no sean nulos. Programación más reciente   lenguajes como Spec # han introducido declaraciones para no nulos   referencias Esta es la solución, que rechacé en 1965.

     

Tony Hoare

    
respondido por el Jarrod Roberson 17.11.2011 - 21:24
fuente
11

Devuelva una opción . Todos los beneficios de devolver un valor inválido diferente (como poder tener valores vacíos en sus colecciones) sin ningún riesgo de NullPointerException.

    
respondido por el hugomg 18.11.2011 - 05:10
fuente
7

Veo ambos lados de este argumento y me doy cuenta de que algunas voces bastante influyentes (por ejemplo, Fowler) recomiendan 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 del retorno nulo. Encuentro que hay una distinción importante al invocar un método y responde con No tengo datos y responde con Tengo esta cadena vacía .

Desde que he visto parte de la discusión haciendo referencia a una clase de Persona, considere el escenario en el que intenta buscar una instancia de la clase. Si pasa algún atributo de buscador (por ejemplo, una identificación), un cliente puede verificar inmediatamente si hay un valor nulo 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 algo de rigor por parte del cliente, y no, no creo que eso sea algo malo.

Ahora considere la alternativa a la que devuelve un objeto Person válido ... que no tiene nada en él. ¿Pone nulos en todos sus valores (nombre, dirección, favoriteDrink) o los rellena con objetos válidos pero vacíos? ¿Cómo puede determinar su cliente que no se encontró una Persona real? ¿Deben comprobar si el nombre es una cadena vacía en lugar de nula? ¿Acaso este tipo de cosas no generará tanto o más desorden de código y declaraciones condicionales que si hubiéramos comprobado que no existía ningún valor y seguimos adelante?

Una vez más, hay puntos en ambos lados 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 (lo que hace que el código sea más fácil de mantener).

    
respondido por el RonU 17.11.2011 - 22:12
fuente
6

Devuelve nulo si necesitas saber si el artículo existe o no. De lo contrario, devuelve el tipo de datos esperado. Esto es especialmente cierto si está devolviendo 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 idiomas (¿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:

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

No debería tener que agregar un caso especial para el caso cuando no hay cosas .

    
respondido por el Bryan Oakley 17.11.2011 - 19:43
fuente
5

Depende de la semántica del método. Posiblemente podría especificar, su método acepta solo "No nulo". En Java puedes declarar que usando algunas anotaciones de metadatos:

string ReverseString(@NotNull String stringToReverse)

De alguna manera debes especificar el valor de retorno. Si declara, acepta solo los 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 una persona por su clave principal (y se espera que la persona exista), es mejor lanzar una excepción.

Person FindPerson(int personID)

Si busca a una persona por un id. adivinado (lo que me parece extraño), es mejor que declare ese método como @Nullable.

    
respondido por el Martin Vejmelka 17.11.2011 - 19:44
fuente
3

Recomendaría usar el patrón de objeto nulo siempre que sea posible. Simplifica el código cuando llama 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 devolver null , y la iteración sobre esta colección vacía prácticamente no tendrá penalización de rendimiento. Otro caso sería un método que devuelva la instancia de clase utilizada para registrar datos; en mi opinión, es preferible devolver un objeto nulo en lugar de null , ya que no obliga a los usuarios a verificar siempre si la referencia devuelta es null .

En los casos en que devolver null tiene sentido (por ejemplo, llamar al método findPerson () ), intentaría al menos proporcionar un método que devuelva si el objeto está presente ( personExists (int personId) por ejemplo), otro ejemplo sería el método containsKey () en un Map en Java). Hace que el código de las personas que llaman esté más limpio, 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 es null confunde el código en mi opinión.

    
respondido por el Laf 17.11.2011 - 23:49
fuente
3

null es lo mejor que se puede devolver si, y solo si, se cumplen las siguientes condiciones:

  • el resultado nulo es esperado en la operación normal . Podría esperarse que no pueda encontrar a una persona en algunas circunstancias razonables, por lo que findPerson () devuelve null está bien. Sin embargo, si se trata de un error realmente inesperado (por ejemplo, en una función llamada saveMyImportantData ()), debería lanzar una excepción. Hay una pista en el nombre - ¡Las excepciones son para circunstancias excepcionales!
  • nulo se utiliza para significar "no encontrado / sin valor" . Si te refieres a otra cosa, ¡entonces devuelve otra cosa! Buenos ejemplos serían las operaciones de punto flotante que devuelven Infinity o NaN; estos son valores con significados específicos, por lo que sería muy confuso si devolviera el valor nulo aquí.
  • La función está destinada a devolver un solo valor , como findPerson (). Si fue diseñado para devolver una colección, por ej. findAllOldPeople () entonces una colección vacía es mejor. Un corolario de esto es que una función que devuelve una colección nunca debe devolver nula .

Además, asegúrese de que documente el hecho de que la función puede devolver un valor nulo.

Si sigues estas reglas, los nulos son en su mayoría inofensivos. Tenga en cuenta que si se olvida de verificar el valor nulo, normalmente obtendrá una NullPointerException inmediatamente después, lo que suele ser 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 alrededor de su sistema, posiblemente dañando datos, sin lanzar una excepción.

Finalmente, si aplica estas reglas a las dos funciones enumeradas en la pregunta:

  • FindPerson : sería apropiado que esta función devuelva nulo si no se encuentra a la persona
  • ReverseString : parece que nunca fallaría encontrar un resultado si se pasa una cadena (ya que todas las cadenas pueden invertirse, incluida la cadena vacía). Por eso nunca debe volver nulo. Si algo sale mal (¿se ha quedado sin memoria?), Debería lanzar una excepción.
respondido por el mikera 12.01.2012 - 06:59
fuente
1

En mi opinión, hay una diferencia entre devolver NULL, devolver un 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 f (v1, ..., vn) llamada como la aplicación de una función

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

donde S es el "estado del mundo" T1, ..., Tn son los tipos de parámetros de entrada y T es el tipo de retorno.

Primero intento definir esta función. Si la función es parcial (es decir, hay algunos valores de entrada para los cuales no está definida) Devuelvo NULL para señalar esto. Esto se debe a que quiero que el cómputo finalice normalmente y me diga que la función que he solicitado no está definida en las entradas proporcionadas. Usar, por ejemplo, una cadena vacía como valor de retorno es ambiguo porque podría ser que la función esté definida en las entradas y la cadena vacía sea el resultado correcto.

Creo que la comprobación adicional para un puntero NULO en el código de llamada es necesaria porque está aplicando una función parcial y es la tarea del método llamado informarle si la función no está definida para la entrada dada.

Prefiero usar excepciones para los errores que no permiten realizar el cálculo (es decir, no fue posible encontrar ninguna respuesta).

Por ejemplo, supongamos que tengo un cliente de clase y quiero implementar un método

Customer findCustomer(String customerCode)

para buscar un cliente en la base de datos de la aplicación por su código. En este método, me gustaría

  • Devuelva un objeto de la clase Cliente si la consulta es exitosa,
  • Devuelve nulo si la consulta no encuentra ningún cliente.
  • Lanzar una excepción si no es posible conectarse a la base de datos.

Los controles adicionales para nulos, por ejemplo,

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

son parte de la semántica de lo que estoy haciendo y no me limitaría a "omitirlos" 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 nulos ocurre con mucha frecuencia, es bueno si el lenguaje admite alguna sintaxis especial para él.

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.

    
respondido por el Giorgio 18.11.2011 - 13:56
fuente
0

Los métodos que devuelven colecciones deberían estar vacíos, pero otros podrían devolver un valor nulo, ya que para las colecciones puede suceder que tenga un objeto pero no contenga elementos, por lo que la persona que llama validará solo el tamaño en lugar de ambos.

    
respondido por el Phani 18.11.2011 - 10:50
fuente
0

Para mí hay dos casos aquí. Si está devolviendo algún tipo de lista, siempre debe devolver la lista sin importar qué, vacíelo si no tiene nada para poner.

El único caso en el que veo un debate es cuando devuelves un solo artículo. Me encuentro prefiriendo devolver el valor nulo en caso de fallar 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 necesitaba un objeto real, podrían seguir adelante e intentar usar el objeto nulo que produce un comportamiento inesperado. Creo que es mejor que la rutina crezca 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.

    
respondido por el Loren Pechtel 05.05.2012 - 22:37
fuente
0

Agregando a lo que la gente ya dijo sobre el constructor de tipo Maybe :

Otra gran ganancia, aparte de no arriesgar las NPE, es la semántica. En el mundo funcional, donde tratamos con funciones puras, nos gusta intentar 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 invertir (las cadenas son básicamente conjuntos de caracteres ordenados).

Ahora, ¿qué pasa con 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 el ID dado O devuelve null .

Este es mi problema principal con la devolución de null y la devolución de un objeto nulo. Ellos son engañosos. null no es un cliente, y tampoco es realmente "la ausencia de un cliente". Es la ausencia de algo. Es sólo un puntero sin tipo a NADA. Muy bajo nivel y muy feo y muy propenso a errores. Creo que un objeto nulo también es bastante engañoso. Un cliente nulo ES un cliente. No es la ausencia de uno, no es el cliente con el ID solicitado, por lo que devolverlo es simplemente INCORRECTO. Este NO es el cliente que pedí, pero aún así usted reclama que devolvió un cliente. Tiene la identificación incorrecta, claramente esto es un error. Rompe el contrato sugerido por el nombre y la firma del método. Requiere que el código del cliente asuma cosas sobre su diseño o lea la documentación.

Ambos problemas se resuelven mediante un Maybe Customer . De repente, no estamos diciendo que "esta función representa al cliente con el ID proporcionado". Estamos diciendo que "esta función representa al cliente con la ID dada si existe . Ahora es muy clara y explícita sobre lo que hace. Si solicita al cliente con una ID no existente, recibirá Nothing . ¿Qué es esto mejor que null ? No solo para el riesgo de NPE. Pero también porque el tipo de Nothing no es solo Maybe . Es Maybe Customer . Tiene significado semántico. significa específicamente "la ausencia de un cliente", lo que indica que tal cliente no existe.

Otro problema con null es, por supuesto, que es ambiguo. ¿Es que no encontró al cliente o hubo un error de conexión a la 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 yo prefiero de esta manera) podría devolver un Either CustomerLoadError Customer , representando algo que salió mal o el cliente devuelto. Tiene todas las ventajas de Maybe Customer a la vez que le permite especificar qué puede salir mal.

Lo principal que busco es capturar el contrato de la función en su firma. No asuma cosas, no confíe en convenciones fugaces, sea explícito.

    
respondido por el sara 29.05.2016 - 08:02
fuente
0

1) Si la función semántica es que no puede devolver nada, la persona que llama DEBE probarlo, de lo contrario, por ejemplo. toma tu dinero y dáselo a nadie.

2) Es bueno hacer funciones que semánticamente siempre devuelvan algo (o lancen).

Con un registrador , tiene sentido devolver el registrador que no registra si no se ha definido ningún registrador. Cuando se devuelve la recopilación, casi nunca tiene sentido (excepto en "nivel suficientemente bajo", donde la recopilación misma es LA información, no lo que contiene) para no devolver nada, porque un conjunto vacío es un conjunto, no nada.

Con un ejemplo de persona , me gustaría "hibernar" (obtener vs cargar, IIRC, pero ahí se complica por los objetos proxy para la carga perezosa) de tener dos funciones, una que devuelve nula y segundo que lanza.

    
respondido por el user470365 18.11.2011 - 10:30
fuente
-1
  • debe devolverse NULL si la aplicación espera que haya datos disponibles pero los datos no están disponibles. Por ejemplo, un servicio que devuelve la CIUDAD en función del código postal debería devolver nulo si no se encuentra la ciudad. La persona que llama puede decidir manejar el nulo o explotar.

  • La lista vacía debe devolverse si hay dos posibilidades. Datos disponible o NO hay datos disponibles. Por ejemplo un servicio que devuelve la (s) CIUDAD (es) si la población es mayor que cierto número. Puede devuelva una lista vacía si no hay datos que satisfagan los criterios dados.

respondido por el java_mouse 04.05.2012 - 17:40
fuente
-2

Devolver NULL es un diseño terrible , en un mundo orientado a objetos. En pocas palabras, el uso NULO lleva a:

  • manejo de errores ad-hoc (en lugar de excepciones)
  • semántica ambigua
  • lento en lugar de fallar rápido
  • pensamiento por computadora vs. pensamiento por objeto
  • objetos mutables e incompletos

Consulte esta publicación del blog para obtener una explicación detallada: enlace

    
respondido por el yegor256 13.05.2014 - 09:57
fuente