Principio de responsabilidad única: ¿lo estoy usando en exceso?

13

Para referencia: http://en.wikipedia.org/wiki/Single_responILITY_principle

Tengo un escenario de prueba en el que en un módulo de aplicación es responsable de crear entradas en el libro mayor. Hay tres tareas básicas que podrían llevarse a cabo:

  • Ver las entradas de contabilidad existentes en formato de tabla.
  • Cree una nueva entrada de libro mayor con el botón Crear.
  • Haga clic en una entrada del libro mayor en la tabla (mencionada en el primer puntero) y vea sus detalles en la página siguiente. Puede anular una entrada de libro mayor en esta página.

(Hay un par de operaciones / validaciones más en cada página, pero por brevedad lo limitaré a estas)

Entonces decidí crear tres clases diferentes:

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

Estas clases ofrecen los servicios que podrían llevarse a cabo en esas páginas y las pruebas de Selenium usan estas clases para llevar la aplicación a un estado en el que podría hacer cierta afirmación.

Cuando lo revisé con mi colega, él estaba abrumado y me pidió que hiciera una sola clase para todos. Aunque todavía siento que mi diseño está muy limpio, dudo si estoy usando en exceso el principio de responsabilidad única

Tarun
fuente
2
En mi humilde opinión, no tiene sentido tratar de responder a su pregunta en general, sin saber dos cosas: ¿Qué tan grandes son estas clases (por método de conteo y LOC)? ¿Y cuánto más grande / más complejo se espera que crezcan en el futuro previsible?
Péter Török
2
10 métodos cada uno es en mi humilde opinión una indicación clara de no poner el código en una clase con 30 métodos.
Doc Brown
66
Este es un territorio límite: una clase de 100 LOC y 10 métodos no es demasiado pequeña, y una de 300 LOC no es demasiado grande. Sin embargo, 30 métodos en una sola clase me parecen demasiado. En general, tiendo a estar de acuerdo con @Doc en que es menos riesgoso tener muchas clases pequeñas que tener una sola clase de sobrepeso. Especialmente teniendo en cuenta que las clases tienden naturalmente a aumentar de peso con el tiempo ...
Péter Török
1
@ PéterTörök: estoy de acuerdo, a menos que el código se pueda refactorizar en una clase que se pueda usar de forma intuitiva que reutilice el código en lugar de duplicar la funcionalidad como espero que tenga el OP.
SoylentGray
1
Quizás la aplicación de MVC aclararía todo esto: tres vistas, un modelo (Ledger) y probablemente tres controladores.
Kevin Cline

Respuestas:

19

Citando a @YannisRizos aquí :

Ese es un enfoque instintivo y probablemente sea correcto, ya que lo que es más probable que cambie es la lógica comercial de administrar los libros mayores. Si eso sucede, sería mucho más fácil mantener una clase que tres.

No estoy de acuerdo, al menos ahora, después de unos 30 años de experiencia en programación. Las clases más pequeñas en mi humilde opinión son mejores para mantener en casi todos los casos que se me ocurren en casos como este. Cuantas más funciones ponga en una clase, menos estructura tendrá y la calidad de su código se degrada, cada día un poco. Cuando entendí Tarun correctamente, cada una de estas 3 operaciones

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

es un caso de uso, cada una de estas clases consta de más de una función para realizar la tarea relacionada, y cada una puede desarrollarse y probarse por separado. Por lo tanto, la creación de clases para cada uno de ellos agrupa las cosas que pertenecen juntas, lo que hace que sea mucho más fácil identificar la parte del código que se va a cambiar si algo va a cambiar.

Si tiene funcionalidades comunes entre esas 3 clases, pertenecen a una clase base común, la Ledgerclase en sí (supongo que tiene una, al menos, para las operaciones CRUD) o en una clase auxiliar separada.

Si necesita más argumentos para crear muchas clases más pequeñas, le recomiendo que eche un vistazo al libro "Código limpio" de Robert Martin .

Doc Brown
fuente
esa es la razón por la que se me ocurrieron tres clases diff en lugar de empujar todo en una clase Como no hay una funcionalidad común entre ninguno de estos, no tengo ninguna clase común. Gracias por el enlace.
Tarun
2
"Las clases más pequeñas son mejores para mantener en casi todos los casos que se me ocurren". No creo que incluso Clean Code llegue tan lejos. ¿Realmente argumentaría que, en un patrón MVC, debería haber un controlador por página (o caso de uso, como lo dice), por ejemplo? En Refactorización, Fowler tiene dos olores de código juntos: uno que se refiere a tener muchas razones para cambiar una clase (SRP), y otro que se refiere a tener que editar muchas clases para un cambio.
pdr
@pdr, el OP indica que no hay una funcionalidad común entre estas clases, por lo que (para mí) es difícil imaginar una situación en la que necesite tocar más de una para hacer un solo cambio.
Péter Török
@pdr: Si tiene demasiadas clases para editar para un cambio, es principalmente una señal de que no refactorizó la funcionalidad común en un lugar separado. Pero en el ejemplo anterior, esto probablemente conduciría a una cuarta clase, y no solo a una.
Doc Brown
2
@pdr: Por cierto, ¿realmente leíste "Código limpio"? Bob Martin llega muy lejos al descomponer el código en unidades más pequeñas.
Doc Brown
11

