¿Es posible aplicar DRY sin aumentar el acoplamiento?

14

Supongamos que tenemos un módulo de software A que implementa una función F. Otro módulo B implementa la misma función que F '.

Hay varias formas de deshacerse del código duplicado:

  1. Deje que A use F 'de B.
  2. Deje que B use F de A.
  3. Coloque F en su propio módulo C y deje que A y B lo usen.

Todas estas opciones generan dependencias adicionales entre módulos. Aplican el principio DRY a costa de aumentar el acoplamiento.

Por lo que puedo ver, el acoplamiento siempre aumenta o, al arrendar, se mueve a un nivel superior al aplicar DRY. Parece haber un conflicto entre dos de los principios más básicos del diseño de software.

(En realidad, no me sorprende que haya conflictos como ese. Esto es probablemente lo que hace que un buen diseño de software sea tan difícil. Me parece sorprendente que estos conflictos normalmente no se aborden en los textos introductorios).

Editar (para aclarar): supongo que la igualdad de F y F 'no es solo una coincidencia. Si F tendrá que modificarse, F 'probablemente tendrá que modificarse de la misma manera.

Frank Puffer
fuente
2
... Creo que DRY puede ser una estrategia muy útil, pero esta pregunta ilustra una ineficiencia con DRY. Algunos (por ejemplo, entusiastas de OOP) podrían argumentar que debería copiar / pegar F en B solo por mantener la autonomía conceptual de A y B, pero no me he encontrado con un escenario en el que lo haría. Creo que copiar / pegar código es la peor opción, no puedo soportar esa sensación de "pérdida de memoria a corto plazo" en la que estaba tan seguro de que ya escribí un método / función para hacer algo; corregir un error en una función y olvidarse de actualizar otra puede ser otro problema importante.
jrh
3
Hay muchos de estos principios OO que se contradicen entre sí. En la mayoría de los casos, debe encontrar una compensación razonable. Pero en mi humilde opinión, el principio DRY es el más valioso. Como escribió @jrh: tener el mismo comportamiento implementado en varios lugares es una pesadilla de mantenimiento que debe evitarse a toda costa. Descubrir que olvidó actualizar una de las copias redundantes en producción puede destruir su negocio.
Timothy Truckle
2
@TimothyTruckle: No deberíamos llamarlos principios OO porque también se aplican a otros paradigmas de programación. Y sí, DRY es valioso pero también peligroso si se exagera. No solo porque tiende a crear dependencias y, por lo tanto, complejidad. También a menudo se aplica a duplicados causados ​​por coincidencia que tienen diferentes razones para cambiar.
Frank Puffer
1
... también a veces, cuando me encuentro con este escenario, he podido dividir F en partes que pueden usarse para lo que A necesita y lo que B necesita.
jrh
1
El acoplamiento no es inherentemente algo malo, y a menudo es necesario para disminuir los errores y aumentar la productividad. Si utilizara una función parseInt de la biblioteca estándar de su idioma dentro de su función, estaría acoplando su función a la biblioteca estándar. No he visto un programa que no haga esto en muchos años. La clave es no crear acoplamientos innecesarios. Muy a menudo, se usa una interfaz para evitar / eliminar dicho acoplamiento. Por ejemplo, mi función puede aceptar una implementación de parseInt como argumento. Sin embargo, esto no siempre es necesario, ni es siempre sabio.
Joshua Jones

Respuestas:

14

Todas estas opciones generan dependencias adicionales entre módulos. Aplican el principio DRY a costa de aumentar el acoplamiento.

¿Por qué sí lo hacen? Pero disminuyen el acoplamiento entre líneas. Lo que obtienes es el poder de cambiar el acoplamiento. El acoplamiento viene en muchas formas. La extracción de código aumenta la indirección y la abstracción. Aumentar eso puede ser bueno o malo. La cosa número uno que decide cuál obtienes es el nombre que usas para ello. Si mirar el nombre me sorprende cuando miro adentro, entonces no le has hecho ningún favor a nadie.

Además, no siga DRY en el vacío. Si elimina la duplicación, asume la responsabilidad de predecir que estos dos usos de ese código cambiarán juntos. Si es probable que cambien de forma independiente, ha causado confusión y trabajo extra por poco beneficio. Pero un nombre realmente bueno puede hacerlo más agradable al paladar. Si todo lo que puede pensar es un mal nombre, por favor, deténgase ahora.

