Recientemente he estado leyendo un sitio web sobre el desarrollo de código limpio (no pongo un enlace aquí porque no está en inglés).
Uno de los principios anunciados por este sitio es el Principio Abierto Cerrado : cada componente de software debe estar abierto para la extensión y cerrado para la modificación. Por ejemplo, cuando hemos implementado y probado una clase, solo debemos modificarla para corregir errores o agregar nuevas funcionalidades (por ejemplo, nuevos métodos que no influyan en los existentes). La funcionalidad y la implementación existentes no deben modificarse.
Normalmente aplico este principio definiendo una interfaz I
y una clase de implementación correspondiente A
. Cuando la clase se A
ha estabilizado (implementado y probado), normalmente no lo modifico demasiado (posiblemente, para nada), es decir
- Si llegan nuevos requisitos (por ejemplo, rendimiento o una implementación totalmente nueva de la interfaz) que requieren grandes cambios en el código, escribo una nueva implementación
B
y sigo usandoA
mientrasB
no esté madura. CuandoB
está maduro, todo lo que se necesita es cambiar cómoI
se instancia. - Si los nuevos requisitos también sugieren un cambio en la interfaz, defino una nueva interfaz
I'
y una nueva implementaciónA'
. Por lo tantoI
,A
se congelan y se mantienen a la aplicación del sistema de producción, siempre y cuandoI'
yA'
no son lo suficientemente estable como para reemplazarlos.
Entonces, en vista de estas observaciones, me sorprendió un poco que la página web sugiriera el uso de refactorizaciones complejas , "... porque no es posible escribir código directamente en su forma final".
¿No hay una contradicción / conflicto entre hacer cumplir el Principio Abierto / Cerrado y sugerir el uso de refactorizaciones complejas como una mejor práctica? ¿O la idea aquí es que uno puede usar refactorizaciones complejas durante el desarrollo de una clase A
, pero cuando esa clase se haya probado con éxito, debería congelarse?
El principio Open-Closed es más un indicador de qué tan bien está diseñado su software ; No es un principio a seguir literalmente. También es un principio que ayuda a evitar que cambiemos accidentalmente las interfaces existentes (clases y métodos a los que llama y cómo espera que funcionen).
El objetivo es escribir software de calidad. Una de estas cualidades es la extensibilidad. Esto significa que es fácil agregar, eliminar, cambiar el código con esos cambios que tienden a limitarse a la menor cantidad posible de clases existentes. Agregar nuevo código es menos riesgoso que cambiar el código existente, por lo que, en este sentido, Open-Closed es algo bueno. ¿Pero de qué código estamos hablando exactamente? El delito de violar OC es mucho menor cuando puede agregar nuevos métodos a una clase en lugar de tener que modificar los existentes.
OC es fractal . Manzanas en todas las profundidades de su diseño. Todos asumen que solo se aplica a nivel de clase. Pero es igualmente aplicable a nivel de método o a nivel de ensamblaje.
La violación demasiado frecuente de OC en el nivel apropiado sugiere que tal vez sea hora de refactorizar . El "nivel apropiado" es una llamada de juicio que tiene todo que ver con su diseño general.
Seguir a Abierto-Cerrado significa literalmente que el número de clases explotará. Crearás (mayúscula "I") interfaces innecesariamente. Terminará con bits de funcionalidad repartidos entre las clases y luego tendrá que escribir mucho más código para conectarlo todo. En algún momento se te ocurrirá que cambiar la clase original hubiera sido mejor.
fuente
El principio abierto-cerrado parece ser un principio que apareció antes de que TDD fuera más frecuente. La idea es que es riesgoso refactorizar el código porque puede romper algo, por lo que es más seguro dejar el código existente tal como está y simplemente agregarlo. En ausencia de pruebas, esto tiene sentido. La desventaja de este enfoque es la atrofia del código. Cada vez que extiende una clase en lugar de refactorizarla, termina con una capa adicional. Simplemente estás atornillando el código en la parte superior. Cada vez que agregue más código, aumentará las posibilidades de duplicación. Imagina; Hay un servicio en mi base de código que quiero usar, creo que no tiene lo que quiero, así que creo una nueva clase para extenderlo e incluir mi nueva funcionalidad. Otro desarrollador aparece más tarde y también quiere usar el mismo servicio. Desafortunadamente, ellos no No me doy cuenta de que mi versión extendida existe. Codifican contra la implementación original pero también necesitan una de las características que codifiqué. En lugar de usar mi versión, ahora también amplían la implementación y agregan la nueva característica. Ahora tenemos 3 clases, la original y dos versiones nuevas que tienen alguna funcionalidad duplicada. Siga el principio abierto / cerrado y esta duplicación continuará acumulándose durante la vida útil del proyecto, lo que conducirá a una base de código innecesariamente compleja.
Con un sistema bien probado no hay necesidad de sufrir esta atrofia del código, puede refactorizar el código de manera segura permitiendo que su diseño asimile nuevos requisitos en lugar de tener que atornillarse continuamente en un nuevo código. Este estilo de desarrollo se llama diseño emergente y conduce a bases de código que pueden mantenerse en buena forma durante toda su vida útil en lugar de recolectar gradualmente cruft.
fuente
En palabras simples:
R. El principio de O / C significa que la especialización debe hacerse extendiendo, no modificando una clase para acomodar necesidades especializadas.
B. Agregar funcionalidades faltantes (no especializadas) significa que el diseño no se completó y debe agregarlo a la clase base, obviamente sin violar el contrato. Creo que esto no está violando el principio.
C. La refactorización no viola el principio.
Cuando un diseño madura , por ejemplo, después de un tiempo en producción:
fuente
Para mí, el Principio Abierto-Cerrado es una guía, no una regla dura y rápida.
Con respecto a la parte abierta del principio, las clases finales en Java y las clases en C ++ con todos los constructores declarados privados violan la parte abierta del principio abierto-cerrado. Hay buenos casos de uso sólidos (nota: sólido, no SÓLIDO) para las clases finales. Diseñar para la extensibilidad es importante. Sin embargo, esto requiere una gran cantidad de previsión y esfuerzo, y siempre estás bordeando la línea de violar a YAGNI (no lo vas a necesitar) e inyectando el olor del código de generalidad especulativa. ¿Deberían los componentes clave del software estar abiertos para la extensión? Si. ¿Todas? No. Eso en sí mismo es generalidad especulativa.
Con respecto a la parte cerrada, cuando se pasa de la versión 2.0 a 2.1 a 2.2 a 2.3 de algún producto, no modificar el comportamiento es una buena idea. A los usuarios realmente no les gusta cuando cada versión menor rompe su propio código de barras. Sin embargo, en el camino a menudo se encuentra que la implementación inicial en la versión 2.0 se rompió fundamentalmente, o que las restricciones externas que limitaron el diseño inicial ya no se aplican. ¿Sonríe y lo soporta y mantiene ese diseño en la versión 3.0, o hace que 3.0 no sea compatible con versiones anteriores en algún aspecto? La compatibilidad con versiones anteriores puede ser una gran limitación. Los límites de liberación principales son el lugar donde es aceptable romper la compatibilidad con versiones anteriores. Debe tener cuidado de que hacer esto puede molestar a sus usuarios. Tiene que haber un buen caso de por qué se necesita esta ruptura con el pasado.
fuente
Refactorizar, por definición, es cambiar la estructura del código sin cambiar el comportamiento. Entonces, cuando refactoriza, no agrega nuevas características.
Lo que hiciste como ejemplo para el principio Open Close suena bien. Este principio se trata de extender el código existente con nuevas características.
Sin embargo, no obtenga esta respuesta incorrecta. No quiero decir que solo debas hacer funciones o solo refactorizar grandes cantidades de datos. La forma más común de programación es hacer un poco de una función que inmediatamente hacer un poco de refactorización (combinado con pruebas, por supuesto, para asegurarse de que no haya cambiado ningún comportamiento). Refactorización compleja no significa refactorización "grande", significa aplicar técnicas de refactorización complicadas y bien pensadas.
Sobre los principios SÓLIDOS. Son realmente buenas pautas para el desarrollo de software, pero no son reglas religiosas que se sigan ciegamente. A veces, muchas veces, después de agregar una segunda y tercera característica, se da cuenta de que su diseño inicial, incluso si respeta Open-Close, no respeta otros principios o requisitos de software. Hay puntos en la evolución de un diseño y de un software cuando hay que hacer cambios más complejos. El objetivo es encontrar y darse cuenta de estos problemas lo antes posible y aplicar técnicas de refactorización lo mejor posible.
No existe el diseño perfecto. No existe tal diseño que pueda y deba respetar todos los principios o patrones existentes. Eso es codificar la utopía.
Espero que esta respuesta te haya ayudado en tu dilema. No dude en solicitar aclaraciones si es necesario.
fuente
B
y, cuando esté listo, reemplace la implementación anteriorA
con la nueva implementaciónB
(ese es un uso de interfaces).A
El código puede servir como base paraB
el código y luego puedo usar la refactorización delB
código durante su desarrollo, pero creo que elA
código ya probado debería permanecer congelado.B
se basa en el códigoA
como una evolución deA
, entonces, cuandoB
se libera,A
debe eliminarse y nunca volver a usarse. Los clientes que usaban anteriormenteA
solo lo usaránB
sin saber sobre el cambio, ya que la interfazI
no se cambió (¿tal vez un poco del Principio de sustitución de Liskov aquí? ... la L de SOLID)Según tengo entendido, si agrega nuevos métodos a la clase existente, entonces no romperá el OCP. Sin embargo, estoy un poco confundido con la adición de nuevas variables en la clase. Pero si cambia el método y los parámetros existentes en el método existente, seguramente romperá el OCP, porque el código ya se ha probado y aprobado si cambiamos intencionalmente el método [Cuando el cambio de requisito] entonces será un problema.
fuente