Diseño de software

Entender bien el principio DRY: una abstracción incorrecta da más miedo que la duplicación de código

Al hacer revisiones de código, hay una escena con la que me encuentro especialmente a menudo.

4 min de lectura
Imagen de portada de Entender bien el principio DRY: una abstracción incorrecta da más miedo que la duplicación de código

Al hacer revisiones de código, hay una escena con la que me encuentro especialmente a menudo.

Buscas una funcionalidad para corregirla y encuentras un código casi idéntico en tres lugares. Corriges uno y los otros dos se quedan como bugs.

La protagonista de hoy es DRY, el principio que apunta directamente a este problema. Va de la mano con el principio KISS que tratamos la vez pasada.

DRY significa “Don’t Repeat Yourself”. Es el principio de no repetir el mismo conocimiento dentro de un sistema.

Andy Hunt y Dave Thomas explicaron este concepto en el libro de 1999 <The Pragmatic Programmer>, cuya definición original es bastante precisa.

Todo conocimiento debe tener una única representación inequívoca y autorizada dentro de un sistema.

La palabra clave aquí no es “código”, sino “conocimiento”. Ese es el núcleo de la conversación de hoy.


¿Por qué es un problema copiar y pegar código?

Copiar y pegar no es malo por sí mismo. El problema llega después.

Si la misma lógica existe en tres lugares, cuando cambian los requisitos tienes que encontrar y modificar los tres. Si omites uno, se convierte en un bug.

// El cálculo de los gastos de envío está repartido en tres archivos
// Cart.swift
let fee = total >= 50000 ? 0 : 3000

// Checkout.swift
let shippingFee = totalPrice >= 50000 ? 0 : 3000

// OrderSummary.swift
let delivery = price >= 50000 ? 0 : 3000

¿Y si un día llega la petición de “bajar el umbral de envío gratuito a 30.000 wones”? Hay que encontrar los tres archivos. Como los términos de búsqueda también son distintos, seguro que se escapa alguno.

// Centralizar el conocimiento en un solo lugar
// Shipping.swift
enum ShippingPolicy {
    static let freeShippingThreshold = 50000
    static let fee = 3000

    static func shippingFee(for total: Int) -> Int {
        total >= freeShippingThreshold ? 0 : fee
    }
}

Ahora, si cambia la política, solo tienes que modificar un lugar. Eso es DRY.


No se duplica el código, sino el “conocimiento”

Sin embargo, aquí mucha gente se equivoca. Interpretan DRY como “une todo el código que tenga un aspecto parecido”.

La duplicación de la que habla DRY no es la forma del código, sino el conocimiento; es decir, las reglas de negocio.

Esta distinción importa porque existe código que solo se parece por casualidad.

Situación ¿Duplicación?
Lógica de cálculo de envío en tres lugares Duplicación real (el mismo conocimiento)
La validación del registro y la validación de la participación en un evento se parecen por casualidad Duplicación aparente (conocimiento diferente)
La constante 3000 aparece por separado en los gastos de envío y en el umbral para obtener puntos Duplicación aparente (significado diferente)

La validación del registro y la de participación en eventos parecen idénticas porque ahora ambas exigen “nombre obligatorio y teléfono de 11 dígitos”, pero las dos reglas cambian por motivos distintos. Si las unes, cambiar las reglas del evento puede romper el registro.

No todo lo que se parece representa el mismo conocimiento
No todo lo que se parece representa el mismo conocimiento

Una abstracción precipitada cuesta más que la duplicación

Por eso en las comunidades de desarrolladores se escucha a menudo: “prefer duplication over the wrong abstraction”. Lo dijo Sandy Metz, un conocido desarrollador de Ruby.

Si unes a la fuerza dos fragmentos de código que solo parecen similares, ocurre lo siguiente.

  1. Crear una función común
  2. Cambian los requisitos de un lado y añades un parámetro de opción
  3. También cambia el otro lado y añades una rama if
  4. De pronto se convierte en una función de cinco parámetros que nadie entiende

Llegados a ese punto, habría sido mejor tener dos copias del código.

Las cuatro etapas del colapso de una función unificada a la fuerza
Las cuatro etapas del colapso de una función unificada a la fuerza

Por eso en la práctica se usa mucho la Regla de Tres. Si un patrón aparece dos veces, lo observas; cuando aparece por tercera vez, lo abstraes. Tras repetirlo unas tres veces, se ve si realmente representa el mismo conocimiento o si es una coincidencia.


Mi criterio para decidir

Cuando detecto duplicación, hago una pregunta.

¿Estos dos códigos cambian por la misma razón?

Si cambian por la misma razón, es duplicación real y los uno. Si cambian por razones distintas, los dejo tal cual, aunque se parezcan mucho.

Esta pregunta es suficiente antes de unirlos
Esta pregunta es suficiente antes de unirlos

Conclusión de hoy

En resumen, esto es lo esencial del principio DRY.

  • La unidad de duplicación es el conocimiento (las reglas de negocio), no la forma del código.
  • Une solo el código que cambia por la misma razón. Deja intacto el código que solo se parece por casualidad.
  • Si no estás seguro, aplica la Regla de Tres. No es tarde para abstraer en la tercera repetición.

Si KISS significa “mantén la simplicidad”, DRY significa “mantén el conocimiento en un solo lugar”. En última instancia, ambos buscan reducir el coste del cambio.

Seguro que se te ocurre alguna lógica que aparece en tres lugares cada vez que la buscas. Esa es la candidata a refactorizar hoy.