Mejores prácticas: lanzar excepciones de las propiedades

¿Cuándo es apropiado lanzar una excepción desde dentro de un getter o setter de propiedades? ¿Cuándo no es apropiado? ¿Por qué? Los enlaces a documentos externos sobre el tema serían útiles … Google apareció sorprendentemente poco.

Microsoft tiene sus recomendaciones sobre cómo diseñar propiedades en http://msdn.microsoft.com/en-us/library/ms229006.aspx

Esencialmente, recomiendan que los captadores de propiedades sean accesos ligeros que siempre sean seguros para llamar. Recomiendan rediseñar getters para que sean métodos si las excepciones son algo que necesita lanzar. Para los instaladores, indican que las excepciones son una estrategia de manejo de errores apropiada y aceptable.

Para los indexadores, Microsoft indica que es aceptable que tanto getters como setters arrojen excepciones. Y, de hecho, muchos indexadores de la biblioteca .NET hacen esto. La excepción más común es ArgumentOutOfRangeException .

Hay algunas buenas razones por las que no desea lanzar excepciones en captadores de propiedades:

  • Debido a que las propiedades “parecen” ser campos, no siempre es aparente que pueden arrojar una excepción (por diseño); mientras que con los métodos, los progtwigdores están entrenados para esperar e investigar si las excepciones son una consecuencia esperada de invocar el método.
  • La gran cantidad de infraestructura .NET, como serializadores y enlaces de datos (en WinForms y WPF, por ejemplo) utilizan los Getters. Tratar con excepciones en tales contextos puede convertirse rápidamente en un problema.
  • Los captadores de propiedades son evaluados automáticamente por los depuradores cuando mira o inspecciona un objeto. Una excepción aquí puede ser confusa y ralentizar sus esfuerzos de depuración. Tampoco es deseable realizar otras operaciones costosas en propiedades (como acceder a una base de datos) por las mismas razones.
  • Las propiedades a menudo se usan en una convención de encadenamiento: obj.PropA.AnotherProp.YetAnother – con este tipo de syntax se vuelve problemático decidir dónde inyectar las declaraciones catch de excepción.

Como nota al margen, uno debe saber que solo porque una propiedad no está diseñada para lanzar una excepción, eso no significa que no lo hará; podría ser código de llamada que hace. Incluso el simple acto de asignar un nuevo objeto (como una cadena) podría dar como resultado excepciones. Siempre debe escribir su código de manera defensiva y esperar excepciones de todo lo que invoque.

No hay nada de malo en lanzar excepciones de los setters. Después de todo, ¿qué mejor manera de indicar que el valor no es válido para una propiedad determinada?

En el caso de los getters, en general no se respeta, y eso se puede explicar con bastante facilidad: un getter de propiedades, en general, informa el estado actual de un objeto; por lo tanto, el único caso donde es razonable que un getter arroje es cuando el estado es inválido. Pero también se considera generalmente que es una buena idea diseñar sus clases de forma que simplemente no sea posible obtener un objeto no válido inicialmente, o ponerlo en estado inválido por medios normales (es decir, siempre asegurar la inicialización completa en los constructores, y intente hacer que los métodos sean a prueba de excepciones con respecto a la validez del estado y las invariantes de clase). Siempre y cuando te apegues a esa regla, tus compradores de propiedades nunca deben entrar en una situación en la que tengan que informar un estado no válido, y por lo tanto nunca lanzar.

Hay una excepción que conozco, y en realidad es bastante importante: cualquier objeto que implemente IDisposable . Dispose está diseñado específicamente como una forma de llevar el objeto a un estado no válido, e incluso hay una clase de excepción especial, ObjectDisposedException , que se utilizará en ese caso. Es perfectamente normal lanzar ObjectDisposedException de cualquier miembro de la clase, incluidos los getters de propiedad (y excluir Dispose ), después de que el objeto haya sido eliminado.

Casi nunca es apropiado en un getter, y algunas veces es apropiado para un setter.

El mejor recurso para este tipo de preguntas es “Pautas de diseño del marco” por Cwalina y Abrams; está disponible como un libro encuadernado, y grandes porciones de él también están disponibles en línea.

De la sección 5.2: Diseño de propiedad

EVITE lanzar excepciones de captadores de propiedades. Los captadores de propiedades deben ser operaciones simples y no deben tener condiciones previas. Si un getter puede arrojar una excepción, probablemente debería ser rediseñado para ser un método. Tenga en cuenta que esta regla no se aplica a los indexadores, donde esperamos excepciones como resultado de la validación de los argumentos.

Tenga en cuenta que esta guía solo se aplica a los captadores de propiedades. Está bien lanzar una excepción en un setter de propiedades.

Un buen enfoque para las Excepciones es usarlas para documentar el código para usted y para otros desarrolladores de la siguiente manera:

Las excepciones deben ser para estados de progtwig excepcionales. ¡Esto significa que está bien escribirlos donde quieras!

