La soglia dei venti anni occupa un posto speciale nella conta degli anniversari perché marca il primo momento in cui si può cominciare a misurare un’esperienza in termini di decadi. Un singolo anno è insufficiente a garantire una giusta prospettiva sulle cose. Una decade, al contrario, permette di valutare il presente con un certo grado di obiettività, mostrando come le cose sono evolute rispetto alle premesse iniziali. Un periodo di venti anni permette di confrontare due decadi tra di loro, e di valutare con sufficiente prospettiva gli eventi più importanti.
Per quelli come me che hanno cominciato a collaborare con MokaByte fin dai primissimi anni, due decadi di MokaByte coincidono con il raggiungimento di un minimo di quattro decadi di vita. Quarant’anni di crescita personale e professionale in un contesto di cambiamenti storici, sociali e tecnologici è un periodo considerevole. Ripercorrere l’evoluzione di MokaByte mi da l’opportunità di fare il bilancio su certi tematiche della mia vita, e di fare un conteggio delle lezioni apprese.
Il Primo Incontro Con Java
Nell’autunno del 1997 ero un giovane studente universitario alle prese con l’esame di Sistemi Operativi. Accanto al classico C, che usavamo come ausilio didattico per farci un’idea della programmazione a basso livello, il consiglio accademico aveva appena introdotto Java come strumento per l’insegnamento della programmazione concorrente.
Questa scelta era giustificata dal fatto che Java includeva a livello di linguaggio primitive per la gestione dei thread che permettevano di implementare con semplicità i principali meccanismi di sincronizzazione, come locks, semafori, e monitor.
Fin dal principio mi resi conto che Java presentava alcune caratteristiche che sfidavano la logica dell’epoca.
In un momento storico in cui l’evoluzione tra una generazione di processori e l’altra si misurava in preziosi incrementi di poche decine di megahertz, Java presentava un meccanismo di esecuzione basato sull’interpretazione di bytecode su virtual machine. Una virtual machine era una sorta di computer virtuale che costituiva il minimo comun denominatore delle maggiori piattaforme dell’epoca, un approccio riduttivo che generava molte perplessità. Sul piano delle performance di esecuzione, il risultato di questo approccio era imbarazzante. Un semplice programma costruito attorno a un ciclo for da qualche migliaio di iterazioni poteva impiegare una decina di secondi prima di presentare un risultato, mentre un equivalente programma C produceva lo stesso risultato in maniera pressoché istantanea.
Come se non bastasse, la presenza di un garbage collector, un meccanismo che sollevava il programmatore dalla gestione manuale della memoria, creava una situazione in cui semplici applicazioni di 100 righe o poco più subivano periodiche interruzioni per operazioni di pulizia.
Erano i tempi di Java 1.1, e modelli di esecuzione Just In Time e HotSpot non solo non esistevano, ma non erano neanche in vista. In capo a cinque anni i laboratori della Sun, della IBM, e della Oracle avrebbero prodotto nuove strategie di esecuzione e garbage collectors generazionali e paralleli, ma per quanto ne potevamo sapere in quel momento, Java sarebbe rimasto per sempre venti volte più lento di C e C++.
Eppure c’era qualcosa di incredibilmente affascinante dietro questo contorto meccanismo di esecuzione che sfidava la logica, dando vita a una cieca forma di fanatismo.
La nostra classe si divise pertanto in due agguerrite fazioni. La stragrande maggioranza si schierò in favore del C, storico linguaggio procedurale di basso livello dalle prestazioni eccezionali. Il resto di noi si convertì a Java, un linguaggio interpretato orientato agli oggetti che, ne eravamo sicuri, avrebbe rivoluzionato la programmazione di alto livello.
Per noi evangelisti di Java il futuro era chiaro, e non riuscivamo semplicemente a capire come mai i programmatori C non si rendessero conto che l’unico futuro del loro amato linguaggio era la codifica di drivers di sistema.
Abbagliati dal fascino della promessa “write once, run everywhere” sviluppammo spocchiose argomentazioni in delirante negazione della realtà per difenderci dai vili attacchi degli invidiosi programmatori della vecchia scuola.
A chi ci faceva notare che i nostri programmi Java erano lenti, facevamo presente con aria di superiorità che di li ad un anno sarebbe uscito il Pentium II Deschutes a 333 Megahertz, e che non valeva la pena perdere il nostro prezioso tempo a ottimizzare i nostri programmi.
A chi cliccava con impazienza i pulsanti del mouse quando le nostre ridicole applicazioni grafiche si congelavano per due o tre interminabili secondi nel bel mezzo di già precarie animazioni, ricordavamo, nascondendo l’umiliazione, che il Garbage Collector permetteva di risparmiare ore e ore a scovare bugs legati alla gestione manuale dei puntatori.
Infine, di fronte a chi ci faceva notare che anche C e C++ erano portabili, a patto di introdurre alcune necessarie direttive di compilazione condizionale, difendevamo con orgoglio la nostra ignoranza del funzionamento di basso livello di processori e sistemi operativi.
L’esplosione combinatoria della quantità di righe di codice eseguite per operazione era un problema che spettava a qualcun altro risolvere. La legge di Moore ci avrebbe vendicati, rendendo vana e obsoleta qualunque iniziativa volta a velocizzare o ottimizzare un programma.
Un Nuovo Culto
L’argomento ultimo nel distinguere tra reazionari programmatori C e visionari programmatori Java, comunque, era la fede nel futuro della programmazione orientata agli oggetti.
La programmazione ad oggetti non era certo una novità. Il C++ era un linguaggio affermato su piattaforme Windows e Unix già dalla fine degli anni ’80, ma la parentela con il C creava una situazione in cui la maggior parte dei programmatori C++ finiva per usare un approccio ibrido, scrivendo programmi procedurali in C che facevano uso di librerie ad Oggetti.
Java invece puntava a tagliare con il passato e a presentare la programmazione Object Oriented come paradigma unico e definitivo. L’ascesa di MokaByte, con cui avrei cominciato a collaborare stabilmente a partire dal 1999, fu fondamentale nel creare in Italia una agguerrita comunità in strenua difesa di questi principi.
Questo approccio, vissuto come rivoluzionario all’epoca, richiedeva una certa dose di fanatismo. Per capirne l’origine, bisogna partire dai fondamenti del linguaggio.
In Java, scrivere un programma semplice come “Hello World” richiede di dichiarare una classe con cinque righe di dichiarazioni per una riga di contenuto.
public class HelloWorld {
public static void main(String args[]) {
System.out.println(“Hello World”);
}
}
Le più semplici istruzioni sono interminabili catene di invocazioni che consistono nell’interrogare un oggetto per ottenere un oggetto, per ottenere un oggetto, per ottenere un oggetto, per effettuare una chiamata la cui firma può essere lunga come il titolo di un film Lina Wertmüller.
Definire una classe con una decina di attributi richiede la stesura di decine di righe di codice di struttura per dichiarare semplici metodi getter e setter, un requisito fondamentale per garantire l’incapsulamento che richiede un certo grado di cieca disciplina.
Dopo aver speso ore e ore a battere migliaia di righe di codice di struttura, tutto comincia ad aquistare senso. Ed è a questo punto che inizia il bello.
Dichiarare una gerarchia di classi con gradi successivi di specializzazione è un atto creativo inebriante. Concepire una gerarchia di interfacce a ereditarietà multipla è un’esperienza spirituale indescrivibile. Progettare una interfaccia di programmazione —una API— con decine di classi, interfacce, e interminabili nomi di metodo auto-descrittivi è un’esperienza mistica che può essere raggiunta da pochi, e che il resto di noi può comprendere solo per intuizione.
L’indiscutibile eleganza di questo approccio era evidente a chi come noi aveva visto la Luce. Argomentazioni legate all’eleganza dell’architettura presero il sopravvento su qualunque considerazione puramente algoritmica. La programmazione non era più una questione operazionale, ma una questione filosofica e spirituale.
Il mondo prese a rivelarsi di fronte ai nostri occhi in gerarchie di oggetti, patterns, e frameworks. E il mondo accademico si mise al lavoro per donare al mondo infernali pratiche di sviluppo del software come il refactoring, il design orientato ai pattern e, Dio ci perdoni, l’UML.
Inebriati dal potere di questi strumenti, cominciammo a domandarci come applicarli a tutto ciò che volevamo fare. La domanda che avremmo dovuto porci invece era se tutto questo fosse davvero necessario.
Refactoring
Il primo segno di decadenza del nostro movimento culturale fu il refactoring, una strategia di sviluppo del codice popolarizzata da Martin Fowler con il suo libro “Refactoring: Improving the Design of Existing Code.”
Il refactoring è un insieme di tecniche per modificare il codice in modo da renderlo più chiaro, migliorarne l’eleganza, e rafforzarne la struttura. Il concetto di fondo è che il codice non va giudicato solamente sul piano funzionale e su quello della correttezza, ma anche su quello estetico e strutturale.
In pratica, la disciplina del refactoring consisteva nel prendere un programma da 300 righe perfettamente funzionante e riformularlo secondo una elegante architettura di 50 o 60 classi con tre livelli di ereditarietà e decine di relazioni. La trasformazione avveniva gradualmente, alternando periodi di codifica a periodi di revisione del codice per migliorarne la leggibilità e l’eleganza strutturale. Il risultato era un programma che faceva esattamente la stessa cosa di prima, ma con una base di codice dieci o venti volte maggiore.
Questo approccio rivelava due problemi di fondo. Da una parte, il concetto di “eleganza” è piuttosto arbitraria, come dimostravano gli accesi dibattiti nella letteratura dell’epoca. Dall’altra, per ogni pratica di refactoring ne esisteva una uguale e contraria (“Extract Method” vs. “Inline Method,” “Change Value to Reference” vs. “Change Reference to Value,” e via così), rendendo il refactoring, in molti casi, un gioco a somma zero.
Ma la ricerca dell’eleganza e il desiderio di scrivere codice per il gusto di farlo era un richiamo irresistibile, e noi ci buttammo con singolare entusiasmo.
Pattern-Oriented Programming
Il secondo segnale dell’avvento di un’epoca di eccessi fu il Pattern-Oriented programming, popolarizzato dalla Gang of Four nel libro “Design Patterns: Elements of Reusable Object-Oriented Software”.
L’idea di fondo era sensata: un pattern è una elemento di architettura del software che può essere adattato a diversi contesti. Al pari di un algoritmo, che descrive in modo astratto una sequenza di operazioni che vanno poi adattati al contesto, un pattern descrive un insieme di classi e interfacce, i rispettivi ruoli, e le modalità di scambio di messaggi e informazioni da adattare al linguaggio e al dominio applicativo in cui lo vogliamo utilizzare.
Nelle nostre mani, tuttavia, i pattern divennero la scusa per moltiplicare a dismisura la quantità di codice di struttura, indipendentemente dalla reale necessità di farlo. Ispirati della Gang of Four, cominciammo ad organizzare il nostro codice in pattern, sviluppandone di nuovi e originali, come il Pointless Indirection, che prevede la continua aggiunta di livelli di incapsulamento nidificati in un malinteso ossequio al principio dell’information hiding, il Responsibility Bumper, in cui una classe delega una funzione ad un’altra classe senza nessuna ragione particolare, e il Simple Solution Obfuscator, che ha lo scopo di nascondere il fatto che il servizio fornito da un gruppo di venti classi meticolosamente dichiarate e implementate si risolve in una singola riga di codice nascosta al termine di una interminabile catena di chiamate.
Universal Modeling Language
Ma il segnale definitivo di una progressiva disconnessione con la realtà fu la stagione dell’Unified Modeling Language (UML), un tentativo accademico di portare il rigore della progettazione upfront tipico delle discipline ingegneristiche nel fluido mondo del software.
Per chi se lo ricorda ancora, l’UML era una notazione visuale che ricorreva a sette tipi di diagrammi (poi saliti a 14) per modellare diversi aspetti di un’architettura software, come relazioni tra classi, sequenze di scambio messaggi, transizioni di stato, e così via.
In università ne andavamo pazzi. L’UML era lo strumento definitivo per asserire la nostra superiorità morale e intellettuale nei confronti degli arretrati programmatori procedurali perché ci permetteva di separare completamente l’inebriante attività di progettazione dalla noiosa e scontata attività di codifica.
Attraverso il modeling di un’architettura noi ingegneri del software potevamo finalmente formulare soluzioni eleganti a qualunque problema senza perderci dietro a irrilevanti dettagli implementativi. Il nostro motto, che dichiaravamo con orgoglio, è che il 90% del tempo destinato a completare un progetto andava dedicato al design.
Quello che presto avremmo scoperto è che nel mondo reale l’implementazione di codice funzionante richiede il restante 90%.
Il problema principale dell’UML e della progettazione di software upfront è l’illusione di poter catturare a priori in un modello non solo tutte le possibili circostanze di uso del programma, ma anche tutte le implicazioni derivanti dalla codifica in termini di tempi di sviluppo, complessità computazionale, dipendenze, e prestazioni.
Progettare una intera applicazione upfront e aspettarsi che l’implementazione vada avanti senza intoppi seguendo le specifiche, in realtà, è estremamente ingenuo. Un modello troppo dettagliato crea vincoli che potrebbero risultare inutili o limitanti in fase di implementazione. Un modello troppo semplificato finisce inevitabilmente per sottovalutare la complessità di realizzare specifici elemento dell’architettura.
La dipendenza da fattori interni, come complessità computazionale di un particolare algoritmo, o esterni, come le prestazioni di una libreria da cui dipende il progetto, possono vanificare completamente l’efficacia di un approccio assolutamente brillante sulla carta. Architetture che sembrano geniali si rivelano disastrose una volta implementate, e il danno cresce in modo esponenziale al crescere della dimensione del progetto.
Questo è probabilmente l’argomento più forte in favore di metodologie snelle tipo Agile, in cui un progetto software viene sviluppato in maniera incrementale da team relativamente piccoli che lavorano in brevi iterazioni, o sprint, culminanti in releases negoziate con l’utente finale.
Una Prospettiva Ventennale
Nel 2009 andai incontro ad una svolta professionale (documentata isu Mokabyte nella serie “Viaggio a El Dorado: alla scoperta della robotica spaziale in Giappone”) che mi portò a cambiare settore e professionalità. Non avendo necessità di usare Java, ho piano piano perso contatto con l’evoluzione della piattaforma.
Scrivere questo articolo mi ha richiesto di percorrere in poche ore gli sviluppi degli ultimi sette anni, un’azione che di sicuro dona un po’ di prospettiva.
Dopo un inizio incerto a metà degli anni ‘90, Java si è affermato non solo come tecnologia, ma anche come fenomeno culturale nel mondo informatico. I trend che ho descritto sopra sono solo alcune delle pratiche che la comunità Java ha creato, incorporato, e diffuso.
All’inizio degli anni 2000, Java era considerato una delle cose più “cool” in circolazione. Quando dico “cool” non sto parlando di “Star Trek” cool: sto parlando di “George Michael” cool. La canzone “Freeek!” del 2002 suggeriva che la conoscenza di Java fosse ciò che serviva per ottenere “un po’ di azione” (“You got yourself some action, Said you got your sexy Java, You got your speed connection”). Era inevitabile che ci facessimo prendere un poco la mano.
Ma nel corso degli anni, Java è diventato un linguaggio enorme e complesso che si è lentamente ingrandito fino a comprendere una serie di paradigmi che per anni aveva rifiutato o addirittura deriso (ultime in ordine di tempo: l’ereditarietà multipla e la programmazione funzionale con Java 8).
La ricerca dell’eleganza a tutti i costi ha dato vita a una generazione di framework enormi, infinitamente riutilizzabili in teoria, scarsamente utilizzabili in pratica.
Molte delle applicazioni Java su cui si fonda la nostra economia sono basate su architetture monolitiche, che fanno un uso parziale o frettoloso di framework troppo complessi da dominare.
Con tutte le sue promesse iniziali, Java è diventato l’opposto di quello che si proponeva di essere: il nuovo Cobol alla base di migliaia di applicazioni legacy dall’architettura criptica che le future generazioni di programmatori si troveranno a dover mantenere.
Osservando il nuovo mondo, dominato da linguaggi di scripting dinamici, mi sono rassegnato al fatto che è giunto il momento di lasciare il passo alle nuove generazioni.
Epilogo
Nel 2014 Apple ha lanciato Swift, un linguaggio di programmazione a paradigma misto. Questo evento, che ha ricevuto un eco incredibile nel mondo informatico, mi ha fatto tornare alla mente i primi tempi di Java, e con entusiasmo mi sono buttato a imparare a livello hobbistico i fondamenti di questo nuovo eccitante linguaggio e i suoi paradigmi.
Un aspetto centrale di Swift è il rifiuto categorico verso la prigione intellettuale dell’accademia, che vuole inchiodarci a concetti datati come la rigida architettura, le odiose ridondanze sintattiche, e la compatibilità con il passato.
In poco più di due anni, Swift ha costantemente revisionato la sintassi di linguaggio e API, in maniera da mantenerci tutti in costante allerta intellettuale e salvarci dalla vile dipendenza da libri e articoli, che diventano completamente inutili ogni sei mesi.
Con orgoglio abbiamo tagliato i legami con i controversi cicli do-while (sostituiti da repeat-while in Swift 2), con il nero passato dei cicli for(init; condition; increment) (deprecati a partire da Swift 2.2), e con gli odiosi operatori ++ e — (illegali a patire da Swift 3.0).
Contrariamente al rigido approccio orientato agli oggetti di Java, in Swift possiamo liberamente dichiarare classi, strutture, enumerazioni, e tuple. Grazie ai Protocolli (l’equivalente delle interfacce Java), possiamo avere classi e strutture che, pur accomunate dalla stessa interfaccia di programmazione, presentano una semantica completamente diversa quando vengono passate come parametro. Possiamo dichiarare funzioni al di fuori dello scope di una classe, e grazie la costrutto della closure, possiamo dichiarare funzioni che, grazie al fatto di possedere uno stato interno, ritornano un valore differente ad ogni successiva chiamata.
Laddove Java predicava la compattezza e la consistenza del linguaggio, noi Swiftiani preferiamo la possibilità di estendere, modificare, e sovraccaricare le API e gli operatori a nostro piacimento, aggiungendo o sovrascrivendo proprietà e funzioni.
Guardate l’eleganza di queste semplici righe che estendono l’API String, aggiungendo la proprietà “banana” che restituisce, a partire da qualsiasi nome breve, una possibile strofa della canzone “The Name Game” di Shirley Elliss:
extension String {
var banana : String {
let shortName = String(characters.dropFirst(1))
return "(self) (self) Bo B(shortName) Banana Fana Fo F(shortName)"
}
}
let bananaName = "Jimmy".banana
// "Jimmy Jimmy Bo Bimmy Banana Fana Fo Fimmy"
Noi Millennials adoriamo la compattare la sintassi delle nostre istruzioni fino a renderle incomprensibili, e non ci lasciamo spaventare dalle interminabili catene di side effects che queste pratiche provocano.
Al contrario, ci gloriamo pensando all’invidia dei programmatori Java, privati della gioia di potersi vantare di un’infinità di funzioni oscure e di dubbia utilità pratica.
Se potessimo tornare indietro, rifaremmo da capo tutto quello che abbiamo fatto, inclusi errori ed eccessi? Non so voi, ma io rifarei ogni singola cosa. Alla mia età bisogna cercare nuove maniere di mantenere la mente allenata. Ci sono persone che si dedicano a cruciverba Sudoku. Io ho scelto di esplorare un nuovo linguaggio di programmazione, e di immergermi nei suoi paradigmi, con tutte le fissazioni e le idiosincrasie che ne derivano. E a giudicare dalla montagna di appunti che ho già raccolto in questi primi tre anni, sono sicuro che non mancherà materiale per l’articolo che certamente scriverò nel 2036 per festeggiare il ventennale di “Swiftabyte.”