Pregunta

Soy un desarrollador desde hace mucho tiempo (tengo 49 años) pero es nuevo en el desarrollo orientado a objetos. He estado leyendo sobre OO desde el Eiffel de Bertrand Meyer, pero he hecho muy poca programación OO.

El punto es que cada libro sobre el diseño OO comienza con un ejemplo de un barco, un 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, generalmente van algo 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 debería caber en una sola página" (agregaría "¿en qué tamaño de monitor?" Ahora que intentamos no para imprimir código!).

Tomar por ejemplo un PurchaseOrder clase, que tiene una máquina de estado finito que controla su comportamiento y una colección de PurchaseOrderItem, uno de los argumentos aquí en el trabajo es que debemos usar un PurchaseOrder clase simple, con algunos métodos (poco más que una clase de datos), y tener un PurchaseOrderFSM "Clase experta" que maneja la máquina de estado finito para el PurchaseOrder.

Diría que cae en la clasificación de "envidia de características" o "intimidad inapropiada" de Jeff Atwood's El código huele Publicar en la codificación de horror. Simplemente lo llamaría sentido común. Si puedo emitir, aprobar o cancelar mi orden de compra real, entonces el PurchaseOrder la clase debería tener issuePO, approvePO y cancelPO métodos.

¿No va eso con la "cohesión maximizar" y "minimizar el acoplamiento" los principios de edad de edad que entiendo como piedras angulares de OO?

Además, ¿eso no ayuda a la mantenibilidad de la clase?

¿Fue útil?

Solución

Una clase debe usar el principio de responsabilidad única. La mayoría de las clases muy grandes que he visto hacen a muchas cosas, por lo que son demasiado grandes. Mire cada método y código decidir si está en esta clase o un código duplicado separado es una pista. Es posible que tenga un método ISSUEPO, pero ¿contiene 10 líneas de código de acceso a datos, por ejemplo? Ese código probablemente no debería estar allí.

Otros consejos

En mi opinión, el tamaño de la clase no importa mientras las variables, las funciones y los métodos sean relevantes para la clase en cuestión.

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 en sí mismo no es un problema, pero como todos los olores, indica un potencial problema. En términos generales, cuando la clase es grande, es una indicación de que la clase está haciendo más de lo que debería una sola clase.

Para la analogía de tu auto, si la clase car es demasiado grande, eso es probablemente una indicación de que debe dividirse en clase engine, clase doors, y clase windshields, etc.

No creo que haya una definición específica de que una clase sea demasiado grande. Sin embargo, usaría las siguientes pautas para determinar si debería refactorizar mi clase o redefinir el alcance de la clase:

  1. ¿Hay muchas variables que son independientes entre sí? (Mi tolerancia personal es de alrededor de 10 ~ 20 en la mayoría de los casos)
  2. ¿Hay muchos métodos diseñados para cajas de esquina? (Mi tolerancia personal es ... bueno, depende en gran medida de mi estado de ánimo, supongo)

Con respecto a 1, imagine que define el tamaño de su parabrisas, el tamaño de la rueda, el modelo de bombilla 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 clase car. Y cuando algún día su colega accidentalmente (o, en algún caso, intencionalmente) cambia el tamaño del parabrisas en función del modelo de bombilla trasera, le lleva una eternidad descubrir por qué el parabrisas ya no encaja en su automóvil.

Con respecto a 2, imagine que intenta definir todos los comportamientos que un automóvil puede tener en la clase car, pero car::open_roof() solo estará disponible para convertibles, y car::open_rear_door() No se aplica a esos autos de 2 puertas. Aunque los convertibles y los autos de 2 puertas son "autos" por definición, implementándolos en la clase car complica la clase. La clase se vuelve más difícil de usar y más difícil de mantener.

Aparte de estas 2 pautas, sugeriría una definición clara del alcance de la clase. Una vez que defina el alcance, implementa la clase estrictamente en función de 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

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 por derecho propio 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, sospecha. Se encienden 300 líneas y campanas de advertencia. Cuando abro el código de otra persona y encuentro 2000 líneas, sé que está refactorizando el tiempo sin siquiera leer la primera página.

Principalmente esto se reduce a la mantenibilidad. Si el ojo es tan complejo que no puede expresar su comportamiento en 300 líneas, será muy difícil de entender para otra persona.

Estoy descubriendo que estoy doblando esta regla en el modelo -> ViewModel -> View World donde estoy creando un 'objeto de comportamiento' para una página de UI 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 que reduce gradualmente el tamaño de mis modelos de vista.

Vamos a hacerlo hacia atrás:

