¿Por qué la "referencia de objeto no establecida en una instancia de un objeto" no nos dice qué objeto?

39

Estamos lanzando un sistema, y ​​a veces recibimos la famosa excepción NullReferenceExceptioncon el mensaje Object reference not set to an instance of an object.

Sin embargo, en un método en el que tenemos casi 20 objetos, tener un registro que dice que un objeto es nulo, realmente no sirve de nada. Es como decirte, cuando eres el agente de seguridad de un seminario, que un hombre entre 100 asistentes es un terrorista. Eso no te sirve de nada. Debería obtener más información, si desea detectar qué hombre es el hombre amenazante.

Del mismo modo, si queremos eliminar el error, necesitamos saber qué objeto es nulo.

Ahora, algo me ha obsesionado durante varios meses, y eso es:

¿Por qué .NET no nos da el nombre, o al menos el tipo de referencia de objeto, que es nulo? . ¿No puede entender el tipo de reflexión o cualquier otra fuente?

Además, ¿cuáles son las mejores prácticas para comprender qué objeto es nulo? ¿Deberíamos siempre probar manualmente la nulabilidad de los objetos en estos contextos y registrar el resultado? ¿Hay una mejor manera?

Actualización: la excepción The system cannot find the file specifiedtiene la misma naturaleza. No puede encontrar qué archivo, hasta que lo adjunte al proceso y lo depure. Supongo que este tipo de excepciones pueden volverse más inteligentes. ¿No sería mejor si .NET pudiera contarnos en c:\temp.txt doesn't exist.lugar de ese mensaje general? Como desarrollador, voto sí.

Saeed Neamati
fuente
14
La excepción debe incluir un seguimiento de pila con número de línea. Comenzaría mi investigación desde allí mirando cada objeto al que se accede en esa línea.
PersonalNexus
2
Además, siempre me he preguntado por qué el cuadro de diálogo auxiliar de excepción en Visual Studio incluye la sugerencia "útil" para usar newpara crear instancias de una clase. ¿Cuándo ayuda realmente tal pista?
PersonalNexus
1
Si tiene una cadena de diez llamadas a objetos, entonces tiene un problema con el acoplamiento en su diseño.
Pete Kirkham
9
Me encanta cómo cada respuesta a esta pregunta está en la línea de "Use un depurador, registre sus errores, compruebe si es nulo, de todos modos es su culpa", que no responden la pregunta sino que le echan la culpa a usted. Solo en stackoverflow alguien realmente te da una respuesta (lo que creo que dice que es demasiada sobrecarga para que la VM pueda realizar un seguimiento). Pero en realidad, las únicas personas que pueden responder esta pregunta correctamente es alguien de Microsoft que trabajó en el marco.
Rocklan

Respuestas:

28

El NullReferenceExceptionbásicamente te dice: lo está haciendo mal. Nada más y nada menos. No es una herramienta de depuración completa, por el contrario. En este caso, diría que lo estás haciendo mal tanto porque

  • hay una NullReferenceException
  • no lo evitaste de una manera que sabes por qué / dónde sucedió
  • y también tal vez: un método que requiere 20 objetos parece un poco fuera de lugar

Soy un gran admirador de verificar todo antes de que las cosas empiecen a salir mal y de proporcionar buena información al desarrollador. En resumen: escriba cheques usando ArgumentNullExceptiony los me gusta y escriba el nombre usted mismo. Aquí hay una muestra:

void Method(string a, SomeObject b)
{
    if (a == null) throw ArgumentNullException("a");
    if (b == null) throw ArgumentNullException("b");

    // See how nice this is, and what peace of mind this provides? As long as
    // nothing modifies a or b you can use them here and be 100% sure they're not
    // null. Should they be when entering the method, at least you know which one
    // is null.
    var c = FetchSomeObject();
    if(c == null)
    {
        throw InvalidOperationException("Fetching B failed!!");
    }

    // etc.
}

También puede buscar contratos de código , tiene peculiaridades, pero funciona bastante bien y le ahorra algo de tipeo.

