¿Por qué muchos desarrolladores de software violan el principio de abrir / cerrar?

71

¿Por qué muchos desarrolladores de software violan el principio de apertura / cierre al modificar muchas cosas, como cambiar el nombre de las funciones que se interrumpirán? ¿La aplicación después de actualizar?

Esta pregunta salta a mi cabeza después de las versiones rápida y continua en la biblioteca Reaccionar .

Cada corto período de tiempo observo muchos cambios en la sintaxis, nombres de componentes, ... etc

Ejemplo en la próxima versión de React :

  

Nuevas advertencias de desaprobación

     

El mayor cambio es que hemos extraído React.PropTypes y   React.createClass en sus propios paquetes. Ambos siguen siendo accesibles   a través del objeto principal React, pero al usarlo se registrará una sola vez   Advertencia de desaprobación a la consola cuando está en modo de desarrollo. Esta voluntad   habilitar futuras optimizaciones de tamaño de código.

     

Estas advertencias no afectarán el comportamiento de su aplicación.   Sin embargo, nos damos cuenta de que pueden causar cierta frustración, especialmente si   utiliza un marco de prueba que trata console.error como un error.

  • ¿Estos cambios se consideran una violación de ese principio?
  • Como principiante en algo como Reaccionar , ¿cómo lo aprendo con estos? ¿Cambios rápidos en la biblioteca (es tan frustrante)?
pregunta Anyname Donotcare 30.04.2017 - 16:54

4 respuestas

143

La respuesta de IMHO a JacquesB, aunque contiene mucha verdad, muestra un malentendido fundamental del OCP. Para ser justos, su pregunta ya expresa este malentendido, el cambio de nombre de las funciones se rompe compatibilidad hacia atrás , pero no el OCP. Si parece necesario romper la compatibilidad (o mantener dos versiones del mismo componente para no romper la compatibilidad), ¡el OCP ya se rompió antes!

Como Jörg W Mittag ya mencionó en sus comentarios, el principio no dice "no se puede modificar el comportamiento de un componente", dice, uno debería intentar diseñar componentes de una manera. están abiertos para ser reutilizados (o extendidos) de varias maneras, sin la necesidad de modificación. Esto se puede hacer proporcionando los "puntos de extensión" correctos, o, como lo menciona @AntP, "descomponiendo una estructura de clase / función al punto donde cada punto de extensión natural está allí por defecto". En mi humilde opinión, seguir al OCP no tiene nada en común con "mantener la versión antigua sin cambios por compatibilidad hacia atrás" ! O, citando el comentario de @DerekElkin a continuación:

  

El OCP es un consejo sobre cómo escribir un módulo, [...] no sobre la implementación de un proceso de gestión de cambios que nunca permita que los módulos cambien.

Los buenos programadores utilizan su experiencia para diseñar componentes con los puntos de extensión "correctos" en mente (o, incluso mejor, de una manera que no se necesitan puntos de extensión artificiales). Sin embargo, para hacer esto correctamente y sin una sobreingeniería innecesaria, debe saber de antemano cómo se verán los futuros casos de uso de su componente. Incluso los programadores experimentados no pueden mirar hacia el futuro y conocer de antemano todos los requisitos futuros. Y es por eso que a veces es necesario violar la compatibilidad con versiones anteriores , sin importar cuántos puntos de extensión tenga su componente, o qué tan bien sigue el OCP con respecto a ciertos tipos de requisitos, siempre habrá un requisito Lo cual no se puede implementar fácilmente sin modificar el componente.

    
respondido por el Doc Brown 30.04.2017 - 23:35
66

El principio abierto / cerrado tiene ventajas, pero también tiene algunos inconvenientes graves.

En teoría el principio resuelve el problema de la compatibilidad con versiones anteriores creando un código que está "abierto para extensión pero cerrado para modificación". Si una clase tiene algunos requisitos nuevos, nunca modifica el código fuente de la clase en sí, sino que crea una subclase que anula solo los miembros apropiados necesarios para cambiar el comportamiento. Por lo tanto, todo el código escrito en contra de la versión original de la clase no se ve afectado, por lo que puede estar seguro de que su cambio no rompió el código existente.

En realidad fácilmente terminas con un código inflado y un confuso lío de clases obsoletas. Si no es posible modificar el comportamiento de un componente a través de la extensión, debe proporcionar una nueva variante del componente con el comportamiento deseado y mantener la versión antigua sin cambios para la compatibilidad con versiones anteriores.

