Empiezas con una promoción del 10 % y, un rato después, estás diseñando un motor configurable con prioridades, extensiones y soporte para campañas que todavía no existen. El descuento sigue sin funcionar, pero el futuro está preparadísimo.

KISS, DRY y YAGNI ayudan a revisar decisiones como esta. Son principios sencillos de enunciar y bastante interesantes cuando los llevamos a un caso real.

KISS: cuánto cuesta entender la solución

KISS, habitualmente desarrollado como Keep It Simple, Stupid, invita a mantener las soluciones simples. La parte interesante está en decidir qué entendemos por simple.

Una función de una línea puede esconder una expresión imposible de leer. Una solución dividida en veinte métodos puede obligarnos a saltar entre archivos para seguir un cálculo elemental. Conviene mirar cuánto cuesta entender el recorrido completo.

Nuestra tienda necesita aplicar un porcentaje a una compra. Podemos empezar con una operación que reciba un importe y un porcentaje, valide sus condiciones y aplique una política de redondeo clara. Un motor de reglas podría tener sentido si aparecen combinaciones y variaciones que lo justifiquen. Hoy necesita aportar algo que compense su coste.

La simplicidad depende del problema y del equipo. Una construcción muy ingeniosa que solo entiende quien la escribió puede resultar cara de mantener. Unos nombres claros y un flujo previsible suelen ayudar más de lo que parece.

Para aplicar KISS: recorre la solución como si acabaras de llegar al proyecto. ¿Qué necesitas aprender para hacer un cambio pequeño? ¿Qué piezas podrías retirar sin perder comportamiento necesario?

Un cálculo que se puede seguir

Para una promoción que solo admite porcentajes enteros, podríamos empezar con esta clase completa:

import java.math.BigDecimal;
import java.math.RoundingMode;

final class PercentageDiscounts {
    private static final int FULL_PERCENTAGE = 100;

    private PercentageDiscounts() {
    }

    static long applyTo(long amountInCents, int percentage) {
        if (amountInCents < 0) {
            throw new IllegalArgumentException("Amount must be non-negative.");
        }
        if (percentage < 0 || percentage > FULL_PERCENTAGE) {
            throw new IllegalArgumentException("Percentage must be between 0 and 100.");
        }

        long discountInCents = BigDecimal.valueOf(amountInCents)
                .multiply(BigDecimal.valueOf(percentage))
                .divide(BigDecimal.valueOf(FULL_PERCENTAGE), 0, RoundingMode.HALF_UP)
                .longValueExact();

        return amountInCents - discountInCents;
    }
}

Una llamada a PercentageDiscounts.applyTo(10_000, 10) devuelve 9000 céntimos: 90 euros. La unidad aparece en el nombre del parámetro y el redondeo se aplica al descuento antes de restarlo. Una solución monetaria completa necesitaría definir también moneda y reglas de negocio.

El resultado es el importe que queda por pagar. Por ejemplo, sobre 105 céntimos, el 10 % son 10,5 céntimos: con HALF_UP el descuento se redondea a 11 y quedan 94. Redondear directamente el importe final daría 95 en este caso. Elegir dónde redondeamos forma parte de la regla; conviene dejarlo escrito y comprobarlo. Un 0 % conserva el importe y un 100 % lo deja en cero.

La función resuelve una necesidad pequeña y permite comprobar casos concretos. Si llegan porcentajes fraccionarios o nuevas reglas fiscales, revisaremos esas necesidades con sus ejemplos delante.

DRY: dónde vive cada regla

DRY, Don't Repeat Yourself, propone una representación única y autorizada de cada conocimiento dentro del sistema.

Si la política de descuentos aparece en el carrito y en la factura, una modificación puede dejar ambas versiones en desacuerdo. Tenemos dos sitios que deben recordar exactamente la misma regla.

Compartir ese cálculo permite que sus consumidores utilicen una referencia común. Antes de extraerlo, merece la pena comprobar que representan el mismo conocimiento.

Dos fragmentos parecidos pueden tener futuros distintos