Una razón por la que puede querer ponerlos en getters es documentar la API de una clase: si el software arroja una excepción tan pronto como un progtwigdor intenta usarla incorrectamente, ¡entonces no la usarán mal! Por ejemplo, si tiene validación durante un proceso de lectura de datos, puede no ser lógico poder continuar y acceder a los resultados del proceso si hubo errores fatales en los datos. En este caso, es posible que desee obtener el lanzamiento de salida si hubo errores para garantizar que otro progtwigdor compruebe esta condición.

Son una forma de documentar las suposiciones y los límites de un subsistema / método / lo que sea. ¡En el caso general, no deberían ser atrapados! Esto también se debe a que nunca se lanzan si el sistema está trabajando de la manera esperada: si se produce una excepción, muestra que las suposiciones de un fragmento de código no se cumplen; por ejemplo, no interactúa con el mundo que lo rodea en el camino Originalmente estaba destinado a. Si detecta una excepción que fue escrita para este propósito, probablemente signifique que el sistema ha entrado en un estado impredecible / inconsistente; esto puede conducir a una falla o corrupción de datos o similar que probablemente sea mucho más difícil de detectar / depurar.

Los mensajes de excepción son una forma muy grosera de informar errores: no se pueden recostackr en masa y solo contienen una cadena. Esto los hace inadecuados para informar problemas en los datos de entrada. En funcionamiento normal, el sistema no debería ingresar un estado de error. Como resultado de esto, los mensajes en ellos deben diseñarse para progtwigdores y no para usuarios: las cosas que son incorrectas en los datos de entrada se pueden descubrir y transmitir a los usuarios en formatos más adecuados (personalizados).

La excepción (¡jaja!) A esta regla es cosas como IO donde las excepciones no están bajo su control y no se pueden verificar con anticipación.

Todo esto está documentado en MSDN (como se vincula en otras respuestas), pero aquí hay una regla general …

En el colocador, si su propiedad debe ser validada más allá del tipo. Por ejemplo, una propiedad llamada PhoneNumber probablemente debería tener validación de expresiones regulares y debería arrojar un error si el formato no es válido.

Para getters, posiblemente cuando el valor es nulo, pero lo más probable es que sea algo que querrás manejar en el código de llamada (según las pautas de diseño).

MSDN: tipos de excepciones estándar de captura y lanzamiento

http://msdn.microsoft.com/en-us/library/ms229007.aspx

Esta es una pregunta y respuesta muy compleja, depende de cómo se usa su objeto. Como regla general, los captadores de propiedades y los intermediarios que son “vinculantes tardíos” no deben arrojar excepciones, mientras que las propiedades con “vinculación anticipada” exclusiva deben arrojar excepciones cuando sea necesario. Por cierto, la herramienta de análisis de código de Microsoft está definiendo el uso de propiedades demasiado estrictamente, en mi opinión.

“enlace tardío” significa que las propiedades se encuentran a través de la reflexión. Por ejemplo, el atributo Serializables “se utiliza para serializar / deserializar un objeto a través de sus propiedades. Lanzar una excepción durante este tipo de situación rompe las cosas de manera catastrófica y no es una buena forma de usar excepciones para crear un código más sólido.

“vinculación anticipada” significa que el comstackdor vincula el uso de la propiedad en el código. Por ejemplo, cuando algún código que escribe hace referencia a un getter de propiedad. En este caso, está bien lanzar excepciones cuando tengan sentido.

Un objeto con atributos internos tiene un estado determinado por los valores de esos atributos. Las propiedades que expresan atributos que son conscientes y sensibles al estado interno del objeto no deben usarse para el enlace tardío. Por ejemplo, supongamos que tiene un objeto que debe abrirse, accederse y luego cerrarse. En este caso, el acceso a las propiedades sin abrir primero debe dar como resultado una excepción. Supongamos, en este caso, que no lanzamos una excepción y permitimos que el código acceda a un valor sin lanzar una excepción. El código parecerá feliz a pesar de que obtuvo un valor de un getter que no tiene sentido. Ahora hemos puesto el código que llamó al getter en una mala situación ya que debe saber cómo verificar el valor para ver si no tiene sentido. Esto significa que el código debe hacer suposiciones sobre el valor que obtuvo del captador de propiedades para validarlo. Así es como se escribe mal el código.

Tenía este código en el que no estaba seguro de qué excepción lanzar.

 public Person { public string Name { get; set; } public boolean HasPets { get; set; } } public void Foo(Person person) { if (person.Name == null) { throw new Exception("Name of person is null."); // I was unsure of which exception to throw here. } Console.WriteLine("Name is: " + person.Name); } 

Evité que el modelo tuviera la propiedad nula en primer lugar al forzarlo como argumento en el constructor.

 public Person { public Person(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } Name = name; } public string Name { get; private set; } public boolean HasPets { get; set; } } public void Foo(Person person) { Console.WriteLine("Name is: " + person.Name); }