No existe una implementación definitiva del Principio de Responsabilidad Única, ni ningún otro principio. Debe decidir en función de lo que es más probable que cambie.

Como @Karpie escribe en una respuesta anterior :

Solo necesita una clase, y la única responsabilidad de esa clase es administrar los libros mayores.

Ese es un enfoque instintivo y probablemente sea correcto, ya que lo que es más probable que cambie es la lógica comercial de administrar los libros mayores. Si eso sucede, sería mucho más fácil mantener una clase que tres.

Pero eso debe ser examinado y decidido por caso. Realmente no debe preocuparse por las preocupaciones con la posibilidad minúscula de cambio y concentrarse aplicando el principio sobre lo que es más probable que cambie. Si la lógica de administrar los libros de contabilidad es un proceso de múltiples capas y las capas tienden a cambiar de manera independiente o son preocupaciones que deberían estar separadas en principio (lógica y presentación), entonces debería usar clases separadas. Es un juego de probabilidades.

Una pregunta similar sobre el tema del uso excesivo de principios de diseño, que creo que le resultaría interesante: Patrones de diseño: cuándo usar y cuándo dejar de hacer todo usando patrones

Yannis
fuente
1
SRP, a mi entender, no es realmente un patrón de diseño.
Doc Brown
@DocBrown Por supuesto, no es un patrón de diseño, pero la discusión sobre la pregunta es extremadamente relevante ... Sin embargo, he actualizado la respuesta
Yannis
4

Examinemos estas clases:

  • LedgerLandingPage: el papel de esta clase es mostrar una lista de libros mayores. A medida que la aplicación crezca, es probable que desee agregar métodos para ordenar, filtrar, resaltar y fusionar de forma transparente los datos de otras fuentes de datos.

  • ViewLedgerEntryPage: esta página muestra el libro mayor en detalle. Parece bastante sencillo. Mientras los datos sean directos. Puede anular el libro mayor aquí. También podrías querer corregirlo. O coméntelo, o adjunte un archivo, recibo o número de reserva externa o lo que sea. Y una vez que permite la edición, definitivamente desea mostrar un historial.

  • CreateLedgerEntryPage: si ViewLedgerEntryPagetiene la capacidad de edición completa, la responsabilidad de esta clase puede cumplirse editando una "entrada en blanco", que puede tener sentido o no.

Estos ejemplos pueden parecer un poco exagerados, pero el punto es que desde un UX estamos hablando de características aquí. Una característica única es definitivamente una responsabilidad distinta y única. Debido a que los requisitos de esa característica pueden cambiar y los requisitos de dos características diferentes pueden cambiar en dos direcciones opuestas, y luego desearía haberse quedado con el SRP desde el principio.
Pero a la inversa, siempre puede colocar una fachada en tres clases, si tener una sola interfaz es más conveniente por cualquier razón que se le ocurra.


Existe un uso excesivo de SRP : si necesita una docena de clases para leer el contenido de un archivo, es bastante seguro asumir que está haciendo exactamente eso.

Si miras el SRP desde el otro lado, en realidad dice que una sola clase debería ocuparse de una sola responsabilidad. Sin embargo, tenga en cuenta que las responsabilidades existen solo dentro de los niveles de abstracción. Por lo tanto, tiene mucho sentido que una clase de un nivel de abstracción más alto lleve a cabo su responsabilidad delegando el trabajo en un componente de nivel más bajo, que se logra mejor a través de la inversión de dependencia .

back2dos
fuente
Supongo que ni siquiera yo podría haber descrito la responsabilidad de estas clases mejor que tú.
Tarun
2

¿Estas clases tienen solo una razón para cambiar?

Si aún no lo sabe, es posible que haya entrado en la trampa de diseño especulativo / sobre ingeniería (demasiadas clases, patrones de diseño demasiado complejos aplicados). No has satisfecho a YAGNI . En las primeras etapas del ciclo de vida del software (algunos) los requisitos pueden no estar claros. Por lo tanto, la dirección del cambio actualmente no es visible para usted. Si te das cuenta de eso, mantenlo en una o una cantidad mínima de clases. Sin embargo, esto conlleva una responsabilidad: tan pronto como los requisitos y la dirección del cambio sean más claros, debe repensar el diseño actual y realizar una refactorización para satisfacer la razón del cambio.

Si ya sabe que hay tres razones diferentes para el cambio y que se asignan a esas tres clases, simplemente aplicó la dosis correcta de SRP. Nuevamente, esto conlleva cierta responsabilidad: si está equivocado o si los requisitos futuros cambian inesperadamente, debe repensar el diseño actual y realizar una refactorización para satisfacer la razón del cambio.

