Pregunta

[Esto es el resultado de Mejores prácticas: ¿Deberían las funciones devolver nulo o un objeto vacío? pero estoy tratando de ser muy general. ]

En una gran cantidad de código C ++ heredado (um ... producción) que he visto, hay una tendencia a escribir mucho de NULL (o similar ) verifica los punteros de prueba. Muchos de estos se agregan cerca del final de un ciclo de lanzamiento cuando se agrega una comprobación NULL que proporciona una solución rápida a un bloqueo causado por la desreferencia del puntero, y no hay mucho tiempo para investigar .

Para combatir esto, comencé a escribir código que tomaba un parámetro de referencia ( const ) en lugar de la técnica (mucho) más común de pasar un puntero. Sin puntero, sin deseo de verificar NULL (ignorando el caso de la esquina de tener una referencia nula).

En C #, el mismo problema de C ++ está presente: el deseo de verificar cada referencia desconocida contra null ( ArgumentNullException ) y corregir rápidamente NullReferenceException s agregando un null verificar.

Me parece que una forma de evitar esto es evitar objetos nulos en primer lugar mediante el uso de objetos vacíos ( String.Empty , EventArgs.Empty ) en su lugar . Otra sería lanzar una excepción en lugar de devolver null .

Estoy empezando a aprender F #, pero parece que hay muchos menos objetos nulos en ese entorno. Entonces, ¿tal vez no tenga que tener muchas referencias null flotando?

¿Estoy ladrando el árbol equivocado aquí?

¿Fue útil?

Solución

Tiendo a dudar del código con muchos NULL, y trato de refactorizarlos donde sea posible con excepciones, colecciones vacías, etc.

El " Introducir objeto nulo " El patrón en Refactorización (página 260) también puede ser útil. Un objeto nulo responde a todos los métodos que haría un objeto real, pero de una manera que "hace lo correcto". Entonces, en lugar de verificar siempre un Pedido para ver si order.getDiscountPolicy () es NULL, asegúrese de que el Pedido tenga un NullDiscountPolicy en estos casos. Esto simplifica la lógica de control.

Otros consejos

