evitando excepciones de referencia nula

Aparentemente, la gran mayoría de los errores en el código son excepciones de referencia nula. ¿Hay alguna técnica general para evitar encontrar errores de referencia nulos?

A menos que esté equivocado, soy consciente de que en idiomas como F # no es posible tener un valor nulo. Pero esa no es la pregunta, estoy preguntando cómo evitar errores de referencia nulos en idiomas como C #.

Cuando se muestra una excepción de referencia nula al usuario, esto indica un defecto en el código que resulta de un error por parte del desarrollador. Aquí hay algunas ideas sobre cómo prevenir estos errores.

Mi principal recomendación para las personas que se preocupan por la calidad del software, y también usan la plataforma de progtwigción.net, es instalar y usar contratos de código de Microsoft ( http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx ) . Incluye capacidades para realizar comprobaciones en tiempo de ejecución y estáticas. La capacidad esencial para construir estos contratos en su código se está incluyendo en la versión 4.0 del framework.net. Si está interesado en la calidad del código, y parece que lo es, puede disfrutar el uso de los contratos de código de Microsoft.

Con los contratos de código de Microsoft, puede proteger su método de valores nulos mediante la adición de condiciones previas como esta “Contrato.Requiere (cliente! = Nulo);”. Agregar una precondición como esta es equivalente a la práctica recomendada por muchos otros en sus comentarios anteriores. Antes de los contratos de código, te habría recomendado que hicieras algo como esto

 if (customer == null) {throw new ArgumentNullException("customer");} 

Ahora lo recomiendo

 Contract.Requires(customer != null); 

A continuación, puede habilitar el sistema de comprobación en tiempo de ejecución, que detectará estos defectos lo antes posible, lo que lo guiará hacia el diagnóstico y la corrección del código defectuoso. Pero no dejes que te dé la impresión de que los contratos de código son simplemente una forma elegante de reemplazar el argumento de excepciones nulas. Ellos son mucho más poderosos que eso. Con los contratos de código de Microsoft, también puede ejecutar el comprobador estático y solicitarle que investigue posibles sitios en su código donde pueden producirse excepciones de referencia nula. El verificador estático requiere un poco más de experiencia para usar fácilmente. No lo recomendaría primero para principiantes. Pero siéntete libre de probarlo y verlo por ti mismo.

INVESTIGACIÓN SOBRE LA PREVALENCIA DE ERRORES DE REFERENCIA NULOS

Ha habido cierto debate en este hilo sobre si los errores de referencia nulos son un problema significativo. Una respuesta larga es la siguiente. Para las personas que no quieren pasar por eso, lo resumiré.

  • Los principales investigadores de Microsoft en lo que respecta a la corrección de progtwigs en los proyectos de contratos de código y especificación, creen que es un problema que vale la pena abordar.
  • El Dr. Bertrand Meyer y el equipo de ingenieros de software de ISE, que desarrollaron y respaldaron el lenguaje de progtwigción de Eiffel, también creen que es un problema que vale la pena abordar.
  • En mi propia experiencia comercial desarrollando software ordinario, he visto errores de referencia nulos con la suficiente frecuencia, que me gustaría abordar el problema en mis propios productos y prácticas.

Durante años, Microsoft ha invertido en investigaciones diseñadas para mejorar la calidad del software. Uno de sus esfuerzos fue el proyecto Spec #. Uno de los desarrollos más interesantes en mi opinión con el framework.net 4.0 es la introducción de los contratos de código de Microsoft, que es una consecuencia del trabajo anterior realizado por el equipo de investigación de Spec #.

En cuanto a su comentario “la gran mayoría de los errores en el código son excepciones de referencia nula”, creo que es el calificador “la gran mayoría” que causará algunos desacuerdos. La frase “gran mayoría” sugiere que quizás el 70-90% de las fallas tienen una excepción de referencia nula como causa raíz. Esto parece demasiado alto para mí. Prefiero citar de la investigación de Microsoft Spec #. En su artículo The Spec # programming system: Una visión general, por Mike Barnett, K. Rustan M. Leino y Wolfram Schulte. En CASSIS 2004, LNCS vol. 3362, Springer, 2004, escribieron