El punto completo es:

  • Esté atento a los controladores para el cambio.
  • Elija un diseño flexible, que se puede refactorizar.
  • Prepárate para refactorizar el código.

Si tiene esta mentalidad, dará forma al código sin mucho miedo al diseño especulativo / a la ingeniería.

Sin embargo, siempre existe el factor tiempo: para muchos cambios no tendrá el tiempo que necesita. Refactorizar cuesta tiempo y esfuerzo. A menudo me encontré con situaciones en las que sabía que tenía que cambiar el diseño, pero debido a limitaciones de tiempo tuve que posponerlo. Esto sucederá y no se puede evitar. Descubrí que es más fácil refactorizar bajo código diseñado que sobre código diseñado. YAGNI me da una buena guía: en caso de duda, mantenga al mínimo la cantidad de clases y la aplicación de patrones de diseño.

Theo Lenndorff
fuente
1

Hay tres razones para invocar SRP como una razón para dividir una clase:

  • para que los cambios futuros en los requisitos puedan dejar algunas clases sin cambios
  • para que un error pueda aislarse más rápidamente
  • para hacer la clase más pequeña

Hay tres razones para negarse a dividir una clase:

  • para que los cambios futuros en los requisitos no hagan que hagas el mismo cambio en varias clases
  • para que la búsqueda de errores no implique el rastreo de largos caminos de indirección
  • para resistir las técnicas de ruptura de encapsulación (propiedades, amigos, lo que sea) que exponen información o métodos a todos cuando solo los necesita una clase relacionada

Cuando miro su caso e imagino cambios, algunos de ellos causarán cambios en varias clases (por ejemplo, agregar un campo al libro mayor, deberá cambiar los tres), pero muchos no lo harán, por ejemplo, agregar ordenamiento a la lista de libros mayores. o cambiar las reglas de validación al agregar una nueva entrada de libro mayor. La reacción de su colega es probablemente porque es difícil saber dónde buscar un error. Si algo se ve mal en la lista de libros mayores, ¿es porque se agregó incorrectamente o hay algo mal en la lista? Si hay una discrepancia entre el resumen y los detalles, ¿cuál está mal?

También es posible que su colega piense que los cambios en los requisitos que significan que tendrá que cambiar las tres clases son mucho más probables que los cambios en los que se saldría con el cambio de solo uno. Esa podría ser una conversación realmente útil para tener.

Kate Gregory
fuente
bueno ver una perspectiva diferente
Tarun
0

Creo que si el libro mayor fuera una tabla en db, sugiero que coloque estas tareas de estrés en una clase (por ejemplo, DAO). Si el libro mayor tiene más lógica y no era una tabla en db, sugiero que creemos más clases para hacer estas tareas (puede ser dos o tres clases) y proporcionar una clase de fachada para hacer simplemente para el cliente.

Mark xie
fuente
bueno, el libro mayor es una característica en la interfaz de usuario y hay muchas operaciones relacionadas que podrían llevarse a cabo desde diferentes páginas.
Tarun
0

En mi humilde opinión, no deberías estar usando clases en absoluto. No hay abstracciones de datos aquí. Los libros de contabilidad son un tipo de datos concreto. Los métodos para manipularlos y mostrarlos son funciones y procedimientos. Sin duda habrá muchas funciones de uso común para compartir, como formatear una fecha. Hay poco lugar para que los datos se abstraigan y se oculten en una clase en las rutinas de visualización y selección.

Hay algún caso para ocultar el estado en una clase para la edición del libro mayor.

Ya sea que estés abusando o no del SRP, estás abusando de algo más fundamental: estás abusando de la abstracción. Es un problema típico, engendrado por la noción mítica OO es un paradigma de desarrollo sensato.

La programación es 90% concreta: el uso de la abstracción de datos debe ser raro y cuidadosamente diseñado. La abstracción funcional, por otro lado, debería ser más común, ya que los detalles del lenguaje y la elección de algoritmos deben separarse de la semántica de la operación.

Yttrill
fuente
-1

Solo necesita una clase, y la responsabilidad única de esa clase es administrar los libros mayores .

sevenseacat
fuente
2
Para mí, una clase de '... gerente' generalmente muestra que no podría molestarse en desglosarlo aún más. ¿Qué es la gestión de la clase? La presentación, la persistencia?
sebastiangeiger
-1. Poner 3 o más casos de uso en 1 clase es exactamente lo que el SRP está tratando de evitar, y por buenas razones.
Doc Brown
@sebastiangeiger Entonces, ¿qué están haciendo las tres clases del OP? Presentación o persistencia?
sevenseacat
@Karpie Sinceramente espero una presentación. De acuerdo, el ejemplo no es realmente bueno, pero esperaría que '... Page' en un sitio web esté haciendo algo similar a una vista.
sebastiangeiger
2
De hecho, solo necesita una clase para toda la aplicación, con la única responsabilidad de hacer felices a los usuarios ;-)
Péter Török