design-patterns - repetir - principios de diseño de software dry
¿La violación del principio DRY siempre es mala? (8)
He estado discutiendo sobre el principio DRY ( Do not Repeat Yourself ) también conocido como DIE ( Duplication Is Evil ) y hay votos, que cualquier repetición simple de código es siempre un mal. Me gustaría escuchar tu opinión sobre los siguientes puntos:
- Futuro incierto Digamos que tenemos el mismo código en dos lugares. La clave es que estos dos lugares tienen solo una connotación incidental. Existe la posibilidad de que varíen en el futuro porque su contexto y semántica son diferentes. Hacer una abstracción de estos lugares no es barato y si uno de estos lugares cambia, desenvolvernos de la abstracción será aún más costoso.
- Legibilidad Hay un cálculo complejo que involucra varias variables o pasos. En otro lugar del código hay otro, que tiene algunas partes idénticas. El problema es que si sacamos las partes comunes, la legibilidad del cálculo disminuirá y la abstracción creada será muy difícil darle un nombre descriptivo. Peor aún, si alguna parte del algoritmo cambiará en el futuro como en el punto 1.
¿Los casos anteriores son una buena razón para abandonar el proceso de abstracción y simplemente dejar el código duplicado a favor del riesgo de cambios futuros o simplemente de legibilidad?
Esas son razones completamente válidas para violar DRY. Debo agregar un tercero: rendimiento. En raras ocasiones es un gran problema, pero puede marcar la diferencia, y la abstracción puede arriesgar ralentizar las cosas.
De hecho, añadiré un cuarto: perder el tiempo y posiblemente introducir nuevos errores cambiando dos (o más) partes de una base de código que ya podría estar funcionando bien. ¿Vale la pena el costo de averiguar cómo abstraer estas cosas si no es necesario y probablemente no ahorrará mucho ni mucho tiempo en el futuro?
Por lo general, el código duplicado no es ideal, pero sin duda hay razones de peso para permitirlo, probablemente con más motivos que los que hemos sugerido el OP y yo.
La ingeniería se trata de intercambios, por lo que no hay un consejo definitivo o patrón de diseño válido para cada problema. Algunas decisiones son más difíciles de respaldar que otras (la repetición del código es una de ellas), pero si las ventajas de repetir el código superan sus inconvenientes en su situación, consígalo.
No hay absolutos, siempre va a ser un juicio entre el menor de los dos males. Por lo general, DRY gana y tienes que tener cuidado con las pendientes resbaladizas cuando empiezas a violarlo, pero tu razonamiento parece estar bien para mí.
No, la violación de DRY no siempre es mala. Especialmente, si no logras encontrar un buen nombre para una abstracción del código duplicado, es decir, un nombre que se adapte a ambos contextos, puede ser que sean cosas diferentes después de todo, y deberían dejarse duplicados.
En mi experiencia, este tipo de coincidencia tiende a ser raro, y cuanto mayor es el código duplicado, más probable es que describa un solo concepto.
También creo que abstraerse de la composición es casi siempre una mejor idea al respecto que abstraerse a la herencia, lo que puede llevarlo fácilmente a ecuaciones falsas y violaciones de LSP e ISP .
Para una excelente respuesta a esta pregunta, consulte "El programador pragmático" por Thomas, Hunt (Fue Dave Thomas quien se le ocurrió el término "seco" en primer lugar)
En resumen, no hay una respuesta fácil, casi siempre es mejor permanecer seco, pero si mejora la legibilidad, entonces debes usar tu mejor juicio. ¡Es tu decisión!
Tratemos de entender por qué DRY es importante, y luego podemos entender dónde es razonable romper la regla:
DRY se debe utilizar para evitar la situación en la que dos piezas de código realizan conceptualmente parte del mismo trabajo, por lo que cada vez que cambie el código en un lugar, deberá cambiar el código en el otro lugar. Si la misma lógica está en dos lugares separados, entonces siempre debe recordar cambiar la lógica en ambos lugares, lo que puede ser bastante propenso a errores. Esto puede aplicarse a cualquier escala. Puede ser una aplicación completa que se está duplicando o puede ser un único valor constante. También puede que no haya ningún código repetido, puede ser simplemente un principio repetido. Tienes que preguntar "Si tuviera que hacer un cambio en un lugar, ¿necesitaría necesariamente hacer un cambio equivalente en otro lugar?". Si la respuesta es "sí", entonces el código está violando DRY.
Imagine que tiene una línea como esta en su programa:
cost = price + price*0.10 // account for sales tax
y en otro lugar de tu programa, tienes una línea similar:
x = base_price*1.1; // account for sales tax
Si el impuesto a las ventas cambia, tendrá que cambiar ambas líneas. Casi no hay código repetido aquí, pero el hecho de que si realiza un cambio en un lugar requiere un cambio en otro lugar es lo que hace que el código no se SECO. Además, puede ser muy difícil darse cuenta de que tiene que hacer el cambio en dos lugares. Tal vez las pruebas unitarias lo capten, pero quizás no, por lo que es importante deshacerse de la duplicación. Tal vez podría factorizar el valor del impuesto a las ventas en una constante separada que se puede usar en varios lugares:
cost = price + price*sales_tax;
x = base_price*(1.0+sales_tax);
o tal vez crear una función para abstraerlo aún más:
cost = costWithTax(price);
x = costWithTax(base_price);
De cualquier manera, es muy probable que valga la pena.
Alternativamente, puede tener un código que se ve muy similar pero que no viola DRY:
x = base_price * 1.1; // add 10% markup for premium service
Si tuviera que cambiar la forma en que se calcula el impuesto a las ventas, no le conviene cambiar esa línea de código, por lo que no está repitiendo ninguna lógica.
También hay casos donde está bien tener que hacer el mismo cambio en varios lugares. Por ejemplo, tal vez tengas un código como este:
a0 = f(0);
a1 = f(1);
Este código no es SECO de varias maneras. Por ejemplo, si cambiaras el nombre de la función f
, tendrías que cambiar dos lugares. Quizás puedas hacer que el código sea más SECO creando un pequeño bucle y convirtiéndolo en una matriz. Sin embargo, esta duplicación particular no es un gran problema. Primero, los dos cambios están muy juntos, por lo que es poco probable cambiar accidentalmente uno sin cambiar el otro. En segundo lugar, si está en un lenguaje compilado, entonces el compilador probablemente detectará el problema de todos modos. Si no estás en un lenguaje compilado, con suerte tus pruebas unitarias lo atraparán.
Hay muchas buenas razones para hacer que su código sea SECO, pero hay muchas buenas razones para no hacerlo.
Yo creo que sí Aunque, como regla general, DRY es ideal, a veces es mejor simplemente repetirse. Me encuentro haciendo caso omiso de DRY a menudo cuando en la fase de prueba previa al desarrollo. Nunca se sabe cuándo tendrá que hacer pequeños cambios en una función, que no quiere hacer en otra. Por supuesto, trato de observar siempre DRY en aplicaciones "terminadas" (aplicaciones que se completan, y NO necesitarán ser modificadas), pero son pocas y distantes. Al final, depende de las necesidades de futuros de las aplicaciones. Hice las aplicaciones que deseaba que estuvieran SECAS, y le di gracias a Dios por no haberlo observado en otros.
Sí, ciertas duplicaciones de código son notoriamente difíciles de descartar sin hacer que la lectura sea significativamente peor. En tales situaciones dejo un TODO
en los comentarios como un recordatorio de que hay algo de duplicación, pero al momento de escribir parecía mejor dejarlo así.
Usualmente lo que sucede es lo que escribes en tu primer punto, las duplicaciones divergen y ya no son duplicaciones. También sucede que la duplicación es un signo de un problema de diseño, pero solo queda claro más adelante.
Larga historia corta: trate de evitar la duplicación; si la duplicación es notoriamente difícil de descartar y al momento de escribir es inocua, simplemente deje un comentario como recordatorio.
Ver también 97 cosas que todo programador debería saber :
pag. 14. Cuidado con la parte de Udi Dahan
El hecho de que dos partes del sistema completamente diferentes realizaran alguna lógica de la misma manera significaba menos de lo que pensaba. Hasta que saqué esas bibliotecas de código compartido, estas partes no dependían entre sí. Cada uno podría evolucionar de forma independiente. Cada uno podría cambiar su lógica para adaptarse a las necesidades del cambiante entorno comercial del sistema. Esas cuatro líneas de código similar fueron accidentales, una anomalía temporal, una coincidencia.
En ese caso, creó la dependencia entre dos partes del sistema que se mantuvieron mejor independientes. La solución fue esencialmente duplicación.