stijn
fuente
17
@SaeedNeamati, no estás insinuando que, debido a que tienes una gran base de código, no deberías hacer una comprobación de errores decente, ¿verdad? Mientras más grande sea el proyecto, más importantes serán los roles o la verificación de errores y los informes.
stijn
66
+1 para un buen consejo, incluso si no responde la pregunta real (que probablemente solo Anders Hejlsberg puede responder).
Ross Patterson
12
+1 por excelentes consejos. @SaeedNeamati deberías escuchar este consejo. Su problema es causado por descuido y falta de profesionalismo en el código. Si no tiene tiempo para escribir un buen código, tiene problemas mucho mayores ...
MattDavey
3
Si no tiene tiempo para escribir un buen código, entonces definitivamente no tiene tiempo para escribir un mal código. Escribir un código incorrecto lleva más tiempo que escribir un código correcto. A menos que realmente no te importen los errores. Y si realmente no te importan los errores, ¿por qué escribir el programa?
MarkJ
55
@SaeedNeamati en I only say that checking every object to get sure that it's not null, is not a good methodserio. Es el mejor método. Y no solo nulo, verifique cada argumento para valores razonables. Cuanto antes detecte errores, más fácil será encontrar la causa. No desea tener que retroceder varios niveles en el seguimiento de la pila para encontrar la llamada que causa.
jgauffin
19

Realmente debería mostrar exactamente lo que está tratando de llamar. Es como decir "Hay un problema. Necesitas solucionarlo. Sé lo que es. No te lo voy a decir. Ve a resolverlo". Irónicamente, como la mitad de las respuestas en este Desbordamiento de pila.

Entonces, ¿qué tan útil sería, por ejemplo, si tuviera esto ...

Object reference (HttpContext.Current) not set to instance of an object

...? Para tener que entrar en el código, recorrerlo y descubrir que lo que está tratando de llamar nullestá bien, pero ¿por qué no solo darnos un poco de ayuda?

Estoy de acuerdo en que generalmente es útil recorrer el código para llegar a la respuesta (porque probablemente descubrirá más), pero a menudo se ahorraría mucho tiempo y frustración si el NullReferenceExceptiontexto fuera más parecido al ejemplo anterior.

Sólo digo.

Piscinas de hígado Número 9
fuente
Sin embargo, ¿cómo se supone que el tiempo de ejecución sabe lo que es nulo?
Será
3
El tiempo de ejecución tiene más información de la que proporciona. Cualquier información útil que tenga, debe proporcionar. Eso es todo lo que digo. En respuesta a su pregunta, ¿por qué no lo sabe? Si conoce la respuesta, puede proporcionar un comentario mejor que el que tiene.
LiverpoolsNumber9
De acuerdo, y diría lo mismo por KeyNotFoundExceptiony muchas otras molestias ...
sinelaw
En realidad, en el caso de anulación de referencia nula, esto parece una limitación en la forma en que CLR desreferencia (gracias al comentario de Shahrooz Jefri sobre la pregunta del OP)
sinelaw
1
blogs.msdn.microsoft.com/dotnet/2016/08/02/… ¡FINALMENTE!
LiverpoolsNumber9
4

Su registro debe incluir un seguimiento de la pila, que generalmente le da una pista sobre qué línea del método tiene el problema. Es posible que deba hacer que su versión de lanzamiento incluya símbolos PDB para que tenga una idea de en qué línea se encuentra el error.

Por supuesto, no te ayudará en este caso:

Foo.Bar.Baz.DoSomething()

El principio tell don't ask puede ayudar a evitar dicho código.

En cuanto a por qué no se incluye la información, no estoy seguro: sospecho que al menos en una compilación de depuración, si realmente quisieran, podrían resolverlo. Hacer un volcado por caída y abrir en WinDBG puede ayudar.

Paul Stovell
fuente
2

Se han creado excepciones como una herramienta para señalar condiciones excepcionales no fatales en la cadena de llamadas. Es decir, no están diseñados como una herramienta de depuración.

Si una excepción de puntero nulo fuera una herramienta de depuración, anularía la ejecución del programa en el acto, permitiendo que se conecte un depurador, apuntándolo directamente a la línea incriminatoria. Esto le daría al programador toda la información de contexto disponible. (Lo cual es más o menos lo que hace un Segfault debido a un acceso de puntero nulo en C, aunque un poco groseramente).

Sin embargo, la excepción de puntero nulo está diseñada como una condición de tiempo de ejecución válida que se puede generar y capturar en el flujo normal del programa. En consecuencia, deben tenerse en cuenta las consideraciones de rendimiento. Y cualquier personalización del mensaje de excepción requiere que se creen, concatenen y destruyan objetos de cadena en tiempo de ejecución. Como tal, un mensaje estático es indiscutiblemente más rápido.

Sin embargo, no estoy diciendo que el tiempo de ejecución no pueda programarse de una manera que produzca el nombre de la referencia incriminatoria. Eso se podría hacer. Simplemente haría excepciones aún más lentas de lo que son. Si alguien se preocupara lo suficiente, tal característica podría incluso hacerse conmutable, de modo que no ralentizaría el código de producción, pero permitiría una depuración más fácil; pero por alguna razón a nadie parece importarle lo suficiente.

