Més enllà de SOLID, existeix un conjunt de principis pragmàtics que actuen com a brúixola en el dia a dia del disseny. DRY ens recorda no repetir coneixement; KISS advoca per la simplicitat; YAGNI ens frena de construir el que encara no necessitem. A aquests s'hi sumen el principi de mínima sorpresa i la preferència per la composició enfront de l'herència. Aquests principis són aparentment senzills, però la seva aplicació equilibrada distingeix un dissenyador experimentat: cadascun estira en una direcció i saber quan aplicar-los —i quan no— és una habilitat arquitectònica clau. En aquesta lliçó els estudiarem amb exemples pràctics en Java.
Contingut
- DRY: no et repeteixis
- KISS: fes-ho simple
- YAGNI: no ho necessitaràs
- La tensió entre DRY, KISS i YAGNI
- Principi de mínima sorpresa
- Composició sobre herència
- Altres principis útils
- Errors comuns i consells
- Exercicis
- Conclusió
- DRY: no et repeteixis
DRY (Don't Repeat Yourself) estableix que cada peça de coneixement ha de tenir una representació única, inequívoca i autoritzada dins del sistema. El matís important és que DRY tracta de coneixement, no de coincidències textuals de codi.
// VIOLACIÓ de DRY: la regla de l'IVA (21%) està duplicada i dispersa
public class Carrito {
public double total(double base) { return base + base * 0.21; }
}
public class Factura {
public double importe(double base) { return base + base * 0.21; }
}Explicació del problema: la regla "l'IVA és el 21%" viu en dos llocs. Si canvia el tipus impositiu, cal trobar i modificar totes les còpies, amb risc d'oblidar-ne alguna.
// CORRECCIÓ: una única font de veritat
public class PoliticaImpuestos {
private static final double IVA = 0.21;
public double aplicar(double base) { return base + base * IVA; }
}
public class Carrito {
private final PoliticaImpuestos impuestos;
public Carrito(PoliticaImpuestos impuestos) { this.impuestos = impuestos; }
public double total(double base) { return impuestos.aplicar(base); }
}Compte amb el DRY fals: dos fragments de codi poden semblar idèntics avui per casualitat però representar coneixements diferents que evolucionaran per separat. Forçar-ne la unificació crea un acoblament nociu. La regla és: unifica coneixement duplicat, no codi coincident.
- KISS: fes-ho simple
KISS (Keep It Simple, Stupid) recomana triar sempre la solució més senzilla que resolgui el problema. La complexitat ha d'estar justificada per un requisit real.
// VIOLACIÓ de KISS: complexitat innecessària per comprovar si és parell
public boolean esPar(int n) {
String binario = Integer.toBinaryString(n);
char ultimo = binario.charAt(binario.length() - 1);
return ultimo == '0';
}Explicació del problema: converteix a binari, manipula caràcters... tot per a una cosa que té una expressió trivial i directa.
KISS també s'aplica a l'arquitectura: no introdueixis microserveis, cues de missatges o capes d'abstracció si un monòlit modular ben organitzat resol el problema. La complexitat accidental és deute futur.
- YAGNI: no ho necessitaràs
YAGNI (You Aren't Gonna Need It) adverteix contra implementar funcionalitat per anticipat "perquè algun dia farà falta". La majoria d'aquestes prediccions fallen, i el codi especulatiu afegeix cost de manteniment sense aportar valor.
// VIOLACIÓ de YAGNI: parametrització especulativa
public class ExportadorCsv {
// Ningú no ha demanat altres separadors, codificacions ni compressió
public String exportar(List<String> filas, char separador,
String codificacion, boolean comprimir,
boolean incluirCabecera, String pieDePagina) {
// ... lògica enorme que gestiona combinacions que mai no es fan servir
return "";
}
}Explicació del problema: s'han afegit sis paràmetres per a escenaris hipotètics. Cadascun s'ha de provar, documentar i mantenir, encara que només es faci servir la coma.
// CORRECCIÓ: resol només el cas real d'avui
public class ExportadorCsv {
public String exportar(List<String> filas) {
return String.join("\n", filas); // separador per comes dins de cada fila
}
}Si demà es necessita un altre separador, s'afegeix aleshores, amb el requisit real al davant. YAGNI no és excusa per a un mal disseny; és una invitació a no resoldre problemes que encara no existeixen.
- La tensió entre DRY, KISS i YAGNI
Aquests principis de vegades s'oposen, i l'art rau a equilibrar-los:
| Situació | Principi que estira | Principi en tensió | Criteri de decisió |
|---|---|---|---|
| Veig codi repetit | DRY (unificar) | KISS/YAGNI (no abstreure encara) | És el mateix coneixement o coincidència? |
| Vull una abstracció genèrica | DRY | YAGNI | Hi ha 2-3 casos reals o un de sol? |
| El disseny es torna complex | KISS | DRY | La simplicitat justifica una mica de duplicació? |
Una heurística pràctica molt citada és la regla de tres: la primera vegada escrius el codi; la segona vegada que apareix quelcom semblant, ho toleres; la tercera vegada, abstreus. Això concilia DRY amb YAGNI: esperes a tenir evidència real d'un patró abans d'unificar-lo.
- Principi de mínima sorpresa
El principi de mínima sorpresa (Principle of Least Astonishment) diu que un component ha de comportar-se com un usuari raonable espera. El codi no ha d'amagar efectes secundaris sorprenents ni trencar convencions.
// VIOLACIÓ: un getter amb efectes secundaris sorprenents
public class Cuenta {
private double saldo;
public double getSaldo() {
registrarAcceso(); // sorpresa: un getter que escriu
saldo -= 1; // sorpresa: consultar cobra comissió
return saldo;
}
}Explicació del problema: ningú no espera que getSaldo() modifiqui l'estat ni cobri comissions. Qui el faci servir s'endurà una sorpresa difícil de depurar.
// CORRECCIÓ: noms honestos i comportament predictible
public class Cuenta {
private double saldo;
public double getSaldo() { return saldo; } // només consulta
public double consultarConComision() { // intenció explícita
saldo -= 1;
return saldo;
}
}Aplicat a APIs: respecta convencions de noms, retorna tipus esperats, llança les excepcions documentades i evita comportaments ocults.
- Composició sobre herència
L'herència crea un acoblament fort i estàtic entre classes. La composició —construir objectes combinant-ne d'altres— sol ser més flexible.
// PROBLEMÀTIC: herència rígida i explosió de subclasses
public class Pajaro {
public void volar() { /* ... */ }
}
public class Pinguino extends Pajaro {
// els pingüins no volen! Herència mal aplicada
public void volar() { throw new UnsupportedOperationException(); }
}Explicació del problema: heretar volar() obliga el pingüí a tenir un comportament que no li correspon. A més, combinar capacitats (neda, vola, corre) per herència provoca una explosió de subclasses.
// MILLOR: compondre comportaments
public interface Desplazamiento { void mover(); }
public class Vuelo implements Desplazamiento {
public void mover() { System.out.println("Vuela"); }
}
public class Nado implements Desplazamiento {
public void mover() { System.out.println("Nada"); }
}
public class Animal {
private final Desplazamiento desplazamiento; // es compon, no s'hereta
public Animal(Desplazamiento desplazamiento) { this.desplazamiento = desplazamiento; }
public void mover() { desplazamiento.mover(); }
}
// Pinguino = new Animal(new Nado()); Aguila = new Animal(new Vuelo());Explicació de la millora: el comportament s'injecta com un objecte. Es pot canviar en temps d'execució i combinar lliurement. Això és la base del patró Strategy. La regla "afavoreix la composició sobre l'herència" prové del llibre dels patrons de disseny (GoF) i evita jerarquies fràgils.
| Aspecte | Herència | Composició |
|---|---|---|
| Acoblament | Fort, estàtic | Feble, dinàmic |
| Reutilització | Limitada a la jerarquia | Flexible i combinable |
| Canvi en runtime | No | Sí |
| Risc | Trencar LSP, jerarquies profundes | Més objectes a orquestrar |
- Altres principis útils
- Separació comanda-consulta (CQS): un mètode o bé canvia estat (comanda) o bé retorna dades (consulta), però no totes dues coses. Reforça la mínima sorpresa.
- Fail-fast: detectar i reportar errors com més aviat millor (validar paràmetres en entrar) en lloc de propagar estats invàlids.
- Encapsulació: amagar l'estat intern i exposar només comportament; base del baix acoblament.
- Alta cohesió / baix acoblament: ja vistos a la lliçó anterior, són el teló de fons de tots aquests principis.
- Boy Scout Rule: deixa el codi una mica més net del que el vas trobar; manteniment incremental que combat el deute tècnic.
Errors Comuns i Consells
- Aplicar DRY de manera dogmàtica i crear abstraccions prematures que acoblen coneixements diferents. Recorda: la duplicació és més barata que l'abstracció equivocada.
- Confondre KISS amb simplista. Simple no és fer menys del que cal; és no fer de més. Un sistema massa simplista que no cobreix els requisits també és un error.
- Fer servir YAGNI com a excusa per no dissenyar. YAGNI rebutja funcionalitat especulativa, no el bon disseny base ni l'extensibilitat raonable.
- Heretar per reutilitzar línies de codi. Si la relació no és un "és-un" genuí, fes servir la composició.
- Sorpreses en els noms: un mètode anomenat
validar()que a més guarda a base de dades viola la mínima sorpresa. Anomena segons el que realment fa. - Consell: fes servir la regla de tres com a àrbitre entre DRY i YAGNI. I mesura: si una abstracció té un sol ús real, probablement sobra.
Exercicis
Exercici 1 (DRY). Detecta la duplicació de coneixement i corregeix-la:
class Empleado { double bono(double s) { return s * 0.1; } }
class Directivo { double bono(double s) { return s * 0.1; } }Exercici 2 (KISS/YAGNI). Simplifica aquest codi aplicant KISS i YAGNI:
public String saludar(String nombre, boolean formal, boolean mayusculas,
String idioma, boolean conEmoji) {
String saludo = formal ? "Estimado " : "Hola ";
saludo += nombre;
if (mayusculas) saludo = saludo.toUpperCase();
if (conEmoji) saludo += " :)";
return saludo; // només es fa servir saludar(nombre)
}Exercici 3 (Composició). Converteix aquesta jerarquia d'herència en composició:
class Coche { void arrancar() {} }
class CocheElectrico extends Coche { void cargar() {} }
class CocheGasolina extends Coche { void repostar() {} }Solucions
Solució 1. El càlcul del bo és el mateix coneixement; el centralitzem:
class PoliticaBonos {
private static final double TASA = 0.1;
double calcular(double salario) { return salario * TASA; }
}
// Empleado i Directivo reben/fan servir PoliticaBonos en lloc de duplicar la regla.Solució 2. Eliminem els paràmetres especulatius no utilitzats:
Si en el futur cal el mode formal o els idiomes, s'afegeixen amb el requisit real.
Solució 3. Modelem el tipus d'energia com un component:
interface FuenteEnergia { void recargar(); }
class Electrica implements FuenteEnergia { public void recargar() { /* carregar */ } }
class Combustion implements FuenteEnergia { public void recargar() { /* repostar */ } }
class Coche {
private final FuenteEnergia energia;
Coche(FuenteEnergia energia) { this.energia = energia; }
void arrancar() {}
void recargar() { energia.recargar(); }
}
// new Coche(new Electrica()); new Coche(new Combustion());Conclusió
DRY, KISS i YAGNI formen un trio que regula l'equilibri entre rigor, simplicitat i pragmatisme: no repetir coneixement, mantenir la senzillesa i no construir de més. Els hem complementat amb el principi de mínima sorpresa, que exigeix comportament predictible, i amb la preferència per la composició sobre l'herència, que evita jerarquies fràgils. Aplicats amb judici —i arbitrats per heurístiques com la regla de tres— aquests principis redueixen la complexitat accidental. A la lliçó següent farem un salt del disseny de classes al disseny del sistema, tot estudiant les tàctiques arquitectòniques amb què assolim atributs de qualitat com ara la disponibilitat, el rendiment o la seguretat.
Curs d'Arquitectura d'Aplicacions
Mòdul 1: Fonaments de l'Arquitectura d'Aplicacions
- Què és l'Arquitectura d'Aplicacions?
- El Rol de l'Arquitecte de Programari
- Atributs de Qualitat i Requisits No Funcionals
- Decisions Arquitectòniques i Compromisos (Trade-offs)
- Documentació d'Arquitectura: Vistes i el Model C4
Mòdul 2: Principis i Tàctiques de Disseny
- Acoblament, Cohesió i Separació de Responsabilitats
- Principis SOLID Aplicats a l'Arquitectura
- DRY, KISS, YAGNI i Altres Principis de Disseny
- Tàctiques Arquitectòniques per als Atributs de Qualitat
- Gestió del Deute Tècnic
Mòdul 3: Estils i Patrons Arquitectònics
- Arquitectura Monolítica
- Arquitectura en Capes (N-Tier)
- Arquitectura Client-Servidor
- Arquitectura Hexagonal (Ports i Adaptadors)
- Arquitectura Neta i Ceba (Clean & Onion)
Mòdul 4: Arquitectures Distribuïdes i Microserveis
- Introducció als Sistemes Distribuïts
- Arquitectura de Microserveis
- Descomposició de Serveis i Bounded Contexts
- API Gateway, Service Discovery i Comunicació entre Serveis
- Patrons de Resiliència: Circuit Breaker, Retry i Bulkhead
- El Teorema CAP i la Consistència de Dades
Mòdul 5: Arquitectures Dirigides per Esdeveniments i Missatgeria
- Fonaments de l'Arquitectura Orientada a Esdeveniments
- Missatgeria Asíncrona: Cues i Brokers
- Patrons d'Esdeveniments: Event Sourcing i CQRS
- Gestió de Transaccions Distribuïdes: Patró Saga
- Streaming de Dades en Temps Real
Mòdul 6: Disseny Dirigit pel Domini (DDD)
- Conceptes Fonamentals del DDD
- Disseny Estratègic: Bounded Contexts i Llenguatge Ubic
- Disseny Tàctic: Entitats, Agregats i Repositoris
- Mapatge de Contextos (Context Mapping)
Mòdul 7: Dades i Persistència
- Estratègies de Persistència: SQL vs NoSQL
- Patrons d'Accés a Dades: Repository, Unit of Work i DAO
- Base de Dades per Servei i Gestió de Dades Distribuïdes
- Cau i Estratègies d'Invalidació
Mòdul 8: Arquitectura al Núvol i Desplegament
- Fonaments del Cloud Computing (IaaS, PaaS, SaaS)
- Contenidors i Orquestració amb Docker i Kubernetes
- Arquitectura Serverless
- Patrons de Disseny Cloud-Native
- Infraestructura com a Codi (IaC)
Mòdul 9: Qualitat, Seguretat i Observabilitat
- Escalabilitat: Horitzontal vs Vertical i Balanceig de Càrrega
- Alta Disponibilitat i Tolerància a Fallades
- Seguretat per Disseny i Autenticació/Autorització
- Observabilitat: Logging, Mètriques i Traçabilitat
- Rendiment i Proves de Càrrega
