Estoy teniendo una discusión con mi compañero de trabajo sobre cuánto trabajo puede hacer un constructor. Tengo una clase, B que requiere internamente otro objeto A. El objeto A es uno de los pocos miembros que la clase B necesita para hacer su trabajo. Todos sus métodos públicos dependen del objeto interno A. La información sobre el objeto A se almacena en la base de datos, así que trato de validarla y obtenerla buscándola en la base de datos en el constructor. Mi compañero de trabajo señaló que el constructor no debería hacer mucho trabajo más que capturar los parámetros del constructor. Dado que todos los métodos públicos fallarían de todos modos si el objeto A no se encuentra usando las entradas al constructor, argumentaba que en lugar de permitir que se cree una instancia y luego falle, en realidad es mejor lanzar temprano en el constructor.
¿Qué piensan los demás? Estoy usando C # si eso hace alguna diferencia.
Lectura ¿Hay alguna razón para hacer todo el trabajo de un objeto en un constructor? Me pregunto si buscar el objeto A yendo a DB es parte de "cualquier otra inicialización necesaria para que el objeto esté listo para usar" porque si el usuario pasara valores incorrectos al constructor, no podría usar ninguno de sus métodos públicos.
Los constructores deben crear instancias de los campos de un objeto y hacer cualquier otra inicialización necesaria para que el objeto esté listo para usar. Esto generalmente significa que los constructores son pequeños, pero hay escenarios en los que esto supondría una cantidad considerable de trabajo.
fuente
Respuestas:
Su pregunta se compone de dos partes completamente separadas:
¿Debería lanzar una excepción desde el constructor, o debería hacer que el método falle?
Esta es claramente la aplicación del principio de falla rápida . Hacer que el constructor falle es mucho más fácil de depurar en comparación con tener que encontrar por qué falla el método. Por ejemplo, puede obtener la instancia ya creada de alguna otra parte del código y obtener errores al llamar a los métodos. ¿Es obvio que el objeto se creó mal? No.
En cuanto al problema de "envolver la llamada en try / catch". Las excepciones están destinadas a ser excepcionales . Si sabe que algún código arrojará una excepción, no ajusta el código en try / catch, pero valida los parámetros de ese código antes de ejecutar el código que puede arrojar una excepción. Las excepciones son solo una forma de garantizar que el sistema no entre en un estado no válido. Si sabe que algunos parámetros de entrada pueden conducir a un estado no válido, asegúrese de que esos parámetros nunca sucedan. De esta manera, solo tiene que hacer try / catch en lugares, donde puede manejar lógicamente la excepción, que generalmente está en el límite del sistema.
¿Puedo acceder a "otras partes del sistema, como DB" desde el constructor.
Creo que esto va en contra del principio de menor asombro . No mucha gente esperaría que el constructor acceda a un DB. Entonces no, no deberías hacer eso.
fuente
Ugh
Un constructor debe hacer lo menos posible; el problema es que el comportamiento de excepción en los constructores es incómodo. Muy pocos programadores saben cuál es el comportamiento adecuado (incluidos los escenarios de herencia), y obligar a sus usuarios a intentar / capturar cada instancia es ... doloroso en el mejor de los casos.
Hay dos partes en esto, en el constructor y usando el constructor. Es incómodo en el constructor porque no puedes hacer mucho allí cuando ocurre una excepción. No puede devolver un tipo diferente. No puedes devolver nulo. Básicamente, puede volver a lanzar la excepción, devolver un objeto roto (constuctor defectuoso) o (en el mejor de los casos) reemplazar alguna parte interna con un valor predeterminado sensato. Usar el constructor es incómodo porque tus instancias (¡y las instancias de todos los tipos derivados!) Pueden arrojarse. Entonces no puede usarlos en inicializadores de miembros. Terminas usando excepciones para que la lógica vea "¿tuvo éxito mi creación?", Más allá de la legibilidad (y fragilidad) causada por try / catch en todas partes.
Si su constructor está haciendo cosas no triviales, o cosas que razonablemente pueden fallar, considere un
Create
método estático (con un constructor no público). Es mucho más utilizable, más fácil de depurar, y aún así te ofrece una instancia completamente construida cuando tienes éxito.fuente
Connection
objeto puedeCreateCommand
para usted para que sepa que el comando está conectado adecuadamente.Constructor: el equilibrio de la complejidad del método es, de hecho, una cuestión de discusión sobre un buen estilo. Muy a menudo
Class() {}
se usa un constructor vacío , con un métodoInit(..) {..}
para cosas más complicadas. Entre los argumentos a tener en cuenta:Class x = new Class(); x.Init(..);
muchas veces, parcialmente en Pruebas unitarias. No tan bueno, sin embargo, claro como el cristal para leer. A veces, simplemente los pongo en una línea de código.fuente
init
método es que manejar las fallas es mucho más fácil. En particular, el valor de retorno delinit
método puede indicar si la inicialización es exitosa y el método puede garantizar que el objeto esté siempre en buen estado.