it-swarm-es.com

¿Es el acoplamiento suelto sin casos de uso un anti-patrón?

El acoplamiento flexible es, para algunos desarrolladores, el santo grial del software bien diseñado. Ciertamente es algo bueno cuando hace que el código sea más flexible frente a los cambios que es probable que ocurran en el futuro previsible, o evita la duplicación de código.

Por otro lado, los esfuerzos por acoplar componentes de manera flexible aumentan la cantidad de direccionamiento indirecto en un programa, lo que aumenta su complejidad, lo que a menudo lo hace más difícil de entender y, a menudo, lo hace menos eficiente.

¿Considera que centrarse en el acoplamiento flexible sin ningún caso de uso para el acoplamiento flexible (como evitar la duplicación de código o planificar cambios que es probable que ocurran en el futuro previsible) es un anti-patrón? ¿Puede el acoplamiento suelto caer bajo el paraguas de YAGNI?

22
dsimcha

Algo.

A veces, el acoplamiento flojo que viene sin demasiado esfuerzo está bien, incluso si no tiene requisitos específicos que exijan que se desacople algún módulo. La fruta madura, por así decirlo.

Por otro lado, la sobreingeniería para cantidades ridículas de cambio será una gran complejidad y esfuerzo innecesarios. YAGNI, como dices, lo golpea en la cabeza.

13
Fishtoaster

¿Es la práctica de programaciónX buena o mala? Claramente, la respuesta es siempre "depende".

Si está mirando su código y se pregunta qué "patrones" puede inyectar, entonces lo está haciendo mal.

Si está construyendo su software para que los objetos no relacionados no jueguen entre sí, entonces lo está haciendo bien.

Si está "diseñando" su solución para que pueda ampliarse y cambiarse infinitamente, en realidad la está haciendo más complicada.

Creo que al final del día, te quedas con la única verdad: ¿es más o menos complicado tener los objetos desacoplados? Si es menos complicado acoplarlos, entonces esa es la solución correcta. Si es menos complicado desacoplarlos, entonces esa es la solución correcta.

(Actualmente estoy trabajando en una base de código bastante pequeña que hace un trabajo simple de una manera muy complicada, y parte de lo que lo hace tan complicado es la falta de comprensión de los términos "acoplamiento" y "cohesión" por parte del original desarrolladores.)

22
dash-tom-bang

Creo que a lo que te refieres aquí es al concepto de cohesión. ¿Este código tiene un buen propósito? ¿Puedo interiorizar ese propósito y comprender el "panorama general" de lo que está sucediendo?

Podría conducir a un código difícil de seguir, no solo porque hay muchos más archivos fuente (asumiendo que son clases separadas), sino porque ninguna clase individual parece tener un propósito.

Desde una perspectiva ágil, podría sugerir que tal acoplamiento flexible sería un anti-patrón. Sin la cohesión, o incluso los casos de uso para ella, no puede escribir pruebas unitarias sensibles y no puede verificar el propósito del código. Ahora, el código ágil puede conducir a un acoplamiento flojo, por ejemplo, cuando se usa el desarrollo basado en pruebas. Pero si se crearon las pruebas correctas, en el orden correcto, es probable que haya una buena cohesión y un acoplamiento flexible. Y estás hablando solo de los casos en los que claramente no hay cohesión.

Nuevamente, desde la perspectiva ágil, no desea este nivel artificial de direccionamiento indirecto porque es un esfuerzo inútil en algo que probablemente no será necesario de todos modos. Es mucho más fácil de refactorizar cuando la necesidad es real.

En general, desea un alto acoplamiento dentro de sus módulos y un acoplamiento flojo entre ellos. Sin el acoplamiento alto, probablemente no tenga cohesión.

3
Macneil

La respuesta simple es que el acoplamiento flojo es bueno cuando se hace correctamente.

Si se sigue el principio de una función, un propósito, entonces debería ser bastante fácil seguir lo que está sucediendo. Además, el código de pareja suelto sigue como una cuestión de rutina sin ningún esfuerzo.

Reglas de diseño simples: 1. No desarrolle el conocimiento de varios elementos en un solo punto (como se señaló en todas partes, depende) a menos que esté construyendo una interfaz de fachada. 2. una función - un propósito (ese propósito puede ser multifacético, como en una fachada) 3. un módulo - un conjunto claro de funciones interrelacionadas - un propósito claro 4. si no puede simplemente realizar una prueba unitaria, entonces no tiene un propósito simple

Todos estos comentarios sobre más fáciles de refactorizar más tarde son un montón de bacalaos. Una vez que el conocimiento se integra en muchos lugares, particularmente en sistemas distribuidos, el costo de refactorización, la sincronización de implementación y casi todos los demás costos lo dejan tan fuera de consideración que, en la mayoría de los casos, el sistema termina siendo destruido por ello.