Imagina un descuento del 10 % para empleados y otro del 10 % para una campaña. Hoy la operación matemática coincide. Mañana la campaña puede tener un límite de importe y el descuento de empleados puede cambiar según su antigüedad.

Si los unimos en una única política, pronto podríamos acabar con una llamada como esta. Es un contraejemplo ilustrativo: las flags obligan a conocer combinaciones que la firma del método apenas explica.

calculateDiscount(purchase, isEmployee, isCampaign, hasLimit, specialMode);

La función compartida empieza a reunir casos que tienen razones diferentes para cambiar. Podemos compartir una operación matemática sin convertir ambas promociones en una sola política, siempre que coincidan también sus unidades y su criterio de redondeo.

Las políticas de empleados y campaña conservan condiciones diferentes y utilizan una misma operación de descuento porcentual.

Las flechas significan «utiliza». Cada política decide si corresponde aplicar su descuento y con qué porcentaje; ambas pueden utilizar applyTo() para la operación matemática. Si mañana cambia la elegibilidad de empleados, la campaña puede seguir igual. Antes de compartir una función conviene identificar precisamente ese límite.

Para aplicar DRY: pregunta qué conocimiento se repite y si un cambio debería afectar a todos sus usos. El parecido visual del código es una pista; hace falta entender su significado.

YAGNI: cuánto futuro estamos construyendo

YAGNI, You Aren't Gonna Need It, cuestiona implementar capacidades que suponemos necesarias en el futuro.

Puede que un día nuestra tienda necesite campañas combinables, prioridades configurables y distintos proveedores de promociones. Cada posibilidad añade código que habrá que entender, probar y mantener desde ahora.

Además, el futuro tiene la costumbre de llegar con requisitos distintos de los que habíamos imaginado. El sistema de extensiones que preparamos quizá no encaje con la necesidad real.

Una forma de aplicar el principio consiste en imaginar el cambio posterior. Si añadir una segunda política dentro de unos meses sería sencillo, quizá podamos esperar a conocer sus condiciones antes de diseñar cómo se incorpora.

Las necesidades actuales incluyen las obligaciones conocidas de seguridad, integridad y funcionamiento. También puede ser necesario investigar una decisión costosa antes de comprometerse con ella. Conviene distinguir esas necesidades de una posibilidad que hemos imaginado tomando café.

Esperar resulta más viable cuando el código se deja cambiar. Mantener pruebas del comportamiento actual, mejorar nombres o reorganizar una función difícil de entender ayuda a incorporar requisitos cuando lleguen. Ese trabajo cuida una necesidad presente: poder evolucionar la aplicación. Si posponemos también ese cuidado, cada cambio futuro puede salir más caro.

Para aplicar YAGNI: identifica qué requisito presente justifica cada capacidad y qué información falta para diseñarla bien.

Podemos comprobarlo con una petición concreta: «El descuento de bienvenida solo se aplica a la primera compra». Necesitamos decidir cómo conocer ese dato y probar la condición. El soporte para campañas por geolocalización puede esperar si nadie lo ha pedido. Esa diferencia nos ayuda a invertir el esfuerzo en el comportamiento que vamos a entregar.

Cómo trabajan juntos

Volvamos a la promoción del 10 %. Aclaramos sus condiciones y escribimos una solución fácil de seguir. Si el carrito y la factura necesitan la misma regla, la dejamos en un lugar común. El soporte para promociones todavía desconocidas puede esperar.

Con el tiempo aparece una campaña diferente. Ahora tenemos dos casos reales que comparar: qué comparten, qué cambia y qué contrato podría servirles. Esa información permite elegir una abstracción con más criterio.

Los tres principios dejan preguntas útiles para revisar el diseño:

  • KISS: ¿se entiende la solución con un esfuerzo razonable?
  • DRY: ¿tenemos una referencia clara para el conocimiento que debe mantenerse coherente?
  • YAGNI: ¿esta capacidad responde a una necesidad que conocemos?

Cuando una decisión parece complicarse, volver a estas preguntas suele ser un buen comienzo. Si para aplicar un descuento necesitamos explicar media plataforma, quizá el descuento esté pidiendo una solución más pequeña.