¿Se considera una mala práctica lanzar una excepción desde un constructor de objetos si los datos pasados ​​no son válidos?

¿Se considera una mala práctica lanzar una excepción desde un constructor de objetos si los datos pasados ​​no son válidos?

Absolutamente no. A menos que hacerlo no cumpla con la estrategia de manejo de errores de su organización / aplicación.

Java tuvo un manejo de excepciones desde el principio y la mayoría del código y las bibliotecas son compatibles con él. También hay reglas de idioma para ayudar a garantizar que se manejen las excepciones que se deben manejar (en algún lugar).

Siempre será mejor en Java lanzar una excepción en lugar de:

  1. Crea un objeto no válido.
  2. Crear un objeto ‘inerte’ en respuesta a argumentos no válidos
  3. Cree un método de constructor proxy que devuelva null (o de lo contrario devuelve sin crear un objeto e indica tal).
  4. Verifique que los argumentos crearán un objeto válido antes de intentar crear uno.
  5. Cree un objeto inerte (utilizando un constructor predeterminado) y luego valide los argumentos ‘reales’ en un miembro / método que realice la construcción ‘real’ y devuelva un error si falla.
  6. Abortar la ejecución del hilo / aplicación.

La creación de un objeto inerte (punto 2) puede requerir un estado adicional e incluso código en sus métodos para tener respuestas de ‘no hacer nada’ a las solicitudes. Dichos objetos que se devuelven a la lógica que no tiene idea de que se ha pasado a ser un ficticio son una pesadilla total de comportamiento de aplicación extraño e insondable.


La respuesta en C ++ es un poco más matizada porque el manejo de excepciones llegó más tarde y se usa en entornos más diversos que Java y no todas las aplicaciones admiten el manejo de excepciones.

Para ser claros, todas las cosas son iguales y no hay restricciones debido a una estrategia existente, una restricción ambiental o una biblioteca de códigos amigable que no sea una excepción. errores de datos (que no son inesperados y deben manejarse en alguna ruta de control ‘normal’).


Tanto en C ++ como en Java, los lenguajes tienen una semántica clara y bien definida de lo que sucede cuando se lanza una excepción durante la creación de objetos y cómo se destruirá cualquier clase de base / súper construida.


Un caso de prueba para la mayoría de los modelos es “Si quiero eliminar la serialización de un objeto de una secuencia, ¿cómo manejo la falla de E / S en la secuencia antes de que termine?”. Las opciones comunes son ir por el # 4 o # 5 en este caso.

Esta no es una mala práctica. Si su constructor sabe que cierto parámetro es completamente inválido, entonces debería lanzar una excepción.

No preste atención a las respuestas que afirman que la persona que llama tendrá que manejar posibles excepciones cada vez que se cree un nuevo objeto. Si su idioma incluso admite excepciones marcadas, evite usarlas. Por ejemplo, en Java, algún tipo de excepción de tiempo de ejecución suele ser apropiado en estas situaciones, y no necesita ser detectado.

Brandon Victor acertó cuando dijo que nunca se debe crear un objeto en un estado no válido. El constructor es responsable de hacer cumplir esto. Me sorprende que piense que es una posición controvertida, porque no podría ser más correcto.

La alternativa es colocar la lógica de validación fuera del objeto, como han sugerido otras respuestas. Esto no tiene sentido. La lógica perteneciente al objeto pertenece dentro del objeto. Cada objeto es responsable de sí mismo, y una buena programación defensiva respalda esa política.

Esto debería ser una cuestión de diseño del sistema, como con todas las preguntas que implican excepciones. El arquitecto del sistema debe decidir cómo y cuándo se lanzan y capturan las excepciones y se debe aplicar en todo el proyecto.

Hay un modelo que llamo “rico en excepciones”, en el que reaccionas ante casi cualquier error lanzando una excepción. Si bien parece facilitar la vida, en realidad supone una carga considerable para los diseñadores, porque casi cualquier bloque de código puede generar una excepción. Ahorra esfuerzo en las llamadas a funciones, ya que no necesita verificar errores. Pero luego debe pensar en cada punto sobre lo que sucede si algo que llama arroja y excepción varios niveles hacia abajo. En este modelo, los constructores pueden lanzar excepciones.

El modelo alternativo es la “luz de excepción” en la que las excepciones están reservadas para errores verdaderamente desastrosos. Los constructores definitivamente no deberían lanzar excepciones. Entonces, ya sea que el constructor tenga parámetros incorrectos, puede generar un objeto “inerte”, que tiene todos los métodos correctos, pero realiza algún tipo de comportamiento inerte predeterminado. O usas construcción en dos etapas. El constructor no puede tener ningún error, y un inicializador puede generar notificaciones de error de excepción o de retorno.

Ninguno de estos modelos es correcto: los sistemas buenos se pueden escribir usando cualquiera de ellos, pero es una decisión de nivel de arquitecto que debe usarse: usted, el diseñador de la clase, no debe tener la opción de elegir.

Voy a ser controvertido y decir; definitivamente tienes razón al poner una excepción en el constructor.

