Essays / Craft
Cartografia dei pattern, non inventario
Quello che segue è insieme un saggio breve e un esperimento.
Complexity is the single major difficulty in the successful development of large-scale software systems.
– Ben Moseley, Peter Marks, Out of the Tar Pit (2006)
La patternite
Il modo più comune di leggere un catalogo di pattern è anche il più dannoso: come un inventario da cui attingere. Visto così, il catalogo si riduce a uno scaffale di soluzioni pronte, e più pattern si incastrano nel codice, più il lavoro sembra riuscito. Il risultato è la patternite: classi che indossano il nome di un pattern come un distintivo e livelli di indirezione introdotti per esibizione, con la complessità accidentale che cresce a ogni “applicazione”. Il fenomeno non è d’annata: ha solo cambiato abito, dall’architettura pulita presa a culto al microservizio che “deve” avere CQRS ed Event Sourcing, fino al codice generato dai LLM, che infila Factory e Strategy dove nessuno le ha chieste. I cataloghi stessi mettono in guardia da questo: la Gang of Four lega ogni pattern a un problema in un contesto, con sezioni dedicate ad applicabilità e conseguenze, e un pattern definito dal suo contesto non si applica ovunque; ma è l’avvertenza meno letta del libro.
Costruire software è un’attività economica: per ogni problema si cerca il compromesso più adeguato agli scopi, e ogni pattern introdotto è un costo che entra nel bilancio solo se rende. Da qui la tesi: un catalogo di pattern non è una cassetta degli attrezzi da cui massimizzare il prelievo, ma una mappa; e saper escludere un pattern con cognizione pesa più che saperlo applicare. Escludere non è purismo, è economia: non si paga ciò che non serve allo scopo.
Tre altitudini sullo stesso terreno
I tre cataloghi storici non sono in competizione né in successione, ma tre altitudini diverse sopra lo stesso terreno, e ciascuno inquadra il problema della costruzione del software a una scala diversa. Non sono la mappa intera: altri cataloghi, dai pattern d’impresa a quelli dei microservizi, si collocano sulle stesse quote; questi tre bastano perché sono quelli che hanno reso canonica la stratificazione per scala.
Il primo, Design Patterns della Gang of Four (GoF), vive alla quota degli oggetti e delle loro collaborazioni dentro un processo. La sua granularità è la classe, il metodo, il piccolo grafo di oggetti che si scambiano messaggi sincroni nello stesso spazio di indirizzamento. Strategy, Decorator, Observer, Composite, Visitor: tutti presuppongono riferimenti condivisi, chiamate dirette, un’unica memoria; è la quota della struttura interna di un componente.
Il secondo, Pattern-Oriented Software Architecture (POSA) di Buschmann e colleghi, sale di quota e guarda la forma grossa del sistema e dei suoi componenti a runtime. La granularità diventa il sottosistema, lo strato, il processo, lo scheletro. Layers, Pipes and Filters, Broker, Microkernel descrivono come si organizza il tutto, non come collaborano due oggetti. I volumi successivi della serie aggiungono concorrenza e networking (Reactor, Proactor, Acceptor-Connector), e qui sta un punto decisivo: quei pattern presuppongono concorrenza e distribuzione. Non sono pattern validi in astratto, sono soluzioni a problemi che esistono solo quando più attività procedono insieme o nodi comunicano.
Il terzo, Enterprise Integration Patterns (EIP) di Hohpe e Woolf, sale ancora e guarda la comunicazione fra sistemi autonomi, o fra componenti disaccoppiati, attraverso messaggi. La granularità è il messaggio e il suo viaggio attraverso i confini: canali, instradatori, traduttori, aggregatori. Content-Based Router, Message Translator, Aggregator, Canonical Data Model presuppongono messaggistica asincrona e l’integrazione di sistemi che non controlli del tutto.
Ogni catalogo codifica un contesto, e quel contesto è la condizione di validità dei suoi pattern. Prova a sollevare un pattern dal terreno che presuppone: osserverai che ne conservi la forma ma ne perdi la forza. Un Aggregator EIP dentro una funzione sincrona in-process è un ciclo con un nome altisonante; un Broker POSA senza distribuzione è un’indirezione che non serve a nulla; un Observer GoF fra due servizi separati da rete non è un Observer, ma un problema di messaggistica travestito. Un catalogo vale solo sul terreno che dà per scontato, e chi conosce i pattern ma non i loro presupposti li applica fuori scala, producendo cerimonia.
I princìpi non sono un quarto piano
C’è una tentazione, quando si stratificano i pattern per scala, di aggiungere un quarto piano: i princìpi orientati agli oggetti (SOLID, e in particolare Dependency Inversion) e il Design by Contract di Meyer. La tentazione va riconosciuta e respinta, perché è un errore di categoria.
I princìpi non sono pattern a una quarta scala, ma un asse ortogonale. Un pattern è una soluzione ricorrente e nominata a un problema in un contesto, un’istanza che puoi indicare nel codice; un principio non lo indichi da nessuna parte, perché è un vincolo con cui ogni soluzione deve fare i conti, a qualunque scala. Dependency Inversion non sta “sopra” Decorator: è una proprietà che il progetto può avere o non avere, sia che tu usi Decorator sia che non lo usi. Command-Query Separation nessuno la “inserisce”: attraversa ogni metodo come una disciplina. I pattern sono istanze; i princìpi sono invarianti. Che poi un principio si realizzi spesso attraverso un pattern, la Dependency Inversion attraverso una Strategy o una Abstract Factory, non confonde i due piani: il pattern resta il mezzo con cui il vincolo viene onorato.
L’immagine corretta non è dunque un palazzo a quattro piani, ma tre strati di pattern ordinati per scala, attraversati da un asse di princìpi che corre verticale per tutti. Confondere i due significa trattare i princìpi come funzionalità da “applicare” anziché come vincoli da rispettare, e un principio applicato come fosse un pattern smette di essere un principio: diventa cerimonia anch’esso (l’interfaccia introdotta “per la DIP” dove non c’è alcuna inversione reale da fare).
Lo stesso gesto, tre nomi
Si prenda l’idea di interporre, tra chi chiama e un componente, un terzo elemento trasparente che aggiunge un comportamento senza che il chiamante se ne accorga. La Gang of Four la chiama Decorator, con l’enfasi sulla composizione di oggetti e l’aggiunta di responsabilità; POSA la chiama Proxy o Interceptor, con l’enfasi sull’indirezione a runtime e sul punto di intercettazione; EIP la chiama Wire Tap quando il comportamento aggiunto è l’osservazione del flusso di messaggi.
Gli intenti divergono nei dettagli (Proxy controlla l’accesso, Decorator aggiunge responsabilità, Wire Tap deriva una copia del traffico), e divergono anche le strutture: il Decorator e il Proxy si interpongono in linea, preservando l’interfaccia, mentre il Wire Tap lavora fuori banda, biforcando una copia su un canale a parte. A ricorrere attraverso le tre quote non è la struttura ma il gesto, interporre un elemento trasparente che il chiamante non vede: è parallasse. Si tratta dello stesso gesto visto da tre altitudini, e ciascun vocabolario mette in primo piano ciò che conta alla sua scala: alla quota degli oggetti la composizione, a quella del sistema dove si inserisce l’indirezione, a quella dell’integrazione cosa succede al messaggio.
Non si codifica il nome del pattern nell’identificatore, perché il nome è relativo all’altitudine mentre l’artefatto è uno solo. Chiamare una classe LoggingDecorator la incatena al vocabolario GoF e a una sola delle tre quote. Si nomina la cosa per ciò che fa nel dominio, e si documenta il ruolo di pattern a parte, dove può convivere con i suoi sinonimi di quota.
La disciplina dell’esclusione
Per la maggior parte dei sistemi, i cataloghi sono più grandi nelle loro parti inapplicabili che in quelle applicabili, e la cultura dei pattern si dimostra in ciò che si sa lasciare fuori; l’eccezione sono i grandi sistemi distribuiti e concorrenti, dove molto di EIP e POSA si guadagna davvero il posto. Davanti a un problema, chi conosce il mestiere sa partizionare il catalogo in tre insiemi: i pattern che si applicano, quelli che sono già implicitamente presenti e quelli che sarebbero cerimonia. Il terzo insieme è di norma il più grande, e distinguerlo dagli altri due richiede il giudizio che l’erudizione da sola non dà. Escludere bene non è l’opposto del conoscere, ma il suo uso più esigente: bisogna possedere il catalogo a fondo proprio per sapere cosa lasciarne fuori, perché conoscere un pattern e dispiegarlo restano cose diverse.
Gran parte di EIP è irrilevante per un progetto sincrono in-process: non c’è messaggistica, quindi instradatori e canali e dead letter sono soluzioni a problemi che non esistono. Gran parte del secondo volume di POSA è irrilevante senza concorrenza: senza eventi concorrenti da smistare, un Reactor non reagisce a nulla. Saper vedere questa irrilevanza, e affermarla, conta più del saper elencare i pattern. L’inclusione senza questo filtro è rumore: ogni pattern non giustificato aggiunge un’indirezione, un nome da imparare, uno strato da attraversare al debug, complessità accidentale senza una forza che la compensi.
Qui sta il rovesciamento rispetto alla lettura immediata: i cataloghi vanno letti come descrizioni, non come prescrizioni. Registrano soluzioni che sono ricorse, insieme al contesto che le ha fatte ricorrere; non comandano di andare a prenderle. Nella tradizione che risale a Christopher Alexander, una struttura si guadagna il nome di pattern solo dopo essere ricorsa in sistemi reali, non prima. Letti come prescrizioni producono over-engineering; letti come una mappa, localizzazione: ti dicono dove sei, a che quota vive il tuo problema e quali soluzioni quella quota richiede davvero.
Riconoscere non è introdurre
Buona parte di ciò che passa per “applicare un pattern” è in realtà riconoscimento, dato che la struttura era già la soluzione ovvia e il nome del pattern le viene attaccato a posteriori. Questo è economico e comunicativo: aggiunge vocabolario condiviso e zero righe di codice. L’introduzione è cosa diversa, perché cambia il codice per portare dentro un pattern e va pagata da una forza reale: un punto di variazione che varia davvero, un accoppiamento che fa male sul serio.
Tre categorie da tenere separate: riconoscere ciò che c’è già, gratis, da fare con liberalità, perché allinea il linguaggio del team al codice senza toccarlo; introdurre perché paga, giustificato, ma solo a fronte di una forza nominata, perché ogni pattern introdotto è un debito di complessità che deve fruttare; introdurre per il gusto di farlo, cerimonia, da rifiutare. Solo la categoria di mezzo è “applicare un pattern” nel senso costoso del termine, e ha sempre un conto da rendere: quale forza risolve. Un pattern introdotto senza una forza che lo giustifichi è una passività travestita da raffinatezza.
Leggere la mappa
I pattern sono esperienza di progetto compressa: il verbale di ciò che è ricorso, con allegato il contesto che lo ha fatto ricorrere; qui sta il loro valore, e insieme il loro limite. Chi li ha assimilati usa la mappa per localizzare l’altitudine e il terreno del problema, poi prende solo ciò che quella posizione esige, e sa dire con precisione perché tutto il resto resta sulla mappa e fuori dal codice.
La misura non è il numero di pattern che dispieghi, ma il rigore con cui giustifichi quelli che hai lasciato fuori.
Bibliografia
Buschmann, Frank, Regine Meunier, Hans Rohnert, Peter Sommerlad, and Michael Stal. Pattern-Oriented Software Architecture, Volume 1: A System of Patterns. Chichester: John Wiley & Sons, 1996.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1994.
Hohpe, Gregor, and Bobby Woolf. Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions. Boston: Addison-Wesley, 2003.
Martin, Robert C. Agile Software Development: Principles, Patterns, and Practices. Upper Saddle River, NJ: Prentice Hall, 2002.
Meyer, Bertrand. Object-Oriented Software Construction. 2nd ed. Upper Saddle River, NJ: Prentice Hall, 1997.
Schmidt, Douglas, Michael Stal, Hans Rohnert, and Frank Buschmann. Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects. Chichester: John Wiley & Sons, 2000.
Saggio concepito e rifinito nell’arco di circa 18 giorni, con un tempo di lavoro complessivo attorno alle 5 ore, in gran parte ri-lettura, giudizio, revisione, e solo in minore parte stesura: costruito con lo stesso metodo che raccomanda per il software.