Supongamos que descubre una falla de diseño fundamental en una clase base de la cual muchas clases heredan. Decir que el error se debe a que un campo privado es del tipo incorrecto. No puedes arreglar esto anulando un miembro. Básicamente, debe anular toda la clase, lo que significa que termina extendiendo Object para proporcionar una clase base alternativa, y ahora también tiene que proporcionar alternativas a todas las subclases, lo que termina con una jerarquía de objetos duplicados, una jerarquía defectuosa , uno mejorado. Pero no puede eliminar la jerarquía defectuosa (ya que la eliminación del código es una modificación), todos los clientes futuros estarán expuestos a ambas jerarquías.

Ahora la respuesta teórica a este problema es "simplemente diseñarlo correctamente la primera vez". Si el código está perfectamente descompuesto, sin fallas ni errores, y diseñado con puntos de extensión preparados para todos los posibles cambios futuros en los requisitos, se evita el desorden. Pero en realidad todos cometen errores y nadie puede predecir el futuro a la perfección.

Tome algo como el marco .NET: todavía contiene el conjunto de clases de colección que se diseñaron antes de que se introdujeran los genéricos hace más de una década. Esto es sin duda una ventaja para la compatibilidad con versiones anteriores (puede actualizar el marco sin tener que volver a escribir nada), pero también infla el marco y presenta a los desarrolladores una gran cantidad de opciones donde muchas son simplemente obsoletas.

Al parecer, los desarrolladores de React han sentido que no valía la pena el costo en complejidad y el código de código para seguir estrictamente el principio de apertura / cierre.

La alternativa pragmática a abrir / cerrar es la depreciación controlada. En lugar de romper la compatibilidad con versiones anteriores en una única versión, los componentes antiguos se conservan durante un ciclo de publicación, pero a través de las advertencias del compilador se informa a los clientes que el enfoque anterior se eliminará en una versión posterior. Esto le da a los clientes tiempo para modificar el código. Este parece ser el enfoque de React en este caso.

(Mi interpretación del principio se basa en El principio abierto-cerrado por Robert C. Martin)

    
respondido por el JacquesB 30.04.2017 - 17:12
20

Llamaría ideal al principio abierto / cerrado. Como todos los ideales, presta poca atención a las realidades del desarrollo de software. Además, como todos los ideales, es imposible alcanzarlo en la práctica, uno simplemente se esfuerza por acercarse a ese ideal de la mejor manera posible.

El otro lado de la historia se conoce como las esposas doradas. Las esposas doradas son lo que obtienes cuando te esclavizas al principio abierto / cerrado demasiado. Las esposas doradas son lo que ocurre cuando su producto que nunca se rompe hacia atrás la compatibilidad no puede crecer porque se han cometido demasiados errores pasados.

Un ejemplo famoso de esto se encuentra en el administrador de memoria de Windows 95. Como parte de la comercialización para Windows 95, se indicó que todas las aplicaciones de Windows 3.1 funcionarían en Windows 95. Microsoft realmente adquirió licencias para miles de programas para probarlas en Windows 95. Uno de los casos problemáticos fue Sim City. Sim City en realidad tenía un error que causaba que escribiera en memoria no asignada. En Windows 3.1, sin un administrador de memoria "adecuado", este fue un pequeño paso en falso. Sin embargo, en Windows 95, el administrador de memoria detectaría esto y causaría un error de segmentación. ¿La solución? En Windows 95, si el nombre de su aplicación es simcity.exe , ¡el sistema operativo realmente relajará las restricciones del administrador de memoria para evitar el fallo de segmentación!

El problema real detrás de este ideal son los conceptos reducidos de productos y servicios. Nadie realmente hace una o la otra. Todo se alinea en algún lugar en la región gris entre los dos. Si piensa desde un enfoque orientado al producto, abrir / cerrar suena como un gran ideal. Sus productos son confiables. Sin embargo, cuando se trata de servicios, la historia cambia. Es fácil demostrar que con el principio abierto / cerrado, la cantidad de funcionalidad que su equipo debe soportar debe se aproxima asintóticamente al infinito, porque nunca puede limpiar la funcionalidad antigua. Esto significa que su equipo de desarrollo debe admitir más y más códigos cada año. Eventualmente llegas a un punto de ruptura.

La mayoría del software actual, especialmente de código abierto, sigue una versión relajada común del principio de apertura / cierre. Es muy común ver abierto / cerrado seguido servilmente para lanzamientos menores, pero abandonado para lanzamientos importantes. Por ejemplo, Python 2.7 contiene muchas "malas elecciones" de Python 2.0 y 2.1 días, pero Python 3.0 los barrió a todos. (Además, el cambio de la base de código de Windows 95 a la base de código de Windows NT cuando lanzaron Windows 2000 rompió todo tipo de cosas, pero significa que nunca tenemos que lidiar con un administrador de memoria que verifique el nombre de la aplicación para decidir el comportamiento!)

    
respondido por el Cort Ammon 30.04.2017 - 21:37
11
La respuesta de

Doc Brown es la más cercana a la precisa, las otras respuestas ilustran malentendidos del Principio Abierto Abierto.

Para articular explícitamente el malentendido, parece haber una creencia de que el OCP significa que no debes hacer cambios incompatibles hacia atrás (o incluso cualquier cambios o algo parecido). El OCP trata sobre diseñar componentes para que no necesite realizar cambios en ellos para ampliar su funcionalidad, independientemente de si esos cambios son compatibles con versiones anteriores o no. Hay muchas otras razones, además de agregar funcionalidad, por las que puede realizar cambios en un componente, ya sean compatibles con versiones anteriores (por ejemplo, refactorización u optimización) o incompatibles con versiones anteriores (por ejemplo, funcionalidad de eliminación y eliminación). El hecho de que pueda realizar estos cambios no significa que su componente haya violado el OCP (y definitivamente no significa que usted esté violando el OCP).

Realmente, no se trata en absoluto del código fuente. Una declaración más abstracta y relevante del OCP es: "un componente debe permitir la extensión sin necesidad de violar sus límites de abstracción". Iría más allá y diría que una versión más moderna es: "un componente debería hacer cumplir sus límites de abstracción pero permitir la extensión". Incluso en el artículo sobre el OCP de Bob Martin mientras él "describe" "cerrado a la modificación" como "el código fuente es inviolable", más tarde comienza a hablar sobre la encapsulación que no tiene nada que ver con modificar el código fuente y todo lo relacionado con la abstracción. límites.

Entonces, la premisa errónea en la pregunta es que el OCP es (destinado como) una guía sobre las evoluciones de una base de código. El OCP se suele consignar como "un componente debe estar abierto a las extensiones y cerrado a las modificaciones de los consumidores". Básicamente, si un consumidor de un componente desea agregar la funcionalidad al componente, debería poder extender el componente antiguo a uno nuevo con la funcionalidad adicional, pero debería no poder cambiar el componente antiguo.

El OCP no dice nada sobre el creador de un componente cambiando o eliminando . El OCP no aboga por mantener compatibilidad de errores para siempre. Usted, como creador, no está violando el OCP al cambiar o incluso eliminar un componente. Usted, o más bien los componentes que ha escrito, está violando el OCP si la única forma en que los consumidores pueden agregar funcionalidad a sus componentes es mediante la mutación, por ejemplo. por parches de mono o tener acceso al código fuente y recompilar. En muchos casos, ninguna de estas opciones es para el consumidor, lo que significa que si su componente no está "abierto para extensión" no tiene suerte. Simplemente no pueden usar su componente para sus necesidades. El OCP argumenta no colocar a los consumidores de su biblioteca en esta posición, al menos con respecto a alguna clase de "extensiones" identificable. Incluso cuando se pueden realizar modificaciones en el código fuente o incluso en la copia principal del código fuente, es mejor "simular" que no se puede modificar ya que existen muchas consecuencias negativas potenciales al hacerlo. .

Entonces, para responder a sus preguntas: No, estas no son violaciones del OCP. Ningún cambio que haga un autor puede ser una violación del OCP porque el OCP no es una proporción de los cambios. Sin embargo, los cambios pueden crear violaciones del OCP y pueden estar motivados por fallas del OCP en versiones anteriores del código base. El OCP es una propiedad de un fragmento de código en particular, no la historia evolutiva de un código base.

Para el contraste, la compatibilidad con versiones anteriores es una propiedad de un cambio de código. No tiene sentido decir que una parte del código es o no es compatible con versiones anteriores. Solo tiene sentido hablar de la compatibilidad con versiones anteriores de algunos códigos con respecto a algunos códigos más antiguos. Por lo tanto, nunca tiene sentido hablar de que el primer corte de un código sea compatible con versiones anteriores o no. El primer corte de código puede satisfacer o no satisfacer el OCP, y en general podemos determinar si algún código satisface al OCP sin hacer referencia a ninguna versión histórica del código.

En lo que respecta a su última pregunta, podría decirse que es un tema ajeno a StackExchange en general debido a que se basa principalmente en la opinión, pero por lo general es bienvenido a la tecnología y especialmente a JavaScript, donde en los últimos años se ha llamado al fenómeno que usted describe fatiga de JavaScript . (No dude en google para encontrar una variedad de otros artículos, algunos satíricos, que hablan de esto desde múltiples perspectivas.)

    
respondido por el Derek Elkins 01.05.2017 - 00:38

Lea otras preguntas en las etiquetas