Pasar no nulo solo para evitar una excepción NullReferenceException es intercambiar un problema sencillo y fácil de resolver ("explota porque es nulo") por un problema mucho más sutil y difícil de depurar ("algo" varias llamadas hacia abajo en la pila no se comportan como se esperaba porque mucho antes recibió algún objeto que no tiene información significativa pero no es nulo ").

¡

NullReferenceException es una cosa maravillosa ! Falla mucho, fuerte, rápido, y casi siempre es rápido y fácil de identificar y corregir. Es mi excepción favorita, porque sé que cuando la vea, mi tarea solo tomará unos 2 minutos. Compare esto con un QA confuso o un informe del cliente que intenta describir un comportamiento extraño que debe reproducirse y rastrearse hasta el origen. ¡Qué asco!

Todo se reduce a lo que usted, como método o fragmento de código, puede inferir razonablemente sobre el código que lo llamó. Si se le entrega una referencia nula, y puede inferir razonablemente lo que la persona que llama podría haber querido decir nulo (¿tal vez una colección vacía, por ejemplo?), Entonces definitivamente debe tratar con los nulos. Sin embargo, si no puede inferir razonablemente qué hacer con un valor nulo, o lo que la persona que llama quiere decir nulo (por ejemplo, el código de llamada le dice que abra un archivo y le da la ubicación como nulo), debe lanzar una excepción ArgumentNullException .

Mantener prácticas de codificación adecuadas como esta en cada " puerta de enlace " punto - límites lógicos de funcionalidad en su código - NullReferenceExceptions debería ser mucho más raro.

Null obtiene mi voto. Por otra parte, soy de la mentalidad de "falla rápida".

String.IsNullOrEmpty (...) también es muy útil, supongo que detecta cualquier situación: cadenas nulas o vacías. Podrías escribir una función similar para todas tus clases que estás pasando.

Si está escribiendo código que devuelve null como condición de error, entonces no lo haga: generalmente, debería lanzar una excepción en su lugar, mucho más difícil de omitir.

Si está consumiendo código que teme que pueda volver nulo, en su mayoría son excepciones descabelladas : quizás haga algunas comprobaciones de Debug.Assert en la persona que llama para verificar la salida durante el desarrollo. No debería realmente necesitar vastos números de cheques null en su producción, pero si alguna biblioteca de terceros devuelve muchos null s de forma impredecible, entonces seguro: haga las comprobaciones.

En 4.0, es posible que desee ver los contratos de código; esto le da un control mucho mejor para decir "este argumento nunca debe pasarse como nulo", "esta función nunca devuelve nulo", etc., y hacer que el sistema valide esas afirmaciones durante el análisis estático (es decir, cuando construye).

Lo que pasa con null es que no tiene significado. Es simplemente la ausencia de un objeto.

Entonces, si realmente quiere decir una cadena / colección / lo que sea vacío, siempre devuelva el objeto relevante y nunca null . Si el idioma en cuestión le permite especificar eso, hágalo.

En el caso de que desee devolver algo que significa que no es un valor que se puede especificar con el tipo estático, tiene varias opciones. Devolver null es una respuesta, pero sin un significado es un poco peligroso. Lanzar una excepción en realidad puede ser lo que quieres decir. Es posible que desee extender el tipo con casos especiales (probablemente con polimorfismo, es decir, el patrón de caso especial (un caso especial del cual es el patrón de objeto nulo)). Es posible que desee ajustar el valor de retorno en un tipo con más significado. O es posible que desee pasar un objeto de devolución de llamada. Por lo general, hay muchas opciones.

Yo diría que depende. Para un método que devuelve un solo objeto, generalmente devolvería nulo. Para un método que devuelve una colección, generalmente devolvería una colección vacía (no nula). Sin embargo, estos están más en la línea de las pautas que las reglas.

Si usted es serio acerca de querer programar en un " null less " entorno, considere usar métodos de extensión con más frecuencia, son inmunes a NullReferenceExceptions y al menos "simulan" que null ya no existe:

public static GetExtension(this string s)
{
    return (new FileInfo(s ?? "")).Extension;
}

que se puede llamar como:

// this code will never throw, not even when somePath becomes null
string somePath = GetDataFromElseWhereCanBeNull();
textBoxExtension.Text = somePath.GetExtension(); 

Lo sé, esto es solo conveniencia y muchas personas lo consideran correctamente una violación de los principios de OO (aunque el "fundador" de OO, Bertrand Meyer, considera que null es malo y lo desterró por completo de su OO diseño, que se aplica al lenguaje Eiffel, pero esa es otra historia). EDITAR: Dan menciona que Bill Wagner ( C # más eficaz ) lo considera una mala práctica y tiene razón. ¿Alguna vez consideró el método de extensión IsNull ;-)?

Para que su código sea más legible, puede haber otra sugerencia: use el operador de fusión nula con más frecuencia para designar un valor predeterminado cuando un objeto es nulo:

// load settings
WriteSettings(currentUser.Settings ?? new Settings());

// example of some readonly property
public string DisplayName
{
    get 
    {
         return (currentUser ?? User.Guest).DisplayName
    }
}

Ninguno de estos elimina la comprobación ocasional de null (y ?? no es más que una rama oculta if). Prefiero la menor cantidad posible de null en mi código, simplemente porque creo que hace que el código sea más legible. Cuando mi código se llena con sentencias if para null , sé que hay algo mal en el diseño y refactorizo. Sugiero que cualquiera haga lo mismo, pero sé que las opiniones varían enormemente al respecto.

(Actualización) Comparación con excepciones

Hasta ahora no se menciona en la discusión la similitud con el manejo de excepciones. Cuando te encuentras ignorando ubicuamente null cada vez que consideras que está en tu camino, es básicamente lo mismo que escribir:

try 
{
    //...code here...
}
catch (Exception) {}

que tiene el efecto de eliminar cualquier rastro de las excepciones solo para descubrir que genera excepciones no relacionadas mucho más tarde en el código. Aunque considero que es bueno evitar usar null , como se mencionó anteriormente en este hilo, tener nulo para casos excepcionales es bueno. Simplemente no los oculte en bloques nulo-ignorar, terminará teniendo el mismo efecto que los bloques de capturar todas las excepciones.

Para los protagonistas de excepciones, generalmente se derivan de la programación transaccional y de fuertes garantías de seguridad de excepción o pautas ciegas. En cualquier complejidad decente, es decir. flujo de trabajo asíncrono, E / S y especialmente código de red son simplemente inapropiados. La razón por la que ve documentos de estilo de Google al respecto en C ++, así como todo el buen código asincrónico 'no lo impone' (piense también en sus grupos administrados favoritos).

Hay más y, aunque puede parecer una simplificación, realmente es así de simple. Por un lado, obtendrás muchas excepciones en algo que no fue diseñado para un uso intensivo de excepciones ... de todos modos, estoy divagando, leí sobre esto de los mejores diseñadores de bibliotecas del mundo, el lugar habitual es el impulso (solo no lo mezcles con el otro campamento en impulso que ama las excepciones, porque tuvieron que escribir software de música :-).

En su caso, y esta no es la experiencia de Fowler, un lenguaje eficiente de 'objeto vacío' solo es posible en C ++ debido al mecanismo de conversión disponible (quizás pero ciertamente no siempre por medio de dominación ). Por otro lado, en su tipo nulo puede lanzar excepciones y hacer lo que quiera mientras preserva el sitio limpio de llamadas y la estructura del código.

En C #, su elección puede ser una sola instancia de un tipo que sea bueno o esté mal formado; como tal, es capaz de lanzar aceptaciones o simplemente correr como está. Por lo tanto, podría o no violar otros contratos (depende de usted lo que considere mejor dependiendo de la calidad del código que esté enfrentando).

Al final, limpia los sitios de llamadas, pero no olvide que enfrentará un choque con muchas bibliotecas (y especialmente regresa de contenedores / diccionarios, los iteradores finales saltan a la mente y cualquier otro código de 'interfaz' para el mundo exterior ). Además, las comprobaciones de valor nulo son piezas de código de máquina extremadamente optimizadas, algo a tener en cuenta, pero estaré de acuerdo cualquier día que el uso de punteros sin comprender la constancia, las referencias y más conducirá a diferentes tipos de problemas de mutabilidad, alias y perf. .

Para agregar, no hay una viñeta plateada, y chocar con una referencia nula o usar una referencia nula en el espacio administrado, o lanzar y no manejar una excepción es un problema idéntico, a pesar de que el mundo administrado y de excepción tratará de venderte. Cualquier entorno decente ofrece protección contra ellos (diablos, puede instalar cualquier filtro en el sistema operativo que desee, ¿qué más cree que hacen las máquinas virtuales?), Y hay tantos otros vectores de ataque que este ha sido destruido en pedazos. Ingrese la verificación x86 de Google una vez más, su propia forma de hacer mucho más rápido y mejor 'IL', código amigable 'dinámico', etc.

Sigue tu instinto, pondera los pros y los contras y localiza los efectos ... en el futuro tu compilador optimizará todas esas comprobaciones de todos modos, y de manera mucho más eficiente que cualquier método humano en tiempo de ejecución o tiempo de compilación (pero no como fácilmente para la interacción entre módulos).

Intento evitar volver nulo de un método siempre que sea posible. En general, hay dos tipos de situaciones: cuando un resultado nulo sería legal y cuándo nunca debería suceder.

En el primer caso, cuando ningún resultado es legal, hay varias soluciones disponibles para evitar resultados nulos y comprobaciones nulas asociadas con ellos: Patrón de objeto nulo y El patrón de caso especial está ahí para devolver objetos sustitutos que no hacen nada o hacen algo específico en circunstancias específicas.

Si es legal no devolver ningún objeto, pero todavía no hay sustitutos adecuados en términos de Objeto nulo o Caso especial, entonces normalmente uso el Tipo funcional de la opción - Entonces puedo devolver una opción vacía cuando no hay un resultado legal. Entonces depende del cliente ver cuál es la mejor manera de lidiar con la opción vacía.

Finalmente, si no es legal que un método devuelva ningún objeto, simplemente porque el método no puede producir su resultado si falta algo, entonces elijo lanzar una excepción y cortar más la ejecución.

No siempre se puede devolver un objeto vacío, porque 'vacío' no siempre está definido. Por ejemplo, ¿qué significa que un int, float o bool esté vacío?

Devolver un puntero NULL no es necesariamente una mala práctica, pero creo que es una mejor práctica devolver una referencia (const) (donde tiene sentido hacerlo, por supuesto).

Y recientemente he usado a menudo una clase Fallible:

Fallible<std::string> theName = obj.getName();
if (theName)
{
    // ...
}

Hay varias implementaciones disponibles para dicha clase (consulte Búsqueda de código de Google), También creé el mío .

¿Cómo son los objetos vacíos mejores que los objetos nulos? Solo estás cambiando el nombre del síntoma. El problema es que los contratos para sus funciones están demasiado definidos ''; esta función podría devolver algo útil, o podría devolver un valor ficticio '' (donde el valor ficticio puede ser nulo, un " objeto vacío " ;, o una constante mágica como -1 . Pero no importa cómo exprese este valor ficticio, las personas que llaman aún tienen que verificarlo antes de que usen el valor de retorno.

Si desea limpiar su código, la solución debería ser reducir la función para que no devuelva un valor ficticio en primer lugar.

Si tiene una función que podría devolver un valor, o podría no devolver nada, entonces los punteros son una forma común (y válida) de expresar esto. Pero a menudo, su código se puede refactorizar para eliminar esta incertidumbre. Si puede garantizar que una función devuelve algo significativo, las personas que llaman pueden confiar en que devuelva algo significativo, y luego no tienen que verificar el valor devuelto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top