El acoplamiento siempre existirá a menos que su sistema esté tan aislado que nadie sepa si funciona. Así que refactorizar el acoplamiento es un juego de elegir tu veneno. Seguir a DRY puede ser rentable al minimizar el acoplamiento creado al expresar la misma decisión de diseño una y otra vez en muchos lugares hasta que sea muy difícil cambiarlo. Pero DRY puede hacer que sea imposible entender su código. La mejor manera de salvar esa situación es encontrar un nombre realmente bueno. Si no puedes pensar en un buen nombre, espero que seas hábil para evitar nombres sin sentido

naranja confitada
fuente
Solo para asegurarme de que entiendo su punto correctamente, permítanme decirlo un poco diferente: si asigna un buen nombre al código extraído, el código extraído ya no es relevante para comprender el software porque el nombre lo dice todo (idealmente). El acoplamiento todavía existe a nivel técnico pero no a nivel cognitivo. Por lo tanto, es relativamente inofensivo, ¿verdad?
Frank Puffer
1
No importa
Basilevs
@FrankPuffer mejor?
candied_orange
3

Hay formas de romper dependencias explícitas. Una popular es inyectar dependencias en tiempo de ejecución. De esta manera, obtiene SECO, elimina el acoplamiento a costa de la seguridad estática. Es tan popular hoy en día, que las personas ni siquiera entienden, eso es una compensación. Por ejemplo, los contenedores de aplicaciones proporcionan rutinariamente una gestión de dependencias que complica enormemente el software al ocultar la complejidad. Incluso la simple inyección de constructor antiguo no garantiza algunos contratos debido a la falta de un sistema de tipos.

Para responder al título, sí, es posible, pero prepárese para las consecuencias del despacho de tiempo de ejecución.

  • Defina la interfaz F A en A, proporcionando la funcionalidad de F
  • Definir interfaz F B en B
  • Pon F en C
  • Cree el módulo D para administrar todas las dependencias (depende de A, B y C)
  • Adaptar F a F A y F B en D
  • Inyectar (pasar) envoltorios a A y B

De esta manera, el único tipo de dependencias que tendría es D, dependiendo de cada módulo.

O registre C en el contenedor de la aplicación con inyección de dependencia incorporada y disfrute de fortunas de autoconexión de bucles y puntos muertos de tiempo de ejecución de crecimiento lento.

Basilevs
fuente
1
+1, pero con la advertencia explícita de que esto suele ser una mala compensación. Esa seguridad estática está ahí para su protección, y eludirla con un montón de acción espeluznante a distancia es solo pedir errores difíciles de rastrear más adelante cuando la complejidad de su proyecto crece un poco ...
Mason Wheeler
1
¿Se puede decir realmente que DI rompe la dependencia? Incluso formalmente, necesita una interfaz con la firma de F para implementarlo. Pero aún así, si el módulo A usa rwally F desde C, depende de él, independientemente de que C se inyecte en tiempo de ejecución o esté directamente vinculado. DI no interrumpe la dependencia, el error solo aplaza la falla si no se proporciona la dependencia
max630
@ max630 reemplaza la dependencia de implementación por una contractual, que es más débil.
Basilevs
@ max630 Tienes razón. No se puede decir que DI rompa una dependencia. DI es, de hecho, un método para introducir dependencias, y es realmente ortogonal a la pregunta que se hace con respecto al acoplamiento. Un cambio en F (o la interfaz encapsulándolo) serían todavía requerir un cambio en ambos A y B.
rey de lado de deslizamiento
1

No estoy seguro de que una respuesta sin más contexto tenga sentido.

¿ AYa depende Bo viceversa? - en cuyo caso podríamos tener una opción obvia de hogar para F.

¿ Comparte Ay Bya comparte dependencias comunes que podrían ser un buen hogar F?

¿Qué tan grande / complejo es F? ¿De qué más Fdepende?

¿Son módulos Ay se Butilizan en el mismo proyecto?

¿ AY Bterminará compartiendo alguna dependencia común de todos modos?

¿Qué sistema de lenguaje / módulo se está utilizando? Por ejemplo, si está escribiendo en C / C ++ con el sistema de módulos como COM, lo que causa dolor al código fuente, requiere herramientas alternativas, tiene implicaciones en la depuración y tiene implicaciones de rendimiento (para invocaciones entre módulos), podría toma una pausa seria.

Por otro lado, si está hablando de Java o C # DLL que se combinan sin problemas en un solo entorno de ejecución, ese es otro asunto.


Una función es una abstracción y admite DRY.

