El principio se define como módulos que tienen una razón para cambiar . Mi pregunta es, ¿seguramente estas razones para cambiar no se conocen hasta que el código realmente comience a cambiar? Casi todas las piezas de código tienen numerosas razones por las cuales podría cambiar, pero seguramente intentar anticipar todo esto y diseñar su código con esto en mente terminaría con un código muy pobre. ¿No es una mejor idea comenzar realmente a aplicar SRP cuando las solicitudes para cambiar el código comienzan a llegar? Más específicamente, cuando un fragmento de código ha cambiado más de una vez por más de una razón, lo que demuestra que tiene más de una razón para cambiar. Suena muy anti-Agile intentar adivinar las razones del cambio.
Un ejemplo sería una pieza de código que imprime un documento. Se presenta una solicitud para cambiarlo para imprimir a PDF y luego se realiza una segunda solicitud para cambiarlo y aplicar un formato diferente al documento. En este punto, tiene pruebas de más de una razón para cambiar (y la violación de SRP) y debe realizar la refactorización adecuada.
fuente
Respuestas:
Por supuesto, el principio YAGNI le indicará que aplique SRP no antes de que realmente lo necesite. Pero la pregunta que debe hacerse es: ¿necesito aplicar SRP primero y solo cuando realmente tengo que cambiar mi código?
Según mi experiencia, la aplicación de SRP le brinda un beneficio mucho antes: cuando tiene que averiguar dónde y cómo aplicar un cambio específico en su código. Para esta tarea, debe leer y comprender sus funciones y clases existentes. Esto se vuelve mucho más fácil cuando todas sus funciones y clases tienen una responsabilidad específica. Entonces, en mi humilde opinión, debe aplicar SRP siempre que haga que su código sea más fácil de leer, cada vez que haga que sus funciones sean más pequeñas y más autodescriptivas. Entonces la respuesta es sí , tiene sentido aplicar SRP incluso para el nuevo código.
Por ejemplo, cuando su código de impresión lee un documento, formatea el documento e imprime el resultado en un dispositivo específico, estas son 3 responsabilidades separables claras. Por lo tanto, haga al menos 3 funciones de ellas, asígneles nombres. Por ejemplo:
Ahora, cuando tiene un nuevo requisito para cambiar el formato del documento u otro para imprimir en PDF, sabe exactamente en cuál de estas funciones o ubicaciones en el código debe aplicar los cambios, y aún más importante, dónde no.
Por lo tanto, cada vez que llega a una función que no comprende porque la función hace "demasiado", y no está seguro de si aplicar un cambio y dónde, entonces considere refactorizar la función en funciones separadas más pequeñas. No esperes hasta que tengas que cambiar algo. El código es 10 veces más leído que modificado, y las funciones más pequeñas son mucho más fáciles de leer. Según mi experiencia, cuando una función tiene una cierta complejidad, siempre se puede dividir la función en diferentes responsabilidades, independientemente de saber qué cambios vendrán en el futuro. Bob Martin típicamente va un paso más allá, mira el enlace que di en mis comentarios a continuación.
EDITAR: a su comentario: La principal responsabilidad de la función externa en el ejemplo anterior no es imprimir en un dispositivo específico, o formatear el documento, es integrar el flujo de trabajo de impresión . Por lo tanto, en el nivel de abstracción de la función externa, un nuevo requisito como "los documentos ya no deben formatearse" o "el documento debe enviarse por correo en lugar de imprimirse" es simplemente "la misma razón", es decir, "el flujo de trabajo de impresión ha cambiado". Si hablamos de cosas así, es importante mantener el nivel correcto de abstracción .
fuente
Creo que estás malinterpretando SRP.
La única razón para el cambio NO se trata de cambiar el código sino de lo que hace su código.
fuente
Creo que la definición de SRP como "tener una razón para cambiar" es engañosa exactamente por esta razón. Tómelo exactamente al pie de la letra: el Principio de Responsabilidad Única dice que una clase o función debe tener exactamente una responsabilidad. Tener una sola razón para cambiar es un efecto secundario de solo hacer una cosa para comenzar. No hay razón para que al menos no pueda hacer un esfuerzo hacia la responsabilidad única en su código sin saber nada sobre cómo podría cambiar en el futuro.
Una de las mejores pistas para este tipo de cosas es cuando elige nombres de clase o función. Si no es evidente de inmediato cómo debe llamarse la clase, o si el nombre es particularmente largo / complejo, o si el nombre usa términos genéricos como "gerente" o "utilidad", entonces probablemente esté violando SRP. Del mismo modo, al documentar la API, debería ser rápidamente evidente si está violando SRP en función de la funcionalidad que está describiendo.
Hay, por supuesto, matices para SRP que no puede saber hasta más adelante en el proyecto: lo que parecía una responsabilidad única resultó ser dos o tres. Estos son casos en los que tendrá que refactorizar para implementar SRP. Pero eso no significa que se deba ignorar SRP hasta que reciba una solicitud de cambio; que derrota el propósito de SRP!
Para hablar directamente a su ejemplo, considere documentar su método de impresión. Si desea decir "este método da formato a los datos para la impresión y la envía a la impresora", que y es lo que se obtiene: eso no es una única responsabilidad, que es dos responsabilidades: formato y enviar a la impresora. Si reconoce esto y los divide en dos funciones / clases, cuando lleguen sus solicitudes de cambio, ya tendría una sola razón para que cada sección cambie.
fuente
Muchas veces me pegué un tiro en el pie al pasar demasiado tiempo adaptando el código para acomodar esos cambios. En lugar de simplemente imprimir el maldito estúpido PDF.
Refactorizador para reducir el código
El patrón de un solo uso puede crear código hinchado. Donde los paquetes están contaminados con pequeñas clases específicas que crean un montón de código basura que no tiene sentido individualmente. Debe abrir docenas de archivos de origen solo para comprender cómo llega a la parte de impresión. Además de eso, puede haber cientos, si no miles de líneas de código que están en su lugar solo para ejecutar 10 líneas de código que hacen la impresión real.
Crea una diana
El patrón de uso único estaba destinado a reducir el código fuente y mejorar la reutilización del código. Estaba destinado a crear especializaciones e implementaciones específicas. Una especie de
bullseye
código fuente para que usted lo hagago to specific tasks
. Cuando había un problema con la impresión, sabía exactamente a dónde ir para solucionarlo.El uso único no significa fractura ambigua
Sí, tiene un código que ya imprime un documento. Sí, ahora debe cambiar el código para imprimir también archivos PDF. Sí, ahora debe cambiar el formato del documento.
¿Estás seguro de que
usage
ha cambiado significativamente?Si la refactorización hace que las secciones del código fuente se generalicen demasiado. Hasta el punto de que la intención original de
printing stuff
ya no es explícita, ha creado fracturas ambiguas en el código fuente.Mantenga siempre su código fuente en la organización más fácil de entender.
No seas un relojero
Demasiadas veces he visto a los desarrolladores colocarse un ocular y centrarse en los pequeños detalles hasta el punto de que nadie más podría volver a armar las piezas si se desmoronara.
fuente
Una razón para el cambio es, en última instancia, un cambio en la especificación o información sobre el entorno donde se ejecuta la aplicación. Por lo tanto, un principio de responsabilidad única le dice que escriba cada componente (clase, función, módulo, servicio ...) para que tenga que considerar la menor especificación y el entorno de ejecución posible.
Como conoce la especificación y el entorno cuando escribe el componente, puede aplicar el principio.
Si considera el ejemplo de código que imprime un documento. Debe considerar si puede definir la plantilla de diseño sin considerar que el documento terminará en PDF. Puedes hacerlo, por lo que SRP te dice que deberías.
Por supuesto, YAGNI te está diciendo que no deberías. Necesita encontrar el equilibrio entre los principios de diseño.
fuente
Flup se dirige en la dirección correcta. El "principio de responsabilidad única" se aplicó originalmente a los procedimientos. Por ejemplo, Dennis Ritchie diría que una función debe hacer una cosa y hacerlo bien. Luego, en C ++, Bjarne Stroustrup diría que una clase debería hacer una cosa y hacerlo bien.
Tenga en cuenta que, excepto como reglas generales, estos dos tienen poco o nada que ver formalmente entre sí. Solo atienden a lo que es conveniente expresar en el lenguaje de programación. Bueno, eso es algo. Pero es una historia bastante diferente a la que está conduciendo flup.
Las implementaciones modernas (es decir, ágiles y DDD) se centran más en lo que es importante para el negocio que en lo que el lenguaje de programación puede expresar. La parte sorprendente es que los lenguajes de programación aún no se han puesto al día. Los antiguos lenguajes similares a FORTRAN capturan responsabilidades que se ajustan a los principales modelos conceptuales de la época: los procesos que uno aplicaba a cada tarjeta a medida que pasaba por el lector de tarjetas, o (como en C) el procesamiento que acompañaba cada interrupción. Luego vinieron los idiomas ADT, que habían madurado hasta el punto de capturar lo que la gente DDD reinventaría más tarde como importante (aunque Jim Neighbours tenía la mayor parte de esto resuelto, publicado y en uso en 1968): lo que hoy llamamos clases . (NO son módulos).
Este paso fue menos una evolución que un columpio pendular. A medida que el péndulo se movía hacia los datos, perdimos el modelo de caso de uso inherente a FORTRAN. Eso está bien cuando su enfoque principal involucra los datos o las formas en una pantalla. Es un gran modelo para programas como PowerPoint, o al menos para sus operaciones simples.
Lo que se perdió son las responsabilidades del sistema . No vendemos los elementos de DDD. Y no conocemos bien los métodos de clase. Vendemos responsabilidades del sistema. En algún nivel, debe diseñar su sistema en torno al principio de responsabilidad única.
Entonces, si nos fijamos en personas como Rebecca Wirfs-Brock, o yo, que solían hablar sobre métodos de clase, ahora estamos hablando en términos de casos de uso. Eso es lo que vendemos. Esas son las operaciones del sistema. Un caso de uso debe tener una sola responsabilidad. Un caso de uso rara vez es una unidad arquitectónica. Pero todos intentaban fingir que sí. Testigo de la gente SOA, por ejemplo.
Es por eso que estoy entusiasmado con la arquitectura DCI de Trygve Reenskaug, que es lo que se describe en el libro de Arquitectura Lean anterior. Finalmente le da cierta importancia a lo que solía ser un respeto arbitrario y místico a la "responsabilidad única", como se encuentra en la mayoría de los argumentos anteriores. Esa estatura se relaciona con los modelos mentales humanos: los usuarios finales primero Y los programadores después. Se relaciona con preocupaciones comerciales. Y, casi por casualidad, encapsula el cambio a medida que flup nos desafía.
El principio de responsabilidad única, tal como lo conocemos, es un dinosaurio sobrante de sus días de origen o un caballo de pasatiempo que utilizamos como sustituto de la comprensión. Necesitas dejar algunos de estos caballos de afición para hacer un gran software. Y eso requiere pensar fuera de la caja. Mantener las cosas simples y fáciles de entender solo funciona cuando el problema es simple y fácil de entender. No estoy terriblemente interesado en esas soluciones: no son típicas, y no es donde radica el desafío.
fuente
Sí, el Principio de Responsabilidad Única debe aplicarse al nuevo código.
¡Pero! ¿Qué es una responsabilidad?
¿Es "imprimir un informe una responsabilidad"? La respuesta, creo que es "Quizás".
Tratemos de usar la definición de SRP como "tener una sola razón para cambiar".
Supongamos que tiene una función que imprime informes. Si tiene dos cambios:
Luego, el primer cambio es "cambiar el estilo del informe", el otro es "cambiar el formato de salida del informe" y ahora debe colocarlos en dos funciones diferentes porque estas son cosas diferentes.
Pero si su segundo cambio hubiera sido:
2b. cambiar esa función porque su informe necesita una fuente diferente
Yo diría que ambos cambios son "cambiar el estilo del informe" y pueden permanecer en una función.
Entonces, ¿dónde nos deja eso? Como de costumbre, debe tratar de mantener las cosas simples y fáciles de entender. Si cambiar el color de fondo significa 20 líneas de código y cambiar la fuente significa 20 líneas de código, vuelva a hacer dos funciones. Si es una línea cada una, manténgala en una.
fuente
Cuando esté diseñando un nuevo sistema, es aconsejable considerar el tipo de cambios que puede tener que hacer durante su vida útil y qué tan caros se les dará a la arquitectura que está implementando. Dividir su sistema en módulos es una decisión costosa de equivocarse.
Una buena fuente de información es el modelo mental en la cabeza de los expertos de dominio de la empresa. Tome el ejemplo del documento, el formato y el pdf. Los expertos en dominios probablemente le dirán que formatean sus letras usando plantillas de documentos. Ya sea en papelería o en Word o lo que sea. Puede recuperar esta información antes de comenzar a codificar y usarla en su diseño.
Una gran lectura sobre estas cosas: Lean Architecture por Coplien
fuente
"Imprimir" es muy parecido a "ver" en MVC. Cualquiera que entienda los conceptos básicos de los objetos lo entenderá.
Es una responsabilidad del sistema . Se implementa como un mecanismo, MVC, que involucra una impresora (la Vista), lo que se imprime (el Módulo) y la solicitud y las opciones de la impresora (del Controlador).
Tratar de localizar esto como una responsabilidad de clase o módulo es una tontería y refleja el pensamiento de 30 años. Hemos aprendido mucho desde entonces, y se evidencia ampliamente en la literatura y en el código de programadores maduros.
fuente
Idealmente, ya tendrá una buena idea de cuáles son las responsabilidades de las diversas partes del código. Divídase en responsabilidades de acuerdo con sus primeros instintos, posiblemente teniendo en cuenta lo que quieren hacer las bibliotecas que está utilizando (delegar una tarea, una responsabilidad, a una biblioteca generalmente es una gran cosa, siempre que esa biblioteca pueda realmente hacer la tarea ) Luego, refine su comprensión de las responsabilidades de acuerdo con los requisitos cambiantes. Cuanto mejor entienda el sistema inicialmente, menos necesita cambiar fundamentalmente las asignaciones de responsabilidad (aunque a veces descubre que una responsabilidad se divide mejor en sub-responsabilidades).
No es que deba pasar mucho tiempo preocupándose por eso. Una característica clave del código es que se puede cambiar más tarde, no tiene que hacerlo completamente bien la primera vez. Simplemente trate de mejorar con el tiempo para aprender qué tipo de responsabilidades de forma tiene para que pueda cometer menos errores en el futuro.
Esto es estrictamente una indicación de que la responsabilidad general - "imprimir" el código - tiene sub-responsabilidades y debe dividirse en partes. Eso no es una violación de SRP per se, sino más bien una indicación de que probablemente se requiera la partición (quizás en subtareas de "formateo" y "representación"). ¿Puede describir esas responsabilidades con claridad para que pueda comprender lo que sucede dentro de las subtareas sin mirar su implementación? Si puede, es probable que sean divisiones razonables.
También podría ser más claro si miramos un ejemplo real simple. Consideremos el
sort()
método de utilidad enjava.util.Arrays
. ¿Qué hace? Ordena una matriz, y eso es todo lo que hace. No imprime los elementos, no encuentra al miembro más apto moralmente, no silba a Dixie . Simplemente ordena una matriz. No tienes que saber cómo tampoco. La clasificación es la única responsabilidad de ese método. (De hecho, hay muchos métodos de clasificación en Java por razones técnicas algo desagradables que tienen que ver con los tipos primitivos; sin embargo, no tiene que prestarle atención, ya que todos tienen responsabilidades equivalentes).Haga sus métodos, sus clases, sus módulos, haga que tengan un papel tan claramente designado en la vida. Mantiene la cantidad que debe comprender de inmediato, y eso a su vez es lo que le permite manejar el diseño y el mantenimiento de un sistema grande.
fuente