Lo triste del desarrollo de software en estos días es que el 90% de las personas desarrollan nuevos sistemas y no tienen la capacidad de comprender sistemas antiguos, y nunca están presentes cuando el sistema ha llegado a un estado de salud tan pobre debido a la refactorización continua de partes y piezas.

1
sweetfa

Para la mayoría de preguntas como esta, la respuesta es "depende". En general, si puedo hacer un diseño lógicamente acoplado libremente de manera que tenga sentido, sin una sobrecarga importante para hacerlo, lo haré. Evitar el acoplamiento innecesario en el código es, en mi opinión, un objetivo de diseño totalmente valioso.

Una vez que se llega a una situación en la que parece que los componentes deberían estar estrechamente acoplados lógicamente, buscaré un argumento convincente antes de comenzar a dividirlos.

Supongo que el principio con el que trabajo con la mayoría de estos tipos de práctica es el de la inercia. Tengo una idea de cómo me gustaría que funcionara mi código y si puedo hacerlo de esa manera sin hacer la vida más difícil, lo haré. Si hacerlo hará que el desarrollo sea más difícil pero el mantenimiento y el trabajo futuro más fáciles, intentaré adivinar si será más trabajo durante la vida útil del código y lo usaré como mi guía. De lo contrario, tendría que ser un punto de diseño deliberado que valga la pena.

1
glenatron

No importa qué tan estrechamente acoplada esté una cosa con la otra si esa otra cosa nunca cambia. En general, he encontrado más productivo a lo largo de los años concentrarme en buscar menos razones para que las cosas cambien, en buscar estabilidad, que en hacerlas más fáciles de cambiar tratando de lograr la forma más flexible de acoplamiento posible. ¡Desacoplamiento Me ha resultado muy útil, hasta el punto de que a veces prefiero la duplicación modesta de código para desacoplar paquetes. Como ejemplo básico, tuve la opción de usar mi biblioteca matemática para implementar una biblioteca de imágenes. No lo hice y simplemente dupliqué algunas funciones matemáticas básicas que eran triviales de copiar.

Ahora mi biblioteca de imágenes es completamente independiente de la biblioteca de matemáticas de una manera en la que no importa qué tipo de cambios haga en mi biblioteca de matemáticas, no afectará la biblioteca de imágenes. Eso es ante todo la estabilidad. La biblioteca de imágenes es más estable ahora, ya que tiene drásticamente menos razones para cambiar, ya que está desacoplada de cualquier otra biblioteca que pueda cambiar (además de la biblioteca estándar de C, que con suerte nunca debería cambiar). Como beneficio adicional, también es fácil de implementar cuando es solo una biblioteca independiente que no requiere extraer un montón de otras bibliotecas para construirla y usarla.

La estabilidad me ayuda mucho. Me gusta construir una colección de código bien probado que tiene cada vez menos razones para cambiar en el futuro. Eso no es una quimera; Tengo el código C que he estado usando y usando nuevamente desde finales de los 80, que no ha cambiado en absoluto desde entonces. Es cierto que se trata de cosas de bajo nivel, como código orientado a píxeles y relacionado con la geometría, mientras que muchas de mis cosas de alto nivel se volvieron obsoletas, pero es algo que todavía ayuda mucho a tener a mano. Eso casi siempre significa una biblioteca que depende de cada vez menos cosas, si es que nada externo. La confiabilidad aumenta y aumenta si su software depende cada vez más de cimientos estables que encuentran pocas o ninguna razón para cambiar. Menos piezas móviles es realmente agradable, incluso si en la práctica las piezas móviles son mucho más numerosas que las piezas estables. Todavía ayuda mucho a mi cordura saber que la totalidad de un código base no consta de partes móviles.

El acoplamiento flojo está en la misma línea, pero a menudo encuentro que el acoplamiento flojo es mucho menos estable que ningún acoplamiento. A menos que esté trabajando en un equipo con diseñadores de interfaz y clientes muy superiores que no cambian de opinión de los que yo he trabajado, incluso las interfaces puras a menudo encuentran razones para cambiar de maneras que aún causan fallas en cascada en todo el código. Esta idea de que la estabilidad se puede lograr dirigiendo las dependencias hacia lo abstracto en lugar de lo concreto solo es útil si el diseño de la interfaz es más fácil de hacer bien la primera vez que la implementación. A menudo lo encuentro al revés cuando un desarrollador podría haber creado una implementación muy buena, si no maravillosa, dados los requisitos de diseño que pensaban que deberían cumplir, solo para encontrar en el futuro que los requisitos de diseño cambian por completo. El diseño es mucho más difícil de acertar que la implementación si me preguntas con bases de código a gran escala que intentan cumplir con los requisitos en constante cambio, y en ese caso, separar los diseños abstractos de las implementaciones concretas no ayuda mucho si el diseño abstracto es lo más importante. propenso a cambiar.