Sin embargo, las buenas abstracciones deben completarse; las abstracciones incompletas pueden muy bien hacer que el cliente consumidor (programador) compense el déficit utilizando el conocimiento de la implementación subyacente: esto da como resultado un acoplamiento más estricto que si la abstracción se ofreciera como más completa.

Por lo tanto, argumentaría que debería buscar crear una mejor abstracción Ay Bdepender de ella, que simplemente mover una sola función a un nuevo módulo C

Estaría buscando un conjunto de funciones para generar una nueva abstracción, es decir, podría esperar hasta que la base del código esté más avanzada para identificar una refactorización de abstracción más completa / completa que hacer en lugar de una basada en un solo código de función tell.

Erik Eidt
fuente
2
¿No es peligroso juntar abstracciones y gráfico de dependencia?
Basilevs
Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.Esto supone que A siempre dependerá de B (o viceversa), lo cual es una suposición muy peligrosa. El hecho de que OP vea a F como parte inherente de A (o B) sugiere que F existe sin ser inherente a ninguna de las bibliotecas. Si F pertenece a una biblioteca (por ejemplo, un método de extensión DbContext (F) y una biblioteca de contenedor Entity Framework (A o B)), entonces la pregunta de OP sería discutible.
Flater
0

Las respuestas aquí que se centran en todas las formas en que puede "minimizar" este problema le están perjudicando. Y las "soluciones" que simplemente ofrecen diferentes formas de crear acoplamiento no son realmente soluciones en absoluto.

La verdad es que no entienden el problema que usted ha creado. El problema con su ejemplo no tiene nada que ver con DRY, más bien (más ampliamente) con el diseño de la aplicación.

Pregúntese por qué los módulos A y B están separados si ambos dependen de la misma función F? Por supuesto, tendrá problemas con la gestión de dependencias / abstracción / acoplamiento / nombre-si se compromete con un diseño deficiente.

El modelado adecuado de la aplicación se realiza de acuerdo con el comportamiento. Como tal, las piezas de A y B que dependen de F deben extraerse en su propio módulo independiente. Si esto no es posible, entonces A y B deben combinarse. En cualquier caso, A y B ya no son útiles para el sistema y deberían dejar de existir.

DRY es un principio que se puede usar para exponer un diseño deficiente, no para causarlo. Si no puede lograr DRY ( cuando realmente se aplica, teniendo en cuenta su edición) debido a la estructura de su aplicación, es una clara señal de que la estructura se ha convertido en una responsabilidad. Es por eso que la "refactorización continua" también es un principio a seguir.

De otros principios de diseño (sólido, seco, etc) por ahí son el ABC de todo usado para hacer el cambio (incluyendo refactorización) una aplicación más indolora. Concéntrese en eso , y todos los otros problemas comienzan a desaparecer.

tobogán lateral
fuente
¿Sugiere tener exactamente un módulo en cada aplicación?
Basilevs
@Basilevs Absolutamente no (a menos que esté garantizado). Sugiero tener tantos módulos completamente desacoplados como sea posible. Después de todo, ese es el propósito completo de un módulo.
King-side-slide
Bueno, la pregunta implica que los módulos A y B contienen funciones no relacionadas y que ya se extraen en consecuencia, ¿por qué y cómo deberían dejar de existir? ¿Cuál es su solución al problema como se indica?
Basilevs
@Basilevs La pregunta implica que A y B no se han modelado correctamente. Es esta deficiencia inherente la que está causando el problema en primer lugar. Ciertamente, el simple hecho de que hacen existir no es evidencia de que deben existir. Ese es el punto que estoy haciendo arriba. Claramente, es necesario un diseño alternativo para evitar romper el SECO. Es importante comprender que el propósito de todos estos "principios de diseño" es hacer que una aplicación sea más fácil de cambiar.
King-side-slide
¿Fueron muchos otros métodos, con dependencias completamente diferentes modeladas mal y tienen que rehacerse, solo por este único no accidental? ¿O supone que los módulos A y B contienen un único método cada uno?
Basilevs
0

Todas estas opciones generan dependencias adicionales entre módulos. Aplican el principio DRY a costa de aumentar el acoplamiento.

Tengo una opinión diferente, al menos para la tercera opción:

De tu descripción:

  • A necesita F
  • B necesita F
  • Ni A ni B se necesitan mutuamente.

Poner F en un módulo C no aumenta el acoplamiento ya que tanto A como B ya necesitan la función de C.

Mouviciel
fuente