¿El acoplamiento suelto sin casos de uso es un antipatrón?

22

El acoplamiento flojo 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 puedan ocurrir en el futuro previsible, o evita la duplicación de código.

Por otro lado, los esfuerzos para acoplar componentes libremente aumentan la cantidad de indirección en un programa, lo que aumenta su complejidad, lo que a menudo hace que sea más difícil de entender y, a menudo, lo hace menos eficiente.

¿Considera que un enfoque en el acoplamiento suelto sin ningún caso de uso para el acoplamiento suelto (como evitar la duplicación de códigos o planificar cambios que puedan ocurrir en el futuro previsible) sea un antipatrón? ¿Puede el acoplamiento suelto caer bajo el paraguas de YAGNI?

dsimcha
fuente
1
La mayoría de los beneficios tienen un costo. Úselo si el beneficio supera el costo.
LennyProgrammers
44
olvidó la otra cara de la moneda de acoplamiento suelta, y eso es high cohesionuno sin el otro es un desperdicio de esfuerzo y una falta fundamental de comprensión de ambas ilustraciones.

Respuestas:

13

Algo.

A veces, el acoplamiento suelto 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 baja, por así decirlo.

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

Fishtoaster
fuente
22

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

Si está mirando su código, preguntándose 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 se pueda extender y cambiar infinitamente, entonces en realidad la está haciendo más complicada.

Creo que al final del día, te queda la única verdad: ¿es más o menos complicado desacoplar los objetos? 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.)

dash-tom-bang
fuente
44
Lo hice el otro día: pensé que necesitaría alguna indirecta entre dos clases "por si acaso". Luego me lastimé la cabeza tratando de depurar algo, eliminé la indirección y fui mucho más feliz.
Frank Shearar
3

Creo que lo que entiendes aquí es el concepto de cohesión . ¿Este código tiene un buen propósito? ¿Puedo internalizar 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 (suponiendo que se trata de clases separadas), sino porque ninguna clase parece tener un propósito.

Desde una perspectiva ágil, podría sugerir que un acoplamiento tan flojo sería un antipatrón. Sin la cohesión, o incluso los casos de uso, no puede escribir pruebas unitarias razonables 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 utiliza el desarrollo basado en pruebas. Pero si se crearon las pruebas correctas, en el orden correcto, entonces es probable que haya una buena cohesión y un acoplamiento flojo. Y solo habla de los casos en los que claramente no existe cohesión.

Nuevamente desde la perspectiva ágil, no desea este nivel artificial de indirección porque es un esfuerzo desperdiciado en algo que probablemente no será necesario de todos modos. Es mucho más fácil 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.

Macneil
fuente
1

Para la mayoría de las 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é. En mi opinión, evitar un acoplamiento innecesario en el código es un objetivo de diseño totalmente valioso.

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

Creo 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 funcione 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 sean 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, debería ser un punto de diseño deliberado con el que valga la pena ir.

glenatron
fuente
1

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 suelta sigue de forma natural sin ningún esfuerzo.

Reglas de diseño simples: 1. No desarrolle el conocimiento de múltiples elementos en un solo punto (como se señala 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 probarlo en la unidad, entonces no tiene un simple propósito

Todos estos comentarios sobre más fácil de refactorizar más tarde son una carga 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 sobresalen tanto que, en la mayoría de los casos, el sistema termina siendo destruido debido a ello.

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

sweetfa
fuente
0

No importa cuán estrechamente acoplado esté una cosa con otra si esa otra cosa nunca cambia. En general, me ha resultado más productivo a lo largo de los años centrarme en buscar menos razones para que las cosas cambien, buscar estabilidad, que hacerlas más fáciles de cambiar al tratar de lograr la forma más flexible de acoplamiento posible. Desacoplamiento que he encontrado muy útil, hasta el punto de que a veces prefiero una modesta duplicación 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 solo dupliqué algunas funciones matemáticas básicas que eran triviales de copiar.

Ahora mi biblioteca de imágenes es completamente independiente de la biblioteca matemática de manera que, sin importar qué tipo de cambios realice en mi biblioteca matemática, 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 C que, con suerte, nunca debería cambiar). Como beneficio adicional, también es fácil de implementar cuando se trata solo de una biblioteca independiente que no requiere extraer un montón de otras bibliotecas para construirla y usarla.