cmaster
fuente
1

Creo que Cromulent golpeó el clavo en la cabeza, sin embargo, también existe el punto obvio de que si está obteniendo una NullReferenceException, tiene variables no inicializadas. El argumento de que tiene alrededor de 20 objetos que se pasan a un método no se puede decir que sea una mitigación: como creador de un fragmento de código, debe ser responsable de sus acciones, lo que incluye su cumplimiento con el resto de una base de código, como así como la correcta y correcta utilización de variables, etc.

es oneroso, tedioso y a veces aburrido, pero las recompensas al final valen la pena: muchas veces he tenido que rastrear archivos de registro que pesan varios gigabytes, y casi siempre son útiles. Sin embargo, antes de llegar a esa etapa, el depurador puede ayudarlo, y antes de esa etapa, una buena planificación ahorrará mucho dolor (y tampoco me refiero a un enfoque completamente diseñado para su solución de código: los bocetos simples y algunas notas pueden y lo harán) Ser mejor que nada).

En lo que respecta al Object reference not set to an instance of an objectcódigo, el código no puede adivinar los valores que nos pueden gustar: ese es nuestro trabajo como programadores, y simplemente significa que ha pasado una variable no inicializada.

GMasucci
fuente
¿Te das cuenta de que la gente solía ofrecer justificaciones como esta para escribir en lenguaje ensamblador? ¿Y no utiliza la recolección automática de basura? ¿Y poder hacer aritmética, en hexadecimal? "oneroso, tedioso y a veces aburrido" es cómo describimos los trabajos que deberían automatizarse.
Spike0xff
0

Aprende a usar el depurador. Este es exactamente el tipo de cosas para las que está diseñado. Establezca un punto de interrupción en el método en cuestión y listo.

Simplemente revise su código y vea exactamente cuáles son los valores de todas sus variables en ciertos puntos.

Editar: Francamente, estoy sorprendido de que nadie más haya mencionado el uso del depurador todavía.

Cromulento
fuente
2
¿Entonces está diciendo que todas las excepciones se pueden reproducir fácilmente cuando se usa un depurador?
jgauffin
@jgauffin Estoy diciendo que puede usar un depurador para ver por qué se lanza una excepción en el código del mundo real en lugar de las pruebas unitarias sintéticas que podrían no probar completamente el código en cuestión o las pruebas unitarias en sí mismas pueden tener errores en las causas ellos para perder errores en el código real. Un depurador triunfa sobre cualquier otra herramienta que se me ocurra (excepto quizás cosas como Valgrind o DTrace).
Cromulent
1
¿Entonces está diciendo que siempre tenemos acceso a la computadora que falla y que la depuración es mejor que las pruebas unitarias?
jgauffin
@jgauffin Estoy diciendo que si tienes otra opción, ve con el depurador en lugar de otras herramientas. Por supuesto, si no tiene esa opción, entonces es un poco inestable. Claramente, este no es el caso con esta pregunta, por eso di la respuesta que hice. Si la pregunta hubiera sido cómo se soluciona este problema en la computadora de un cliente sin ninguna forma de depuración remota (o depuración local), mi respuesta hubiera sido diferente. Parece que estás tratando de distorsionar mi respuesta de maneras que no tienen relevancia para la pregunta en cuestión.
Cromulento
0

Si desea ir con la respuesta de @ stijn y poner cheques nulos en su código, este fragmento de código debería ayudar. Aquí hay información sobre fragmentos de código . Una vez que tenga esto configurado, simplemente escriba argnull, presione la pestaña dos veces y luego complete el espacio en blanco.

<CodeSnippet Format="1.0.0">
  <Header>
    <Title>EnsureArgNotNull</Title>
    <Shortcut>argnull</Shortcut>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>argument</ID>
        <ToolTip>The name of the argument that shouldn't be null</ToolTip>
        <Default>arg</Default>
      </Literal>
    </Declarations>
    <Code Language="CSharp">
      <![CDATA[if ($argument$ == null) throw new ArgumentNullException("$argument$");$end$]]>
    </Code>
  </Snippet>
</CodeSnippet>
usuario2023861
fuente
nota: c # 6 ahora tiene, nameofpor lo que su fragmento podría ser el throw new ArgumentNullException(nameof($argument$))que tiene las ventajas de no incluir constantes mágicas, ser revisado por el compilador y trabajar mejor con herramientas de refactorización
stijn