Así que me gusta favorecer la estabilidad y el desacoplamiento completo para poder decir al menos con confianza: "Esta pequeña biblioteca aislada que se ha utilizado durante años y se ha asegurado mediante pruebas exhaustivas, casi no tiene probabilidades de requerir cambios sin importar lo que suceda en el caótico mundo exterior . " Me da un poco de cordura sin importar qué tipo de cambios de diseño se requieran afuera.

Acoplamiento y estabilidad, ejemplo de ECS

También me encantan los sistemas de componentes de entidad e introducen una gran cantidad de acoplamiento estrecho porque el sistema y las dependencias de los componentes acceden y manipulan los datos en bruto directamente, así:

enter image description here

Todas las dependencias aquí son bastante estrechas ya que los componentes solo exponen datos sin procesar. Las dependencias no fluyen hacia abstracciones, fluyen hacia ¡datos sin procesar lo que significa que cada sistema tiene la máxima cantidad de conocimiento posible sobre cada tipo de componente al que solicitan acceder. Los componentes no tienen ninguna funcionalidad con todos los sistemas que acceden y manipulan los datos sin procesar. Sin embargo, es muy fácil razonar sobre un sistema como este, ya que es tan plano. Si una textura sale mal, entonces con este sistema sabrá inmediatamente que solo el sistema de renderizado y pintura accede a los componentes de la textura, y probablemente pueda descartar rápidamente el sistema de renderizado, ya que solo lee texturas conceptualmente.

Mientras tanto, una alternativa débilmente acoplada podría ser esta:

enter image description here

... con todas las dependencias que fluyen hacia funciones abstractas, no datos, y cada cosa en ese diagrama exponiendo una interfaz pública y una funcionalidad propia. Aquí todas las dependencias pueden estar muy sueltas. Es posible que los objetos ni siquiera dependan directamente entre sí e interactúen entre sí a través de interfaces puras. Aún así, es muy difícil razonar sobre este sistema, especialmente si algo sale mal, dada la compleja maraña de interacciones. También habrá más interacciones (más acoplamientos, aunque más flexibles) que el ECS porque las entidades deben conocer los componentes que agregan, incluso si solo conocen la interfaz pública abstracta de cada uno.

Además, si hay cambios de diseño en algo, se producen más roturas en cascada que el ECS, y normalmente habrá más razones y tentaciones para los cambios de diseño, ya que todo está tratando de proporcionar una abstracción y una interfaz orientada a objetos agradable. Eso viene inmediatamente con la idea de que todas y cada una de las pequeñas cosas intentarán imponer restricciones y limitaciones al diseño, y esas restricciones son a menudo las que justifican los cambios de diseño. La funcionalidad está mucho más restringida y tiene que hacer muchas más suposiciones de diseño que datos sin procesar.

En la práctica, he descubierto que el tipo anterior de sistema ECS "plano" es mucho más fácil de razonar que incluso los sistemas más débilmente acoplados con una compleja telaraña de dependencias sueltas y, lo más importante para mí, encuentro tan pocas razones para que la versión ECS necesite cambiar algún componente existente, ya que los componentes de los que dependen no tienen ninguna responsabilidad, excepto proporcionar los datos apropiados necesarios para que los sistemas funcionen. Compare la dificultad de diseñar una interfaz IMotion pura y un objeto de movimiento concreto implementando esa interfaz que proporciona una funcionalidad sofisticada mientras intenta mantener invariantes sobre los datos privados frente a un componente de movimiento que solo necesita proporcionar datos brutos relevantes para resolver el problema y no se molesta con la funcionalidad.

La funcionalidad es mucho más difícil de acertar que los datos, por lo que creo que a menudo es preferible dirigir el flujo de dependencias hacia los datos. Después de todo, ¿cuántas bibliotecas de vectores/matrices hay? ¿Cuántos de ellos utilizan exactamente la misma representación de datos y solo difieren sutilmente en la funcionalidad? Innumerables y, sin embargo, todavía tenemos muchos a pesar de las representaciones de datos idénticas porque queremos diferencias sutiles en la funcionalidad. ¿Cuántas bibliotecas de imágenes hay? ¿Cuántos de ellos representan píxeles de una manera diferente y única? Casi ninguno, y nuevamente muestra que la funcionalidad es mucho más inestable y propensa a cambios de diseño que los datos en muchos escenarios. Por supuesto, en algún momento necesitamos funcionalidad, pero puede diseñar sistemas donde la mayor parte de las dependencias fluyan hacia los datos y no hacia las abstracciones o la funcionalidad en general. Eso sería priorizar la estabilidad por encima del acoplamiento.

Las funciones más estables que he escrito (del tipo que he estado usando y reutilizando desde finales de los 80 sin tener que cambiarlas en absoluto) eran todas las que se basaban en datos sin procesar, como una función de geometría que solo aceptaba una matriz de flotantes y enteros, no los que dependen de un objeto Mesh complejo o interfaz iMesh, o multiplicación de vector/matriz que solo dependía de float[] o double[], no uno que dependiera de FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse.

0
user204677