¿No va eso con la "cohesión maximizar" y "minimizar el acoplamiento" los principios de edad de edad que entiendo como piedras angulares de OO?

En realidad, es una piedra angular de la programación, de la cual OO es solo un paradigma.

Dejaré Antoine de Saint-Exupéry Responda su pregunta (porque soy perezoso;)):

Se logra la perfección, no cuando no hay nada más que agregar, pero cuando no queda nada que quitar.

Cuanto más simple sea la clase, más segura estará bien, más fácil será completamente Pruébalo.

Estoy con el OP en la clasificación de envidia de la función. 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 otra clase. OO sugiere que modele datos y las operaciones de esos datos. ¡Eso no significa que coloque las operaciones en un lugar diferente de los datos! Está tratando de hacer que ponga los datos y esos métodos. juntos.

Con el car y person modelos tan frecuentes, sería como poner el código para hacer la conexión eléctrica, o incluso girar el iniciador, en el person clase. Espero que esto sea Obviamente equivocado a cualquier programador experimentado.

Realmente no tengo una clase o tamaño de método ideal, pero prefiero mantener mis métodos y funciones que se ajustan en una pantalla para que pueda ver su totalidad en un solo lugar. (Tengo VIM configurado para abrir una ventana de 60 líneas, que resulta ser justo alrededor de la cantidad 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 guía para las definiciones de clase e intentar mantener mis archivos en unas pocas "páginas" de largo. Cualquier cosa más de 300, 400 líneas comienzan a sentirse difíciles de manejar (principalmente porque la clase ha comenzado a asumir demasiadas responsabilidades directas).

Cuando una definición de clase tiene un par de cientos de métodos, es demasiado grande.

Estoy totalmente de acuerdo con el uso del principio de responsabilidad única Para las clases, pero si eso se sigue y da como resultado grandes clases, entonces, que así sea. El enfoque real debe ser que los métodos y propiedades individuales no sean demasiado grandes, complejidad ciclomática Debe ser la guía allí y seguir 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 permanece legible, entonces el tamaño es irrelevante.

La emisión de una orden de compra es a menudo un proceso complicado que implica algo más que la orden de compra. En este caso, mantener la lógica comercial en una clase separada y simplificar la orden de compra es probablemente lo correcto. En general, sin embargo, tiene razón: no desea clases de "estructura de datos". Por ejemplo, su "Pomanager" clase ejecutiva " issue() El método probablemente debería llamar a los PO issue() método. Luego, el PO puede establecer su estado en "emitido" y anotar la fecha de emisión, mientras que el pomano puede enviar una notificación a las cuentas pagaderas para hacerles saber que esperan una factura, y otra notificación al inventario para que sepan que hay algo que está entrando y la fecha esperada.

Cualquiera que presione "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 solo método que abarque más de 500 líneas. No te quedes colgado.

Encontré varios autores que dan recetas como "una clase debería caber en una sola página"

En términos de 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 hay olores. (Por lo general, lo son) Como lo expresarían los piratas en los piratas del Caribe, esto es más lo que llamaría "pautas" que las reglas reales. Había intentado seguir estrictamente el "no más de cien loc en una clase" una vez y el resultado fue una gran ingeniería innecesaria.

Por lo tanto, generalmente van algo como "cuanto mejor sea el modelo, mejor representa el objeto en la aplicación y mejor sale todo".

Normalmente empiezo 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? Agreguamos un poco de estado aquí, un campo allí, un método para trabajar en esos campos, ¡agregamos algunos más y voila! Tenemos una clase trabajadora. Ahora complete las firmas con las implementaciones adecuadas y estamos listos, o eso piensa. Ahora tenemos una gran clase grande con toda la funcionalidad que necesitamos. Pero el problema con esto es que no tiene en cuenta la forma en que el mundo real funciona. Y con eso quiero decir que no tiene en cuenta "cambio".

La razón por la cual 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 la refactorización, los patrones de diseño, los sólidos, etc. entran al juego y el resultado final suele ser una clase pequeña que trabaja en otras subcases más pequeñas y más granulares.

Además, en su ejemplo, agregar Issuepo, aprobación y cancelar PO en la clase de compras de compras parece ilógica. Las órdenes de compra no emiten, aprueban ni se cancelan. Si PO debería tener métodos, entonces los métodos deberían estar funcionando en su estado, no en la clase en su conjunto. Realmente son solo clases de datos/registros en las que otras clases funcionan.

Lo suficientemente grande como para contener toda la lógica necesaria para realizar su trabajo.

Lo suficientemente pequeño para ser mantenible y seguir el Principio de responsabilidad única.

Licenciado bajo: CC-BY-SA con atribución
scroll top