1.0 Tipos no nulos Muchos errores en los progtwigs modernos se manifiestan como errores de desreferencia nula, lo que sugiere la importancia de un lenguaje de progtwigción que proporcione la capacidad de discriminar entre expresiones que pueden evaluarse como nulas y aquellas que están seguras de no hacerlo (para algunas pruebas experimentales, ver [24, 22]). De hecho, nos gustaría erradicar todos los errores de desreferencia nulos.

Esta es una fuente probable para las personas de Microsoft que están familiarizadas con esta investigación. Este artículo está disponible en el sitio de Spec #.

He copiado las referencias 22 y 24 a continuación e incluí el ISBN para su conveniencia.

  • Manuel Fahndrich y K. Rustan M. Leino. Declarar y verificar tipos no nulos en un lenguaje orientado a objetos. En Actas de la Conferencia ACM 2003 sobre Progtwigción Orientada a Objetos, Sistemas, Idiomas y Aplicaciones, OOPSLA 2003, volumen 38, número 11 en Avisos SIGPLAN, páginas 302-312. ACM, noviembre de 2003. isbn = {1-58113-712-5},

  • Cormac Flanagan, K. Rustan, M. Leino, Mark Lillibridge, Greg Nelson, James B. Saxe y Raymie Stata. Comprobación estática extendida para Java. En Actas de la Conferencia ACM SIGPLAN 2002 sobre Diseño e Implementación del Lenguaje de Progtwigción (PLDI), volumen 37, número 5 en Avisos SIGPLAN, páginas 234-245. ACM, mayo de 2002.

Revisé estas referencias. La primera referencia indica algunos experimentos que revisaron su propio código para detectar posibles defectos de referencia nulos. No solo encontraron varios, sino que en muchos casos, la identificación de una posible referencia nula indicó un problema más amplio con el diseño.

La segunda referencia no proporciona ninguna evidencia específica para la afirmación de que los errores de referencia nulos son un problema. Pero los autores afirman que, en su experiencia, estos errores de referencia nulos son una fuente importante de defectos de software. El documento luego procede a explicar cómo tratan de erradicar estos defectos.

También recordé haber visto algo sobre esto en un anuncio de ISE sobre un reciente lanzamiento de Eiffel. Se refieren a este tema como “seguridad nula” y, como tantas otras cosas inspiradas o desarrolladas por el Dr. Bertrand Meyer, tienen una descripción eloquent y educativa del problema y cómo lo hacen para prevenirlo en su lenguaje y herramientas. Te recomiendo que leas su artículo http://doc.eiffel.com/book/method/void-safety-background-definition-and-tools para obtener más información.

Si desea obtener más información sobre los contratos de código de Microsoft, hay toneladas de artículos que han surgido recientemente. También puede consultar mi blog en http: SLASH SLASH codecontracts.info, que se dedica principalmente a conversaciones sobre la calidad del software mediante el uso de progtwigción con contratos.

Además de lo anterior (objetos nulos, colecciones vacías), existen algunas técnicas generales, a saber, la adquisición de recursos es la inicialización (RAII) de C ++ y el diseño por contrato de Eiffel. Estos se reducen a:

  1. Inicializa variables con valores válidos.
  2. Si una variable puede ser nula, entonces verifique nulo y trátelo como un caso especial o espere una excepción de referencia nula (y trate con eso). Las afirmaciones se pueden usar para probar violaciones de contrato en comstackciones de desarrollo.

He visto un montón de código que se ve así:

if ((value! = null) && (value.getProperty ()! = null) && … && (… doSomethingUseful ())

Muchas veces esto es completamente innecesario y la mayoría de las pruebas se pueden eliminar con una inicialización más estricta y definiciones de contrato más estrictas.

Si esto es un problema en su base de código, entonces es necesario entender en cada caso lo que representa el valor nulo:

  1. Si el nulo representa una colección vacía, use una colección vacía.
  2. Si el nulo representa un caso excepcional, lanza una excepción.
  3. Si el valor nulo representa un valor accidentalmente no inicializado, inicialícelo explícitamente.
  4. Si el nulo representa un valor legítimo, pruébelo, o mejor aún use un objeto nulo que realice una operación nula.

En la práctica, este estándar de claridad en el nivel de diseño no es trivial y requiere esfuerzo y autodisciplina para aplicarse de manera coherente a su código base.

Tu no

O más bien, no hay nada especial que hacer para tratar de ‘prevenir’ las NRE en C #. En su mayor parte, un NRE es solo un tipo de error de lógica. Puedes desactivar estos firewalls en los límites de la interfaz comprobando los parámetros y teniendo muchos códigos como

 void Foo(Something x) { if (x==null) throw new ArgumentNullException("x"); ... } 

en todas partes (gran parte de .Net Framework lo hace), de modo que cuando lo arruinas, obtienes un diagnóstico un poco más informativo (el rastro de la stack es aún más valioso, y un NRE también lo proporciona). Pero aún así terminas con una excepción.

(Aparte: Excepciones como estas – NullReferenceException, ArgumentNullException, ArgumentException, … – normalmente no deberían ser detectadas por el progtwig, sino que simplemente significa “desarrollador de este código, hay un error, por favor, corrígelo”. Me refiero a estos como excepciones de “tiempo de diseño”, contrastémoslas con verdaderas excepciones de “tiempo de ejecución” que ocurren como resultado del entorno de tiempo de ejecución (por ejemplo, FileNotFound) y que el progtwig pretende captar y gestionar potencialmente).

Pero al final del día, solo tienes que codificarlo correctamente.

Idealmente, la mayoría de los NRE nunca ocurrirían porque ‘nulo’ es un valor sin sentido para muchos tipos / variables, e idealmente el sistema de tipo estático no permitiría ‘nulo’ como un valor para esos tipos / variables particulares. Entonces, el comstackdor evitará que introduzca este tipo de error accidental (descartar ciertas clases de errores es lo que mejor comstackn los comstackdores y los sistemas de tipos). Aquí es donde se destacan ciertos lenguajes y sistemas de tipos.

Pero sin esas características, simplemente prueba su código para asegurarse de que no tiene rutas de código con este tipo de error (o posiblemente use algunas herramientas externas que pueden hacer un análisis adicional para usted).

El uso de patrones de objetos nulos es la clave aquí.

Asegúrese de que las colecciones estén vacías en el caso cuando no estén rellenas, en lugar de nulas. Usar una colección nula cuando una colección vacía sería confusa ya menudo innecesaria.

Finalmente, hago que mis objetos hagan valer valores no nulos en la construcción siempre que sea posible. De esta forma, no tengo dudas más tarde sobre si los valores son nulos, y solo tienen que realizar comprobaciones nulas donde sea esencial . Para la mayoría de mis campos y parámetros, puedo suponer que los valores no son nulos basados ​​en afirmaciones previas.

Uno de los errores de referencia nulos más comunes que he visto es de cadenas. Habrá un cheque:

 if(stringValue == "") {} 

Pero, la cadena es realmente nula. Debería ser:

 if(string.IsNullOrEmpty(stringValue){} 

Además, podría ser demasiado cauteloso y comprobar que un objeto no es nulo antes de intentar acceder a los miembros / métodos de ese objeto.

Puede verificar fácilmente una referencia nula antes de que cause una excepción, pero generalmente ese no es el problema real, por lo que terminaría arrojando una excepción de todos modos ya que el código no puede continuar sin ningún dato.

A menudo, el problema principal no es el hecho de que tenga una referencia nula, sino que tiene una referencia nula en primer lugar. Si se supone que una referencia no es nula, no debe pasar el punto donde se inicializa la referencia sin tener una referencia adecuada.

Una forma es utilizar los Objetos de valor nulo (también conocido como el Patrón de Objeto Nulo ) siempre que sea posible. Hay más detalles aquí

Realmente, si en su idioma hay valores nulos, es inevitable que suceda. Los errores de referencia nulos provienen de errores en la lógica de la aplicación, por lo tanto, a menos que pueda evitar todos los que está obligado a alcanzar.

El uso apropiado del manejo estructurado de excepciones puede ayudar a evitar dichos errores.

Además, las pruebas unitarias pueden ayudarlo a asegurarse de que su código se comporte como se espera, lo que incluye garantizar que los valores no sean nulos cuando se supone que no deberían ser.

Una de las formas más sencillas de evitar NullReferenceExceptions es verificar de forma agresiva las referencias nulas en los constructores / métodos / establecedores de propiedades de su clase y llamar la atención sobre el problema.

P.ej

 public MyClass { private ISomeDependency m_dependencyThatWillBeUsedMuchLater // passing a null ref here will cause // an exception with a meaningful stack trace public MyClass(ISomeDependency dependency) { if(dependency == null) throw new ArgumentNullException("dependency"); m_dependencyThatWillBeUsedMuchLater = dependency; } // Used later by some other code, resulting in a NullRef public ISomeDependency Dep { get; private set; } } 

En el código anterior, si pasa una referencia nula, se dará cuenta inmediatamente de que el código de llamada está utilizando el tipo incorrectamente. Si no hubo una verificación de referencia nula, el error puede ocultarse de muchas maneras diferentes.

Notará que las bibliotecas .NET Framework casi siempre fallan temprano y, a menudo, si proporciona referencias nulas donde no es válido hacerlo. Dado que la excepción arrojada explícitamente dice “¡echaste a perder!” y le dice por qué, hace que la detección y corrección del código defectuoso sea una tarea trivial.

He escuchado quejas de algunos desarrolladores que dicen que esta práctica es demasiado prolija y redundante ya que una NullReferenceException es todo lo que necesitas, pero en la práctica creo que hace una gran diferencia. Este es especialmente el caso si la stack de llamadas es profunda y / o el parámetro está almacenado y su uso se difiere hasta más tarde (tal vez en un hilo diferente u oscurecido de alguna otra manera).

¿Qué preferirías tener, una ArgumentNullException en el método de entrada, o un oscuro error en el intestino? Cuanto más te alejas del origen de un error, más difícil es rastrearlo.

Las buenas herramientas de análisis de código pueden ayudar aquí. Las buenas pruebas unitarias también pueden ser útiles si está utilizando herramientas que consideran nulo como una ruta posible a través de su código. Intenta lanzar ese interruptor en la configuración de tu comstackción que diga “tratar las advertencias como errores” y ver si puedes mantener el número de advertencias en tu proyecto = 0. Es posible que las advertencias te indiquen mucho.

Una cosa a tener en cuenta es que puede ser bueno que esté lanzando una excepción de referencia nula. ¿Por qué? porque puede significar que el código que debería haber ejecutado no. Inicializar a los valores predeterminados es una buena idea, pero debe tener cuidado de no terminar ocultando un problema.

 List GetAllClients() { List returnList = new List; /* insert code to go to data base and get some data reader named rdr */ for (rdr.Read() { /* code to build Client objects and add to list */ } return returnList; } 

Muy bien, así que esto puede parecer bien, pero dependiendo de las reglas de su negocio, esto puede ser un problema. Claro, nunca lanzarás una referencia nula, pero tal vez tu tabla de Usuario nunca debería estar vacía. ¿Desea que su aplicación funcione correctamente y genere llamadas de soporte de los usuarios diciendo que “solo se trata de una pantalla en blanco”, o desea generar una excepción que pueda registrarse en algún lugar y generar una alerta rápidamente? No olvide validar lo que está haciendo, así como las excepciones de ‘manejo’. Esta es una de las razones por las cuales algunos aborrecen quitar nulos de nuestros idiomas … hace que sea más fácil encontrar los errores aunque pueda causar algunos nuevos.

Recuerde: maneje las excepciones, no las oculte.

Solución de código simple

Siempre se puede crear una estructura que ayude a detectar errores de referencia nulos con anterioridad marcando variables, propiedades y parámetros como “no anulables”. Aquí hay un ejemplo conceptual modelado después del modo en que Nullable funciona:

 [System.Diagnostics.DebuggerNonUserCode] public struct NotNull where T : class { private T _value; public T Value { get { if (_value == null) { throw new Exception("null value not allowed"); } return _value; } set { if (value == null) { throw new Exception("null value not allowed."); } _value = value; } } public static implicit operator T(NotNull notNullValue) { return notNullValue.Value; } public static implicit operator NotNull(T value) { return new NotNull { Value = value }; } } 

Nullable muy similar a la misma forma en que Nullable , excepto con el objective de lograr exactamente lo opuesto: no permitir null . Aquí hay unos ejemplos:

 NotNull person = null; // throws exception NotNull person = new Person(); // OK NotNull person = GetPerson(); // throws exception if GetPerson() returns null 

NotNull se NotNull implícitamente hacia y desde T para que pueda usarlo en cualquier lugar que lo necesite. Por ejemplo, puede pasar un objeto Person a un método que toma una NotNull :

 Person person = new Person { Name = "John" }; WriteName(person); public static void WriteName(NotNull person) { Console.WriteLine(person.Value.Name); } 

Como puede ver arriba, como con la opción de nulos, accederá al valor subyacente a través de la propiedad Value . Alternativamente, puede usar el molde explícito o implícito, puede ver un ejemplo con el valor de retorno a continuación:

 Person person = GetPerson(); public static NotNull GetPerson() { return new Person { Name = "John" }; } 

O incluso puede usarlo cuando el método simplemente devuelve T (en este caso Person ) haciendo un molde. Por ejemplo, el siguiente código simplemente le gustaría el código anterior:

 Person person = (NotNull)GetPerson(); public static Person GetPerson() { return new Person { Name = "John" }; } 

Combinar con la extensión

Combine NotNull con un método de extensión y podrá cubrir aún más situaciones. Aquí hay un ejemplo de cómo se ve el método de extensión:

 [System.Diagnostics.DebuggerNonUserCode] public static class NotNullExtension { public static T NotNull(this T @this) where T : class { if (@this == null) { throw new Exception("null value not allowed"); } return @this; } } 

Y aquí hay un ejemplo de cómo podría usarse:

 var person = GetPerson().NotNull(); 

GitHub

Para su referencia, hice que el código anterior esté disponible en GitHub, puede encontrarlo en:

https://github.com/luisperezphd/NotNull

Puede usar el patrón de Objeto nulo y el patrón de Caso especial en los casos en que puede haber un objeto legítimo que puede sustituir a nulo.

En los casos en que no se puede construir dicho objeto, porque simplemente no hay forma de implementar sus operaciones obligatorias, puede confiar en las colecciones vacías, como en las Consultas Map-Reduce .

Otra solución es el tipo funcional Opción , que es la colección con cero o un elemento. De esa manera, tendrá la oportunidad de omitir la operación que no se puede realizar.

Estas son las opciones que pueden ayudarlo a escribir código sin tener referencias nulas y verificaciones nulas.

Herramientas que pueden ayudar

También hay varias bibliotecas que pueden ayudar. Microsoft Code Contracts fue mencionado anteriormente.

Algunas otras herramientas incluyen Resharper que puede proporcionarle advertencias mientras escribe código, especialmente si usa su atributo: NotNullAttribute

También hay PostSharp que te permitirá usar atributos como este:

 public void DoSometing([NotNull] obj) 

Al hacer eso y hacer que PostSharp sea parte de su proceso de comstackción obj se comprobará en nulo en tiempo de ejecución. Ver: comprobación nula PostSharp

El proyecto de tejido de código Fody tiene un complemento para implementar protecciones nulas.

NullReferenceException se puede mostrar cuando no se encuentra un método en el ensamblado, para ex m0 = mi.GetType (). GetMethod (“TellChildToBeQuiet”) donde el ensamblado es SportsMiniCar, mi es la instancia de MiniVan y TellChildToBeQuiet es un método en el ensamblaje . Podemos evitar esto al ver que esta Asamblea versión 2.0.0.0 que contiene el método anterior se coloca en el GAC. ejemplo: invocación de métodos con parámetros: `

 enter code here using System; using System.Rwflection; using System.IO; using Carlibraries; namespace LateBinding { public class program { static void Main(syring[] args) { Assembly a=null; try { a=Assembly.Load("Carlibraries"); } catch(FileNotFoundException e) { Console.Writeline(e.Message); Console.ReadLine(); return; } Type miniVan=a.GetType("Carlibraries.MiniVan"); MiniVan mi=new MiniVan(); mi.TellChildToBeQuiet("sonu",4); Console.ReadLine(); } } } 

Recuerde actualizar MiniSportsCar Assembly con TellChildToBeQuiet (string ChildName, int count)