Creo que es imprescindible que no cree un objeto con datos no válidos. Cuando se trata de objetos, nunca deben estar en un estado no válido, de lo contrario, debe verificar si está en un estado no válido cada vez que quiera usarlo. La alternativa es que debe considerar situaciones no válidas cada vez que desee hacerlo, que solo puede ser menos frecuente que la cantidad de lugares que desea usar.

Tener que atrapar diferentes excepciones al construir un objeto no suena muy bien, pero es mejor que un objeto inválido flotando. La única excepción a esto es si realmente puede, 100%, asegurarse de que puede hacer que el objeto tenga un comportamiento “inerte” predeterminado que no afecte nada más en su programa como lo sugiere Alec Cawley.

EDITAR: También quiero señalar que no debe colocar la lógica de validación fuera del objeto en sí. Si su lenguaje admite algo como funciones estáticas, QUIZÁS puede hacer una función estática en la clase que pueda verificar la validez (devolver boolean) para decidir si desea hacer el objeto o no, pero, en realidad, será muy similar a lanzando una excepción, de todos modos.

Realmente depende de la arquitectura.

Generalmente requiero que un constructor devuelva un objeto con datos válidos: sin datos válidos significa que no hay generación de objetos, por lo que mis constructores tienden a lanzar excepciones. Tampoco suelo usar constructores predeterminados a menos que el estado predeterminado del objeto sea válido (todos los tipos de valor en 0 y todos los tipos de referencia establecidos en nulo).

Supongamos que tiene un objeto de perro y las reglas son que todos los perros tienen al menos tres letras en el nombre y un número entero no negativo para la edad.

clase perro
{
Nombre de cadena pública {get;}

public int Age {get;}

Perro público (nombre de cadena, edad int)
{
si (edad <0)
{
lanzar nuevo ArgumentOutOfRangeException ();
}
if (string.IsNullOrWhitespace (name) || name.Count (item => char.IsLetter (item)) <3)
{
lanzar nuevo ArgumentException ();
}
Nombre = nombre;
Edad = edad;
}
}

Me parece una excelente práctica. Es literalmente imposible crear un objeto perro a menos que ya tenga los datos necesarios y sepa que es válido.

Soy un gran creyente del fracaso ruidoso e inmediato: el hecho de que exista un objeto perro garantiza que los datos que contiene son válidos (principalmente).

Si necesita un perro mutable sin tales garantías, haría la interfaz IDog con Age and Name como propiedades de solo lectura y una descripción de las reglas. La clase Dog implementaría IDog.

Luego haría una clase IDog llamada DogBuilder con un constructor predeterminado y propiedades configurables. Cuando un DogBuilder se convierte en un perro, lanzo la excepción.

La gran diferencia aquí es que el Perro es un objeto de datos que, por su existencia, garantiza la validez de sus datos: ningún dato válido produce ningún objeto Perro posible.

Creo firmemente que esta es una buena práctica. Observe los constructores en todos los objetos del marco .NET: los datos no válidos arrojan una excepción. Es una buena práctica moderna aceptada que considero la buena práctica predeterminada.

Imagina que cada vez que quisieras llamar a alguien nuevo en algo, debes atrapar tres tipos diferentes de errores, basados ​​en tres tipos diferentes de invalidez. Eso sería una locura.

En un mundo mejor, estaría creando una versión vacía, o una versión no válida, o estableciendo los valores de lo que existe y estableciendo valores predeterminados para lo que no.

Y en ese mundo mejor, su consumidor tomaría ese objeto y haría lo mejor con la información que se le dio, para dejar una salida más precisa, basada en valores predeterminados razonables …
… y su consumidor haría lo mismo.

Editar: dados los detalles, ya sea

  1. Analice el documento para decidir si es utilizable, tan pronto como ingrese al sistema, matando la rama antes de que llegue tan lejos
  2. Construya su objeto y pruebe la idoneidad del objeto antes de avanzar

Esto es en parte por qué se idearon excepciones en primer lugar; un constructor no puede devolver un valor, por lo que puede hacer que arroje una excepción si es necesario.

Pero a menudo es más elegante agregar un método de inicialización que puede generar una excepción o devolver un valor nulo. Si esto es mejor o no depende en parte de si el usuario de la clase va a tener que hacer algún tipo de inicialización, como conectarse a una base de datos.

Mucha gente tiene diferentes pensamientos al respecto. Personalmente, tampoco prefiero este tipo de diseño. Aquí tenemos que entender que esto no es algo que deba hacerse, sino que es una de las formas en que diseñas tu clase. El encadenamiento de constructores es una de las razones por las que me pareció bastante interesante por qué no debería lanzar una excepción de los constructores.

Aquí hay un artículo que puede explicarlo en detalle ¿Por qué un constructor no debería lanzar una excepción? – bytefold

No consideraría esta mala práctica, pero parece un poco extraño. Solo debe crear instancias de objetos que sean válidos, por lo tanto, debe hacer verificaciones de errores antes de instanciar su objeto. Esta es probablemente una pregunta más orientada a la preferencia, por lo que no hay una respuesta correcta o incorrecta aquí. Supongo que una consideración de rendimiento es la asignación de memoria, aunque es bastante insignificante.