La estabilidad es muy útil para mí. Me gusta construir una colección de código bien probado que tenga cada vez menos razones para cambiar en el futuro. Eso no es un sueño imposible; Tengo el código C que he estado usando y volviendo a usar desde finales de los 80, que no ha cambiado en absoluto desde entonces. Es cierto que es algo de bajo nivel como el código orientado a píxeles y relacionado con la geometría, mientras que muchas de mis cosas de nivel superior se volvieron obsoletas, pero es algo que todavía ayuda mucho a tener. Eso casi siempre significa una biblioteca que se basa en cada vez menos cosas, si no hay nada externo. La fiabilidad aumenta si su software depende cada vez más de bases estables que encuentran pocas o ninguna razón para cambiar. Menos partes móviles es realmente agradable, incluso si en la práctica las partes móviles son mucho más numerosas que las partes estables.

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 y clientes de interfaces muy superiores que no cambian de opinión con respecto a lo que yo he trabajado, incluso las interfaces puras a menudo encuentran razones para cambiar de maneras que todavía causan roturas en cascada en todo el código. Esta idea de que se puede lograr la estabilidad 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 corregir la primera vez que la implementación. A menudo me parece invertido donde un desarrollador podría haber creado una implementación muy buena, si no maravillosa, dados los requisitos de diseño que pensaron que deberían cumplir, solo para descubrir en el futuro que los requisitos de diseño cambian por completo.

Por lo tanto, 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 asegurada mediante pruebas exhaustivas casi no tiene probabilidad de requerir cambios sin importar lo que ocurra en el caótico mundo exterior. ". Me da un poco de cordura sin importar qué tipo de cambios de diseño se requieran en el exterior.

Acoplamiento y estabilidad, ejemplo ECS

También me encantan los sistemas de componentes de entidad e introducen una gran cantidad de acoplamiento estrecho porque el sistema a las dependencias de los componentes accede y manipula los datos sin procesar directamente, así:

ingrese la descripción de la imagen aquí

Todas las dependencias aquí son bastante estrictas ya que los componentes solo exponen datos sin procesar. Las dependencias no están fluyendo hacia abstracciones, están fluyendo 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 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 muy plano. Si una textura sale mal, entonces sabe con este sistema de inmediato que solo el sistema de renderizado y pintura accede a los componentes de 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:

ingrese la descripción de la imagen aquí

... con todas las dependencias que fluyen hacia funciones abstractas, no datos, y cada cosa en ese diagrama expone una interfaz pública y funcionalidad propia. Aquí todas las dependencias pueden ser muy flojas. 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 acoplamiento, aunque más flexibles) que el ECS porque las entidades tienen que 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, obtendrá más roturas en cascada que el ECS, y generalmente habrá más razones y tentaciones para los cambios de diseño ya que cada cosa está tratando de proporcionar una interfaz y abstracción 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 muchos más supuestos de diseño que los datos sin procesar.

En la práctica, he descubierto que el tipo de sistema ECS "plano" anterior es mucho más fácil de razonar que incluso los sistemas más flojos con una telaraña compleja de dependencias sueltas y, lo más importante para mí, encuentro muy pocas razones para que la versión ECS necesite cambiar los componentes existentes, ya que los componentes dependían de no tener responsabilidad, excepto proporcionar los datos apropiados necesarios para que los sistemas funcionen. Compare la dificultad de diseñar una IMotioninterfaz pura y un objeto de movimiento concreto que implemente esa interfaz que proporcione una funcionalidad sofisticada mientras intenta mantener invariantes sobre datos privados versus un componente de movimiento que solo necesita proporcionar datos sin procesar relevantes para resolver el problema y no se molesta con funcionalidad

La funcionalidad es mucho más difícil de corregir 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 usan exactamente la misma representación de datos y solo difieren sutilmente en la funcionalidad? Incontables, y aún así tenemos muchas, a pesar de las representaciones de datos idénticas, porque queremos diferencias sutiles en la funcionalidad. ¿Cuántas bibliotecas de imágenes hay por ahí? ¿Cuántos de ellos representan píxeles de una manera diferente y única? Casi ninguno, y una vez más 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 abstracciones o funcionalidad en general. Tal sería priorizar la estabilidad sobre el 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) fueron todas las que se basaron en datos sin procesar, como una función de geometría que acaba de aceptar una matriz de flotantes y enteros, no los que dependen de un Meshobjeto o IMeshinterfaz complejo , o la multiplicación de vectores / matrices que solo dependía float[]o double[]no, uno que no dependía FancyMatrixObjectWhichWillRequireDesignChangesNextYearAndDeprecateWhatWeUse.


fuente