Soy un desarrollador de mucho tiempo (tengo 49 años) pero soy bastante nuevo en el desarrollo orientado a objetos. He estado leyendo sobre OO desde Eiffel de Bertrand Meyer, pero he hecho muy poca programación de OO.
El punto es que cada libro sobre diseño OO comienza con un ejemplo de un bote, automóvil o cualquier objeto común que usemos muy a menudo, y comienzan a agregar atributos y métodos, y explican cómo modelan el estado del objeto y qué se puede hacer con eso.
Por lo tanto, suelen decir algo así como "cuanto mejor sea el modelo, mejor representa el objeto en la aplicación y mejor sale todo".
Hasta ahora todo bien, pero, por otro lado, he encontrado varios autores que dan recetas como "una clase debe caber en una sola página" (agregaría "¿en qué tamaño de monitor?" Ahora que intentamos no para imprimir el código!).
Tomemos, por ejemplo, una PurchaseOrder
clase, que tiene una máquina de estados finitos que controla su comportamiento y una colección de PurchaseOrderItem
, uno de los argumentos aquí en el trabajo es que deberíamos usar una PurchaseOrder
clase simple, con algunos métodos (poco más que una clase de datos), y tener una PurchaseOrderFSM
"clase experta" que maneja la máquina de estados finitos para el PurchaseOrder
.
Diría que eso cae en la clasificación de "Envidia de características" o "Intimidad inapropiada" de la publicación de los olores de código de Jeff Atwood en Coding Horror. Simplemente lo llamaría sentido común. Si puedo emitir, aprobar o cancelar mi orden de compra real, entonces la PurchaseOrder
clase debe tener issuePO
, approvePO
y cancelPO
métodos.
¿No va eso con los principios antiguos de "maximizar la cohesión" y "minimizar el acoplamiento" que entiendo como pilares de OO?
Además, ¿eso no ayuda a la mantenibilidad de la clase?
fuente
PurchaseOrder
, sólo podía nombrar sus métodosissue
,approve
ycancel
. ElPO
sufijo agrega solo valor negativo.Respuestas:
Una clase debe usar el Principio de responsabilidad única. La mayoría de las clases muy grandes que he visto hacen muchas cosas, por eso son demasiado grandes. Mire cada método y el código decida si debe estar en esta clase o por separado, el código duplicado es una pista. Es posible que tenga un método de problema PO, pero ¿contiene 10 líneas de código de acceso a datos, por ejemplo? Ese código probablemente no debería estar allí.
fuente
En mi opinión, el tamaño de la clase no importa siempre que las variables, funciones y métodos sean relevantes para la clase en cuestión.
fuente
El mayor problema con las clases grandes es cuando se trata de probar esas clases o su interacción con otras clases. El tamaño de clase grande por sí solo no es un problema, pero como todos los olores, indica un problema potencial . En términos generales, cuando la clase es grande, es una indicación de que la clase está haciendo más de lo que una clase debería hacer.
Para la analogía de su automóvil, si la clase
car
es demasiado grande, probablemente sea una indicación de que debe dividirse en claseengine
, clasedoor
y clasewindshield
, etc.fuente
No creo que haya una definición específica de una clase que sea demasiado grande. Sin embargo, usaría las siguientes pautas para determinar si debo refactorizar mi clase o redefinir el alcance de la clase:
Con respecto a 1, imagine que define el tamaño de su parabrisas, el tamaño de la rueda, el modelo de la bombilla de la luz trasera y otros 100 detalles en su clase
car
. Aunque todos son "relevantes" para el automóvil, es muy difícil rastrear el comportamiento de dicha clasecar
. Y cuando algún día su colega cambie accidentalmente (o, en algún caso, intencionalmente) el tamaño del parabrisas según el modelo de la bombilla de la luz trasera, le llevará una eternidad descubrir por qué el parabrisas ya no cabe en su automóvil.Con respecto al 2, imagine que intenta definir todos los comportamientos que un automóvil puede tener en la clase
car
, perocar::open_roof()
que solo estará disponible para los convertibles, ycar::open_rear_door()
no se aplica a esos automóviles de 2 puertas. Aunque los convertibles y los autos de 2 puertas son "autos" por definición, implementarlos en la clasecar
complica la clase. La clase se vuelve más difícil de usar y más difícil de mantener.Además de estas 2 pautas, sugeriría una definición clara del alcance de la clase. Una vez que defina el alcance, implemente la clase estrictamente basada en la definición. La mayoría de las veces, las personas obtienen una clase "demasiado grande" debido a la mala definición del alcance, o debido a las características arbitrarias agregadas a la clase
fuente
Di lo que necesitas en 300 líneas.
Nota: esta regla general supone que está siguiendo el enfoque de 'una clase por archivo', que es en sí mismo una buena idea de mantenimiento
No es una regla absoluta, pero si veo más de 200 líneas de código en una clase, sospecho. Se encienden 300 líneas y campanas de advertencia. Cuando abro el código de otra persona y encuentro 2000 líneas, sé que es tiempo de refactorización sin siquiera leer la primera página.
Principalmente esto se reduce a la mantenibilidad. Si el objeto es tan complejo que no puede expresar su comportamiento en 300 líneas, será muy difícil que alguien más lo entienda.
Me doy cuenta de que estoy doblando esta regla en el Modelo -> ViewModel -> Ver mundo donde estoy creando un 'objeto de comportamiento' para una página de IU porque a menudo se necesitan más de 300 líneas para describir la serie completa de comportamientos en una página. Sin embargo, todavía soy nuevo en este modelo de programación y encuentro buenas formas de refactorizar / reutilizar comportamientos entre páginas / modelos de vista, lo que reduce gradualmente el tamaño de mis modelos de vista.
fuente
Vayamos al revés:
En realidad, es una piedra angular de la programación, de la cual OO es solo un paradigma.
Dejaré que Antoine de Saint-Exupéry responda tu pregunta (porque soy vago;)):
Cuanto más simple sea la clase, más seguro estará de que es correcto, más fácil será probarlo por completo .
fuente
Estoy con el OP en la clasificación Feature Envy. Nada me irrita más que tener un montón de clases con un montón de métodos que funcionan en las partes internas de otras clases. OO sugiere que modele datos y las operaciones en esos datos. ¡Eso no significa que coloque las operaciones en un lugar diferente de los datos! Está tratando de llegar a poner los datos y los métodos juntos .
Con los modelos
car
yperson
tan frecuentes, sería como poner el código para hacer la conexión eléctrica, o incluso hacer girar el motor de arranque, en laperson
clase. Espero que esto obviamente sea incorrecto para cualquier programador experimentado.Realmente no tengo un tamaño ideal de clase o método, pero prefiero mantener mis métodos y funciones ajustados en una pantalla para poder verlos todos en un solo lugar. (Tengo Vim configurado para abrir una ventana de 60 líneas, que está justo alrededor del número de líneas en una página impresa). Hay lugares donde esto no tiene mucho sentido, pero funciona para mí. Me gusta seguir la misma directriz para las definiciones de clase, y tratar de mantener mis archivos en unas pocas "páginas" de largo. Cualquier cosa por encima de 300, 400 líneas comienza a sentirse difícil de manejar (principalmente porque la clase ha comenzado a asumir demasiadas responsabilidades directas).
fuente
Cuando una definición de clase tiene un par de cientos de métodos, es demasiado grande.
fuente
Estoy totalmente de acuerdo con el uso del principio de responsabilidad única para las clases, pero si se sigue y da como resultado clases grandes, que así sea. El enfoque real debe ser que los métodos y las propiedades individuales no sean demasiado grandes, la complejidad ciclomática debe ser la guía allí y el seguimiento que puede dar lugar a muchos métodos privados que aumentarán el tamaño general de la clase pero lo dejarán legible y comprobable a un nivel granular. Si el código sigue siendo legible, el tamaño es irrelevante.
fuente
Emitir una orden de compra es a menudo un proceso complicado que involucra más que solo la orden de compra. En este caso, mantener la lógica de negocios en una clase separada y simplificar la orden de compra es probablemente lo correcto. Sin embargo, en general, tienes razón: no quieres clases de "estructura de datos". Por ejemplo, el método de su clase de negocios "POManager"
issue()
probablemente debería llamar alissue()
método del PO . El PO puede establecer su estado en "Emitido" y anotar la fecha de emisión, mientras que el POManager puede enviar una notificación a las cuentas por pagar para informarles que esperan una factura y otra notificación al inventario para que sepan que hay algo entrando y La fecha esperada.Cualquiera que empuje las "reglas" para el tamaño del método / clase obviamente no ha pasado suficiente tiempo en el mundo real. Como otros han mencionado, a menudo es un indicador de refactorización, pero a veces (especialmente en el trabajo de tipo "procesamiento de datos") realmente necesita escribir una clase con un método único que abarque más de 500 líneas. No te obsesiones con eso.
fuente
En términos del tamaño físico de una clase, ver una clase grande es una indicación de un olor a código, pero no necesariamente significa que siempre haya olores. (Por lo general, lo son) Como lo dirían los piratas de Piratas del Caribe, esto es más lo que llamarías "pautas" que reglas reales. Intenté seguir estrictamente el "no más de cien LOC en una clase" una vez y el resultado fue MUCHA ingeniería innecesaria.
Normalmente comienzo mis diseños con esto. ¿Qué hace esta clase en el mundo real? ¿Cómo debería mi clase reflejar este objeto como se indica en sus requisitos? Agregamos un poco de estado aquí, un campo allí, un método para trabajar en esos campos, agregamos algunos más y ¡listo! Tenemos una clase trabajadora. Ahora complete las firmas con las implementaciones adecuadas y estamos listos, o eso cree. Ahora tenemos una gran clase con toda la funcionalidad que necesitamos. Pero el problema con esto es que no toma en cuenta la forma en que funciona el mundo real. Y con eso quiero decir que no tiene en cuenta, "CAMBIO".
La razón por la que las clases se dividen preferiblemente en partes más pequeñas es que es difícil cambiar las clases grandes más adelante sin afectar la lógica existente o introducir un nuevo error o dos sin querer o simplemente hacer que todo sea difícil de reutilizar. Aquí es donde entran en juego la refactorización, los patrones de diseño, SOLID, etc. y el resultado final suele ser una clase pequeña que trabaja en otras subclases más pequeñas y más granulares.
Además, en su ejemplo, agregar IssuePO, ApprovePO y Cancel PO a la clase PurchaseOrder parece ilógico. Las órdenes de compra no se emiten, aprueban ni cancelan. Si PO debe tener métodos, entonces los métodos deberían funcionar en su estado, no en la clase en su conjunto. Realmente son solo clases de datos / registros en las que trabajan otras clases.
fuente
Lo suficientemente grande como para contener toda la lógica necesaria para realizar su trabajo.
Suficientemente pequeño para ser mantenible y seguir el Principio de responsabilidad única .
fuente