Tanquem el mòdul amb una pregunta que no va de tècnica sinó de procés: si l'equip de PideYa treballa en sprints de dues setmanes, lliurant a cadascun la funcionalitat mínima que aporta valor, quan es dissenya? Es dibuixen els patrons per endavant, o es deixen aparèixer? La tensió entre dissenyar bé i lliurar aviat és tan vella com l'agilitat mateixa, i els patrons són just al centre: són l'eina perfecta de disseny... i la temptació perfecta de sobreenginyeria (ho vam veure a 05-05). Aquesta lliçó ensenya a resoldre aquesta tensió: com emergeixen els patrons del cicle TDD, com fan el codi testejable, quan apostar per un a mig sprint i com es converteixen en el llenguatge de l'equip.
Contingut
- Disseny emergent vs. Big Design Up Front
- TDD i patrons: el refactor com a bressol
- Patrons que faciliten testejar
- YAGNI vs. anticipació: quan apostar
- Patrons com a llenguatge de l'equip: revisions i ADRs
- Cas: una història d'usuari, tres sprints
Disseny emergent vs. Big Design Up Front
El Big Design Up Front (BDUF) dissenya tota l'arquitectura abans d'escriure codi: diagrames de tots els patrons, totes les interfícies, tots els casos. El seu problema no és dissenyar — és dissenyar amb la informació de pitjor qualitat: al principi del projecte és quan menys saps del domini. El BDUF de PideYa hauria dissenyat un Abstract Factory de passarel·les per a vuit països... i després el negoci només va obrir en dos.
El disseny emergent inverteix l'aposta: s'escriu el mínim que resol la història actual, amb tests, i el disseny es refina contínuament mitjançant refactorització. Els patrons no es col·loquen: es descobreixen quan el codi els demana. Compte amb la caricatura: disseny emergent no vol dir "sense disseny" — vol dir disseny continu, amb criteri a cada pas. Les quatre regles del disseny simple de Kent Beck ordenen aquest criteri: (1) passen els tests, (2) revela la intenció, (3) sense duplicació, (4) tan petit com sigui possible.
| BDUF | Disseny emergent | |
|---|---|---|
| Quan es decideix un patró | Abans de codificar | Quan el codi el demana |
| Risc típic | Patrons per a futurs que no arriben (patternitis) | Deute si no es refactoritza de debò |
| Informació disponible | Mínima | Màxima (codi i tests reals al davant) |
| Requereix | Encertar el futur | Disciplina de refactorització i tests |
TDD i patrons: el refactor com a bressol
El cicle TDD — red (test que falla) → green (codi mínim que passa) → refactor (millorar sense canviar comportament) — té un moment exacte on neixen els patrons: el refactor. En green està prohibit ser llest; en refactor, amb la xarxa de tests estesa, mires el codi i apliques el mètode de 05-01: quina força està fent mal? quin patró la resol?
Exemple real de l'equip de PideYa. Sprint N, història "cobrar amb targeta": en green n'hi ha prou amb una classe CobramentTargeta. Sprint N+1, història "cobrar amb Bizum": el segon test porta a un if (metode == BIZUM). Sprint N+2, "cobrar amb PayPal": tercer if. Ara el codi fa mal (regla 3: duplicació de l'esquelet de cobrament) i el refactor extreu la interfície MetodeCobrament — l'Strategy de 04-10 ha emergit, amb tres implementacions reals i ni una d'especulativa.
L'altra meitat del vincle la vam veure a 05-04: quan el codi no té tests, els tests de caracterització (fixar el comportament actual abans de tocar res) són el prerequisit per refactoritzar cap a patrons. TDD et regala aquesta xarxa des del principi; la caracterització la reconstrueix a posteriori. En tots dos casos la seqüència és la mateixa: xarxa de tests primer, patró després.
Patrons que faciliten testejar
La relació és bidireccional: TDD fa emergir patrons, i certs patrons fan possible el TDD. Els tres mecanismes, amb els seus dobles de prova en JUnit:
1. DIP + injecció → dobles de prova. Si FacanaCheckout rep els seus ports per constructor (06-01), el test els substitueix per mocks:
@Test
void checkoutRebutjatSiElCobramentFalla() {
PassarellaPagament passarella = mock(PassarellaPagament.class); // doble del port
Notificador notificador = mock(Notificador.class);
when(passarella.cobrar(any(), any()))
.thenReturn(ResultatCobrament.rebutjat("fons insuficients"));
var checkout = new FacanaCheckout(passarella, notificador); // injeccio: la costura
assertThrows(CobramentRebutjatException.class, () -> checkout.processar(comandaDeProva()));
verify(notificador, never()).notificar(any()); // no es notifica allo que no s'ha cobrat
}Sense la injecció, FacanaCheckout faria new AdaptadorRedsys() per dins i el test necessitaria... un compte de Redsys. La interfície injectada és el punt per on el test entra.
2. Strategy → fakes. Un fake és un doble amb lògica real simplificada. La PassarellaEnMemoria de 06-01 és un fake del port de pagaments: cobra "de debò" contra un mapa en memòria, i serveix per a tests d'integració ràpids sense mockejar cada crida. Tota Strategy admet una estratègia-fake: un CalculEnviamentFix que sempre retorna 2,99 € fa deterministes els tests del checkout.
3. Façanes → costures de test. Una costura (seam, Michael Feathers) és un punt on pots canviar el comportament sense editar el codi. FacanaCheckout és la costura perfecta per als tests de la capa web: els tests del controlador mockegen la façana sencera i no arrosseguen el subsistema de checkout; els tests del subsistema ataquen la façana real amb fakes als ports. Cada frontera ben dissenyada (façana, port, estratègia) és un lloc on un test pot agafar el sistema.
Regla de diagnòstic: el codi difícil de testejar és codi mal dissenyat. Si per provar una classe necessites base de dades, xarxa i un Singleton mutat (la Singletonitis de 05-05), el test t'està cridant quin patró falta.
YAGNI vs. anticipació: quan apostar
YAGNI ("You Aren't Gonna Need It"): no construeixis per a necessitats futures hipotètiques. Però llavors no s'anticipa mai res? La resposta pràctica distingeix dues coses que se solen confondre:
- Flexibilitat especulativa (cara d'afegir, cara de treure): jerarquies, fàbriques i capes "per si de cas". Aquí YAGNI guanya gairebé sempre — treure un Abstract Factory innecessari costa més que afegir-lo quan calgui.
- Costures barates (gairebé gratis d'afegir, valuosíssimes després): rebre dependències per constructor, programar contra interfícies a les fronteres (BD, xarxa, tercers), no deixar cap
newd'infraestructura enterrat al domini. Això no és especular: és no clavar portes.
Guia per decidir a mig sprint:
| Senyal | Decisió |
|---|---|
| La necessitat és de la història actual | Aplica el patró ja, sense culpa |
| "Segurament en algun sprint futur..." | YAGNI: codi simple + costura barata |
| Segona vegada que apareix la variació | Prepara el refactor (vegeu la regla de tres) |
| Tercera vegada | Refactoritza cap al patró: ja tens tres casos reals que el validen |
| És una frontera amb l'exterior (pagament, enviament, tercers) | Interfície des del dia 1: el cost és una línia |
La regla de tres (Fowler) és el punt mitjà operatiu entre YAGNI i anticipació: a la tercera repetició, el patró es paga sol — i és la versió àgil de l'"espera fins que faci mal" de 05-01.
Patrons com a llenguatge de l'equip
L'avantatge més citat dels patrons des de 01-06 — el vocabulari compartit — és en àgil una eina de procés:
- En revisions de codi: "això demana un Strategy" transmet en quatre paraules un redisseny complet; "aquest mediador s'està engreixant" (el mediador-déu de 05-05) és una alarma que tothom entén. El comentari de revisió amb nom de patró és verificable: el revisat pot anar al catàleg i comprovar si les forces hi encaixen.
- En ADRs lleugers (Architecture Decision Records): un fitxer Markdown per decisió, al mateix repo, amb quatre camps. El del circuit breaker de PideYa:
# ADR-014: Circuit breaker a les crides a Pagaments
Estat: acceptada (sprint 23)
Context: dos incidents de fallada en cascada quan Pagaments degrada (>5 s de resposta).
Decisio: Resilience4j amb llindar 5 fallades / espera 10 s; fallback = encuar cobrament diferit.
Consequencies: el checkout respon sempre en <3 s; apareix l'estat "pagament pendent"
que Suport ha de coneixer; alternativa descartada: reintents sense fusible (amplifica la carrega).Deu línies escrites en deu minuts. El seu valor apareix dos anys després, quan algú pregunta "per què hi ha un circuit breaker aquí?" i la resposta no depèn de la memòria de ningú. En àgil, on el disseny es decideix en petit i sovint, els ADRs són la memòria del disseny emergent.
Cas: una història d'usuari, tres sprints
Història del backlog de PideYa: «Com a restaurant vull oferir 2x1 en pizzes els dimarts per atraure comandes entre setmana». Vegem el disseny evolucionar:
Sprint 1 — allò simple que funciona. TDD: test duesPizzesElDimartsEnCobraUna(). En green, la solució mínima és un descompte codificat al càlcul del total: un if (esDimarts() && ...). En refactor s'extreu a un mètode amb nom (aplicarPromocioDimarts), i aquí es para. Zero patrons. Correcte: una promoció no justifica una arquitectura. S'anota la costura: el càlcul del total ja rep el rellotge injectat (Clock), perquè si no, el test del dimarts només passaria els dimarts — la costura barata que YAGNI sí que permet.
Sprint 2 — segona variació. Nova història: "20 % en sushi els diumenges". Segon if. La regla de tres diu: encara no, però prepara el terreny — els tests de totes dues promocions s'organitzen perquè el refactor imminent no els trenqui (proven el resultat, no l'estructura interna).
Sprint 3 — el patró emergeix. Tercera història: "enviament gratis a partir de 30 € a la primera comanda". Tres promocions, tres condicions, tres efectes: el codi fa mal d'una manera reconeixible. Refactor del cicle: emergeix Promocio com a interfície amb implementacions per promoció — Strategy — i les promocions actives s'apliquen en cadena sobre la comanda. A la revisió de codi algú assenyala que si el negoci vol que els restaurants escriguin les seves pròpies regles, el pas següent seria l'Interpreter de regles de promocions que ja vam construir a 04-04. S'escriu un ADR de cinc línies: "Strategy ara; Interpreter només si la història de 'regles configurables per restaurant' entra al backlog". El patró gran queda documentat i ajornat: això és dissenyar amb YAGNI, no contra ell.
El resultat a vista de tres sprints: la mateixa destinació a la qual un BDUF hauria volgut saltar el primer dia — però arribant-hi amb tres promocions reals que validen el disseny, tests que el protegeixen i ni una abstracció de més pel camí.
Errors Comuns i Consells
- Confondre disseny emergent amb no dissenyar: saltar-se sistemàticament la fase de refactor "perquè hi ha pressa" acumula els
ifper sempre. El refactor no és opcional: és on passa el disseny. - BDUF disfressat d'sprint 0: dedicar tres sprints a "muntar l'arquitectura" amb tots els patrons abans de la primera història. Lliura la primera història sobre el mínim i deixa que l'arquitectura creixi amb les següents.
- Mockejar allò que no és teu... ni és frontera: tests amb set mocks encadenats que es trenquen amb cada refactor. Mockeja a les costures (ports, façanes), usa objectes reals al domini pur — que per ser pur (immutable, sense I/O, lliçó 06-04) no necessita dobles.
- Usar YAGNI com a excusa per clavar portes: negar-se a injectar una dependència "perquè YAGNI" confon flexibilitat especulativa amb costura barata. La interfície a la frontera costa una línia; el
newenterrat costa un sprint de desenterrar. - Decisions de patró sense rastre: el patró es va aplicar, el perquè es va oblidar, i dos anys després algú el des-refactoritza (05-05) perquè "sobrava". Deu minuts d'ADR ho eviten.
- Imposar el patró a la revisió sense les forces: "jo aquí hi posaria un Visitor" sense assenyalar què fa mal és opinió, no revisió. Anomena la força (duplicació, frontera, variació) i llavors el patró.
Exercicis
- Classifica les anticipacions. Per a cada decisió al sprint 1 d'una funcionalitat nova de PideYa ("propines al repartidor"), digues si és costura barata (fes-la) o flexibilitat especulativa (YAGNI): (a) interfície
CalculadorPropinaamb una sola implementació de percentatge fix; (b) rebre el repositori de propines per constructor; (c) jerarquia d'esdevenimentsPropinaAfegida/PropinaModificada/PropinaCanceladaamb Event Sourcing "perquè les comandes ja l'usen"; (d)Clockinjectat per al timestamp de la propina. - Fes testejable la classe.
GeneradorFacturescrida internamentConfiguracioPideYa.getInstancia().getIvaVigent()inew ClientHisenda().enviar(factura). Explica per què és difícil de testejar i refactoritza-la (signatura de la classe i test d'exemple) usant els patrons d'aquesta lliçó. - Escriu l'ADR. L'equip decideix al sprint 3 del cas 2x1 aplicar Strategy i ajornar Interpreter. Redacta l'ADR complet (títol, estat, context, decisió, conseqüències) en menys de 12 línies.
Solucions
- (a) Frontera dubtosa: si el càlcul és una regla interna estable, la interfície amb una sola implementació és especulativa — YAGNI, extreu-la quan arribi la segona variant (regla de tres); només és defensable si ja se sap que percentatge/fix/arrodoniment entra el sprint que ve. (b) Costura barata: fes-la — és una línia i sense ella no hi ha test unitari possible. (c) Flexibilitat especulativa clara: tres tipus d'esdeveniment i Event Sourcing per a una funcionalitat que encara no té ni una escriptura real; YAGNI. (d) Costura barata: sense
Clockinjectat els tests depenen de l'hora real — fes-la. - És difícil de testejar perquè el Singleton acobla a l'estat global (no pots variar l'IVA per test sense mutar-lo — Singletonitis) i el
newintern obliga a parlar amb Hisenda de debò. Refactor:public GeneradorFactures(ConfiguracioFiscal config, EnviamentFactures enviament)— dos ports injectats (DIP);ConfiguracioFiscalla poden implementar la configuració real i un fake de test;EnviamentFacturesla implementenClientHisendai un mock. Test:var gen = new GeneradorFactures(configAmbIva(21), mock(EnviamentFactures.class)); var f = gen.generar(comandaDe(100)); assertEquals(new BigDecimal("121.00"), f.total());— sense xarxa, sense estat global, determinista. - Exemple vàlid:
# ADR-021: Strategy per a promocions/Estat: acceptada (sprint 3)/Context: tres promocions (2x1 dimarts, 20 % sushi diumenges, enviament gratis >30 EUR) implementades com a ifs al calcul del total; duplicacio i risc de regressio en afegir la quarta./Decisio: interficie Promocio amb una implementacio per promocio, aplicades en cadena sobre la comanda; alta de promocions per configuracio./Consequencies: afegir una promocio = una classe + un test, sense tocar el checkout; descartat de moment l'Interpreter de regles (04-04): nomes s'adoptara si entra al backlog la historia "regles configurables per restaurant"; els tests existents de les tres promocions es conserven com a xarxa de seguretat.
Conclusió
Els patrons i l'agilitat no competeixen: es necessiten. El disseny emergent sense catàleg és fer voltes reinventant solucions amb noms pitjors; el catàleg sense disseny emergent és patternitis planificada. L'equilibri que practica l'equip de PideYa cap en quatre hàbits: deixar que els patrons neixin al refactor del cicle TDD, mantenir costures barates a les fronteres encara que YAGNI retalli tota la resta, aplicar la regla de tres abans d'abstraure, i deixar per escrit — en una revisió, en un ADR de deu línies — per què cada patró és on és.
Amb aquesta lliçó es tanca el mòdul 6 i, amb ell, el recorregut tècnic del curs: els vint-i-tres patrons del GoF construïts peça a peça sobre PideYa, els criteris per triar-los i no abusar-ne, i el catàleg modern — arquitectures, microserveis, distribució, concurrència i procés àgil — on vas reconèixer una vegada i una altra les mateixes intencions estirades sobre problemes nous. Aquesta és potser la lliçó de fons: els catàlegs creixen, però les intencions romanen, i qui les domina aprèn cada patró nou en minuts. El viatge d'aprenentatge, en canvi, no s'acaba aquí: a l'últim mòdul reunim els millors recursos per continuar-lo — començant per Llibres Recomanats.
Curs de Patrons de Disseny de Programari
Mòdul 1: Introducció als Patrons de Disseny
- Què són els Patrons de Disseny?
- Història i Origen dels Patrons de Disseny
- Principis de Disseny: SOLID i Altres Fonaments
- UML Essencial per Entendre Patrons
- Classificació dels Patrons de Disseny
- Avantatges i Desavantatges d'Usar Patrons de Disseny
Mòdul 2: Patrons Creacionals
- Introducció als Patrons Creacionals
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparativa i Elecció de Patrons Creacionals
Mòdul 3: Patrons Estructurals
- Introducció als Patrons Estructurals
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparativa i Elecció de Patrons Estructurals
Mòdul 4: Patrons de Comportament
- Introducció als Patrons de Comportament
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparativa i Elecció de Patrons de Comportament
Mòdul 5: Aplicació de Patrons de Disseny
- Com Seleccionar el Patró Adequat
- Exemples Pràctics d'Ús de Patrons
- Patrons de Disseny en Projectes Reals
- Refactorització Usant Patrons de Disseny
- Antipatrons: Quan els Patrons es Tornen un Problema
Mòdul 6: Patrons de Disseny Avançats
- Patrons de Disseny en Arquitectures Modernes
- Patrons de Disseny en Microserveis
- Patrons de Disseny en Sistemes Distribuïts
- Patrons de Concurrència
- Patrons de Disseny en Desenvolupament Àgil
