Sii acqua, amico mio. L’acqua può scorrere o può colpire. Sii acqua, amico mio.

— Bruce Lee

Se pensi che l’ottimizzazione precoce sia la radice di tutti i mali della programmazione, significa che sei stato abbastanza fortunato da non aver ancora incontrato l’ingegneria precoce.

In questo articolo introdurrò FLUID, una nuova metodologia di programmazione che ho sviluppato nel corso della mia carriera, basata sui primi principi del manifesto AGILE e sul secondo principio di minimalità che ho raffinato nel tempo: il principio del codice minimo per azione.

Il principio del codice minimo per azione completa il più antico tra i principi di programmazione che ho proposto, il principio di minima incompletezza. Insieme, questi principi forniscono un quadro per comprendere la fonte della leggibilità e della flessibilità del software, e gli assiomi da cui muove qualsiasi metodologia di programmazione.

Sebbene la metodologia FLUID sia diametralmente opposta alla più nota SOLID, i principi di minimalità (incompletezza e codice per azione) informano entrambe. Spiegherò come ciò sia possibile nel resto di questo articolo.

Demistificare SOLID

La metodologia SOLID è ben conosciuta nel settore, ma continuo a trovare sviluppatori che ne hanno una comprensione piuttosto vaga. Mi piace pensare che ciò sia dovuto all’eccessiva enfasi glamourizzata di concetti piuttosto semplici, spesso per fini contrattuali o di marketing. In termini semplici, SOLID, più che una metodologia, è un framework che raccoglie pratiche atte a mantenere leggibili e manutenibili grandi basi di codice. L’acronimo significa:

  • Single Responsibility Principle: una classe dovrebbe avere un solo motivo per cambiare.
  • Open/Closed Principle: gli oggetti o entità dovrebbero essere aperti all’estensione ma chiusi alla modifica.
  • Liskov Substitution Principle: gli oggetti di una superclasse dovrebbero poter essere sostituiti da oggetti di una sottoclasse senza compromettere la correttezza del programma.
  • Interface Segregation Principle: molte interfacce specifiche per cliente sono migliori di un’unica interfaccia generica.
  • Dependency Inversion Principle: dipendere dalle astrazioni, non dalle concretizzazioni.

È evidente che questo framework presuppone un ambiente orientato agli oggetti, dove un’eccessiva libertà nella creazione delle astrazioni (le classi) può portare a codice spaghetti e soluzioni ad hoc che hanno senso solo per gli sviluppatori originali — se pure per loro.

Grandi progetti orientati agli oggetti richiedono disciplina e schemi di progettazione chiari, e SOLID offre un framework per standardizzare e quindi semplificare il processo di scrittura di basi di codice di grandi dimensioni.

Ma non tutta la programmazione avviene in grandi codebase, e non tutti i problemi della cibernetica e dell’informatica si risolvono meglio attraverso un approccio orientato agli oggetti. Questo, di per sé, significa che SOLID non è il modo migliore di scrivere codice in ogni situazione possibile.

Codice Minimo per Azione

Il secondo principio di minimalità che ho scoperto nel corso della mia carriera è che quanto più piccolo è il codice necessario a ottenere un risultato desiderato, tanto meglio.

Idealmente, vorresti poter scrivere semplicemente “fallo”, e che tutto il sistema sia predisposto affinché “lo faccia”.

L’azione di cui parla questo principio è un’attività singola che un programma può eseguire. Trasformare un insieme di dati, riassumere contenuti, eseguire un’analisi statistica, offrire un singolo servizio: tutte queste sono azioni, tipi di output che un programma può generare.

È intuitivo comprendere perché meno codice serve per eseguire una singola azione, meglio è. Meno istruzioni significano codice più facile da capire (rientrando nel principio di minima incompletezza), più semplice da mantenere ed estendere, e meno soggetto a debiti tecnici. Se mai si rendesse necessario riscriverlo, un codice più piccolo è meno problematico di uno grande.

Non sorprende che ogni componente del framework SOLID riduca il rapporto codice/azione nelle grandi basi di codice orientate agli oggetti.

Il principio di singola responsabilità riduce i rami decisionali e la gestione dei casi speciali, che richiederebbero codice aggiuntivo.

L’apertura/chiusura incoraggia l’estensibilità del codice esistente, spingendo a scrivere le classi base in modo da minimizzare la quantità di codice personalizzato necessario per le sottoclassi.

Il principio di sostituzione di Liskov suggerisce che le superclassi non dovrebbero gestire casi speciali, delegandoli invece alle sottoclassi, riducendo ulteriormente il codice di gestione delle eccezioni.

La segregazione delle interfacce aiuta a implementare solo le funzionalità necessarie in ciascuna implementazione concreta.

L’inversione delle dipendenze impedisce che entità del programma debbano controllare il tipo del loro chiamante per gestire casi speciali.

Tutto questo vale per grandi progetti orientati agli oggetti. Tuttavia, al di fuori di questo scenario, il framework SOLID può risultare dannoso per i principi di minimalità — in particolare, in un ambiente a microservizi.

Le Insidie di SOLID

Il framework SOLID mira a minimizzare incompletezza e codice per azione in grandi codebase orientate agli oggetti. Tuttavia, recentemente è emerso un modello di applicazione diverso, divenuto standard industriale: il microservizio.

In un ambiente a microservizi, la compattezza del codice gioca un ruolo fondamentale; inoltre, i microservizi sono progettati intorno a un’interfaccia funzionale, non a una orientata agli oggetti. Descrivere questa differenza in dettaglio va oltre lo scopo dell’articolo, ma in sintesi, l’orientamento agli oggetti è più adatto alla gestione di astrazioni di dati, mentre l’interfaccia API funzionale si basa sul tipo di servizio offerto o richiesto da agenti automatizzati e utenti.

I microservizi non sono l’unico dominio in cui la progettazione orientata agli oggetti non è la più adeguata, né l’unico in cui mantenere la base di codice piccola per progettazione è un requisito centrale: sono semplicemente un esempio chiaro e standardizzato di come un simile design possa avere successo.

I principi SOLID non rispondono bene alle esigenze di tale dominio:

  • Single Responsibility Principle: applicare l’SRP a livello micro all’interno di ciascun servizio può portare a un eccesso di ingegnerizzazione.
  • Open/Closed Principle: può risultare difficile da applicare, poiché i microservizi evolvono rapidamente e richiedono modifiche che non si adattano bene al principio di “chiuso alla modifica”.
  • Liskov Substitution Principle: meno rilevante in architetture a microservizi, dove i servizi comunicano tramite interfacce o contratti ben definiti, piuttosto che per ereditarietà.
  • Interface Segregation Principle: applicabile tra microservizi, ma non necessariamente dentro di essi, poiché un microservizio deve essere dedicato a un solo compito; di conseguenza, il concetto stesso di interfaccia diventa superfluo.
  • Dependency Inversion Principle: nella pratica dei microservizi, l’attenzione si sposta più sui pattern e protocolli di comunicazione tra servizi che sul design di classi astratte.

Il Framework FLUID

FLUID incapsula l’essenza della flessibilità e del design minimalista, soprattutto in contesti come i microservizi. Sottolinea l’importanza di adattabilità, semplicità ed efficienza.

Il design FLUID si compone dei seguenti principi:

  • Flexible Design: abbraccia schemi di design adattabili e modificabili nel tempo.
  • Lean Implementation: concentrati sul minimalismo nel codice e nelle funzionalità, ottenendo di più con meno per raggiungere gli obiettivi in modo efficiente.
  • Universal Interoperability: assicurati che componenti e servizi possano interagire e integrarsi facilmente con sistemi e tecnologie eterogenee.
  • Intuitive Interfaces: progetta componenti con interfacce chiare, semplici e centrate sull’utente, facilitando l’uso e l’integrazione.
  • Decentralised Control: consenti ai singoli componenti o servizi di prendere decisioni basate sulla conoscenza locale, migliorando agilità e reattività.

La metodologia FLUID mira a guidare lo sviluppo di sistemi adattabili, semplici ed efficienti, dando priorità alla facilità di manutenzione e alla capacità di rispondere rapidamente a requisiti mutevoli o a nuove opportunità.

FLUID è Minimalista

Il framework FLUID si basa sul principio del codice minimo per azione e sul principio ancor più fondamentale della minima incompletezza. Questo può sembrare controintuitivo, poiché FLUID si oppone a SOLID, che è a sua volta basato sugli stessi principi.

Il punto è che FLUID e SOLID incarnano gli stessi principi minimalisti in domini diversi. Con una dimensione di base del codice e un ambito d’azione limitati per progettazione, i principi FLUID rispettano la minimalità:

  • Flexible Design: scrivi codice in modo che lo sforzo per adattarsi ai cambiamenti sia minimo.
  • Lean Implementation: non aggiungere mai più codice di quanto strettamente necessario per completare il compito richiesto.
  • Universal Interoperability: se i componenti sono interoperabili, è più facile sostituirli con una nuova implementazione semplice.
  • Intuitive Interfaces: interfacce semplici hanno pochi punti di ingresso e di uscita.
  • Decentralised Control: poiché nessun elemento è cruciale, ogni componente può essere facilmente sostituito con una versione minima.

FLUID fino a un certo punto

Man mano che il sistema cresce e i requisiti si spostano da funzionali (es. fai questo per me) a orientati agli oggetti (es. produci un elenco di oggetti simili a “auto” per me), il framework FLUID dovrebbe naturalmente fluire verso SOLID.

Se gli sviluppatori insistono nel mantenere la semplicità anche quando la complessità aumenta, quando il progetto diventa sufficientemente grande, la fluidità iniziale che consente crescita e flessibilità può trasformarsi in codice spaghetti difficile da mantenere.

È cruciale riconoscere questi punti di flessione e:

  • Diramare il progetto in uno nuovo e separato (eventualmente un microservizio), affinché entrambi — l’originale e il derivato — mantengano la loro semplicità pianificata; oppure
  • Reingegnerizzare la base di codice da FLUID a SOLID, per applicare quei principi che rendono una grande codebase leggibile e flessibile.

Conclusioni

In questo articolo ho introdotto il concetto di metodologia (o framework) di programmazione FLUID, in contrapposizione a SOLID, mettendoli entrambi in relazione con il principio del codice minimo per azione. Ho descritto come entrambe le metodologie rispettino i principi del minimalismo, seppur in modi e contesti differenti.

La conclusione principale è che alcuni domini richiedono un approccio diverso — FLUID — per sviluppare applicazioni in linea con i principi minimalisti.

Per ulteriori approfondimenti sulla metodologia FLUID, vedi gli articoli dedicati: