Paradigmi di programmazione
Nella lezione su pensiero induttivo e deduttivo abbiamo visto come la programmazione consiste in due processi distinti ma collegati:
- un processo induttivo: a partire da un insieme di requisiti il programmatore/progettista crea un modello della realtà di riferimento del problema da risolvere. Questa parte viene chiamata anche analisi.
- un processo deduttivo: a partire dal modello creato, il programmatore realizza una applicazione funzionante per un sistema informatico facendo uso di tecniche di programmazione, tecnologie, linguaggi e strumenti di sviluppo. Questa parte viene chiamata sviluppo.
In qualsiasi progetto di complessità non banale occorre sempre una analisi preliminare per produrre un modello di realtà, prima di cominciare con lo sviluppo vero e proprio.
La programmazione quindi è una attività creativa e non una operazione meccanica, in quanto ogni singolo problema richiede una progettazione/programmazione specifica.
Questo tuttavia non significa che ogni volta che si crea un nuovo programma si parte da zero. Nel programmare si accumula esperienza, si fa tesoro delle buone pratiche, si acquisiscono metodologie, tecniche e processi di sviluppo.
Nel corso della storia dell’informatica si sono accumulate esperienze, e da queste gli studiosi di informatica hanno cercato di generalizzare le migliori metodologie di analisi e sviluppo, fino a sviluppare una intera disciplina, chiamata ingegneria del software1 con lo scopo di migliorare la qualità del software.
Tra queste attività la più importante è lo studio dei cosiddetti “paradigmi di programmazione”.
Ma cos’è un paradigma di programmazione?
Un paradigma di programmazione è un insieme di principi e regole che guidano sia il processo di analisi che quello di sviluppo. Il paradigma guida il modo in cui concepiamo il problema, il modello di realtà che andremo a creare, e infine determina la struttura del codice del programma realizzato. Dietro ad uno specifico paradigma c’è una intera visione di cosa si intende per fare software, come deve funzionare, ed è influenzato da molti aspetti, anche tecnologici ma non solo.
Ogni linguaggio di programmazione viene creato sulla base di uno o più paradigmi di programmazione.
Chi infatti progetta un linguaggio di programmazione lo fa sulla base di un paradigma di programmazione in modo da agevolare lo sviluppo di applicazioni. La scelta del linguaggio implica quindi l’adozione proprio di uno specifico paradigma, in quanto il linguaggio ne dipende fortemente.
Prima di passare a vedere i principali paradigmi, è bene capire che I paradigmi di programmazione non sono qualcosa di eterno ed immutabile. Sono nati e cresciuti nel tempo, come evoluzione o messa in discussione di paradigmi precedenti, e sono il frutto di una visione culturale, dell’esperienza accumulata e dalle tecnologie che si sono evolute nel tempo. Pertanto vanno visti in ottica evolutiva e mai come qualcosa di definitivo: anche oggi sono costantemente oggetto di revisione, evoluzione e modifica.
Vediamo ora come si sono evoluti nel tempo.
Programmazione procedurale
Coi primi computer, ed in particolare con la macchina di Von Neumann (1947), i primi linguaggi macchina si basavano su un paradigma di programmazione per cui un algoritmo ha solo 3 istruzioni fondamentali:
- di assegnazione/calcolo
- salto condizionato (if)
- salto incondizionato (goto)
In questo paradigma inoltre i dati sono codificati come numeri binari (anche lettere, stringhe, numeri, ecc.). Il computer era concepito infatti come una evoluzione della calcolatrice ed il programma era fatto per svolgere una procedura di elaborazione, con un input, uno stato iniziale, un output ed uno stato finale. Non era prevista interattività. Erano già possibili i sottoprogrammi tramite l’utilizzo dello stack pointer ed apposite istruzioni di salto.
L’attività di analisi del programmatore quindi era quella di codificare il problema in un insieme di dati ed istruzioni molto semplici, tramite un grande lavoro di astrazione da parte del programmatore, in genere un ingegnere o matematico esperto.
Questo modello è stato poi implementato in linguaggi come Fortran ed Ada, che permettevano di avere istruzioni più complesse e Basic, un linguaggio che cercava di semplificare al massimo la programmazione.
Programmazione strutturata
A partire dagli anni 60 il paradigma procedurale venne messo in discussione sia per l’evoluzione tecnologica, che permetteva già una certa interattività, sia per la sua eccessiva astrazione nella predisposizione dei dati, sia infine e soprattutto per il problema del goto.
Infatti col teorema di Bohm-Jacopini (1966) viene infatti dimostrato che in realtà qualunque algoritmo può essere implementato tramite queste tre istruzioni fondamentali:
- assegnazione/calcolo
- condizione
- ciclo
Come si può vedere il goto è una istruzione non necessaria. Dijkstra (nel 1968) dimostrò poi che il goto non solo non era necessario, ma era proprio dannoso perché poteva permettere salti incondizionati in parti diverse del programma producendo quello che venne chiamato “spaghetti code”.
Qui un esempio di Spaghetti Code (calcolo del Pi greco) scritto in linguaggio Basic.
10 LET PI = 0
20 LET I = 0
30 LET FLAG = 1
40 LET MAX = 1000000
50 IF FLAG = 1 THEN GOTO 70
60 GOTO 90
70 LET PI = PI + 4 / (2 * I + 1)
80 GOTO 100
90 LET PI = PI - 4 / (2 * I + 1)
100 LET I = I + 1
110 IF FLAG = 1 THEN LET FLAG = 0 ELSE LET FLAG = 1
120 IF I < MAX THEN GOTO 50
130 PRINT "Valore approssimato di PI: "; PI
140 END
Come si vede il goto infatti può essere usato in modo indiscriminato per saltare da un punto qualsiasi del programma ad un altro. Questo comporta una eccessiva libertà al programmatore e quindi errori in esecuzione difficili da individuare.
Il paradigma che viene quindi proposto è la programmazione strutturata, ed è alla base di linguaggi come il Pascal o il C2 mentre altri (come Ada) si sono adattati al nuovo paradigma. Qui un esempio dello stesso codice in C/C++.
#include <iostream>
using namespace std;
double calcolaPi(int termini) {
double pi = 0.0;
for (int i = 0; i < termini; ++i) {
double termine = 4.0 / (2 * i + 1);
if (i % 2 == 0) {
pi += termine;
} else {
pi -= termine;
}
}
return pi;
}
int main() {
int n:
cout << "Inserisci il numero di iterazioni: ";
cin >> n;
double pi = calcolaPi(n);
cout << << pi << endl;
return 0;
}
La programmazione strutturata introduce al posto del sottoprogramma il concetto di funzione e inoltre permette di creare strutture dati più complesse, come array e struct (in C ed altri linguaggi), aumentando il livello di astrazione e semplificando il modello concettuale del programma.
Con la programmazione strutturata viene introdotto il concetto di diagramma di flusso, uno strumento che permette di modellizzare algoritmi in modo visuale e rende più semplice la sua codifica in un programma.

La programmazione strutturata è uno dei paradigmi fondamentali di programmazione e come tale è usato ancora oggi per lo sviluppo di software, soprattutto in progetti semplici. E’ supportato da quasi tutti i linguaggi di programmazione ed è la prima tecnica di programmazione che impara un programmatore.
Programmazione ad oggetti
L’informatica però a partire dagli anni ’70-’80 si evolve ulteriormente: compaiono le prime interfacce grafiche, cominciano ad essere sviluppati programmi molto complessi, spesso interattivi, che gestiscono grandi basi di dati formate da molte strutture dati collegate tra loro, compaiono i primi videogiochi e le prime applicazioni client-server, specie su Internet. La programmazione strutturata comincia ad evidenziare grossi limiti nel gestire applicazioni interattive, complesse e con database relazionali.
La prima soluzione adottata è la programmazione modulare, che consiste nel suddividere un grosso programma in moduli, porzioni di programma autonome che svolgono un insieme di funzioni specifiche e che sono riutilizzabili anche in futuri sviluppi di altri programmi. I principali linguaggi di programmazione (C, Cobol, Pascal, ecc.) vengono quindi adattati alla programmazione modulare, altri vengono inventati appositamente (Modula-2).
Il vero salto evolutivo lo si ha però con la programmazione orientata ad oggetti (OOP), che introduce un nuovo modo di progettare le applicazioni. Nella programmazione strutturata le strutture dati sono memorizzate in variabili e le funzioni sono elementi di programma che le gestiscono. Nella programmazione ad oggetti invece dati e funzioni vengono incapsulati in un solo “oggetto” che contiene sia i dati che le funzioni per utilizzarli. I dati sono chiamati “proprietà” dell’oggetto, e le funzioni sono dette “metodi“.
La programmazione ad oggetti prevede quindi una rete di oggetti che rappresentano ognuno una porzione del problema, del modello e del programma.
L’analisi quindi consiste nel creare una modello concettuale dove la realtà del problema è definita tramite una rete (grafo) di oggetti che interagiscono tra loro, tramite relazioni di utilizzo, ereditarietà, estensione, ecc. Questo modello è molto efficace perché rende molto più semplice progettare applicazioni grafiche ed interattive, modellizzare strutture dati composte da entità distinte tra loro, e creare strutture di oggetti riusabili in applicazioni differenti.

Per la modellizzazione è introdotto un vero e proprio linguaggio grafico, UML, che come si vede dalla figura permette di modellizzare schematicamente oggetti ma anche il comportamento dinamico delle applicazioni.
Con la programmazione ad oggetti sono stati introdotti nuovi linguaggi, come C++, Java, C# e altri successivi. Ma è in particolare Java che storicamente ha costituito il più importante e diffuso linguaggio ad oggetti. Altri linguaggi hanno preferito avere un approccio multiparadigma, come Javascript, Python, Kotlin, Swift dove la programmazione ad oggetti è possibile ma opzionale.
Oggi la maggior parte dei linguaggi prevede una qualche forma di programmazione ad oggetti e questo paradigma ad oggi resta il più diffuso.
Programmazione funzionale
La programmazione ad oggetti è efficace per risolvere molti problemi ma non è l’unica evoluzione della programmazione strutturata. Un modello alternativo si concentra anzichè su una modellizzazione per oggetti, su flussi di dati e funzioni di trasformazione, a partire da un insieme di input che tramite un insieme di funzioni collegate tra loro produce il risultato di una elaborazione. Questo modello viene chiamato programmazione funzionale.
In questo modello il programma è costituito da una funzione, una sequenza di funzioni, o una combinazione di funzioni dove ciascuna svolge (parzialmente) una porzione dell’algoritmo di calcolo e che restituisce alla funzione successiva o dei dati o una funzione che elaborerà quei dati. Il programma al termine della sua elaborazione svolge tutte le operazioni definite dalle funzioni ed infine genera un risultato di output.
Qui un semplice esempio col calcolo del Pi greco:
const n = 10000;
const value = Array
.from({length: n})
.map((_, i) => {
return (i % 2 === 0 ? 1 : -1) * (4 / (2 * i + 1))
})
.reduce((sum, element) => sum+=element, 0);
La funzione from genera un array di dimensione “length”, che viene passato alla funzione successiva, map, che a sua volta inserisce nell’array per ogni elemento alla posizione i il valore calcolato. Come si può vedere non esiste alcun ciclo for, ci pensa la funzione ad eseguire l’operazione su ogni elemento dell’array e a produrre un nuovo array, sempre di n elementi alla funzione successiva. Infine c’è l’istruzione reduce, che esegue internamente un ciclo su ogni elemento e lo somma alla variabile sum.
Come si può vedere in programmazione funzionale i cicli e condizioni sono nascosti o eliminati e si lavora direttamente su strutture dati complesse anziché su singoli elementi. Inoltre le funzioni sono in sequenza e possono produrre dati o altre funzioni per una loro esecuzione posticipata in base all’algoritmo di calcolo. E’ importante osservare che in ogni caso le funzioni non memorizzano nulla internamente ma sono pensare per restituire i dati ad una funzione successiva ed infine al chiamante.

Questo meccanismo, detto pipeline, permette di comporre una operazione complessa come un insieme di operazioni più semplici. Allo stesso tempo però è bene osservare che le singole operazioni non sono composte da istruzioni o comandi che scompongono esplicitamente una struttura dati (ad esempio un array) ma agiscono in modo implicito su tutto l’array. E’ il linguaggio di programmazione che si occupa di eseguire internamente i cicli, creare condizioni, assegnare variabili locali, ecc. La programmazione funzionale si basa sul fatto che anziché fare più azioni all’interno dello stesso ciclo, rendendolo complesso in termini computazionali, sia più utile fare più fasi con una singola operazione (e quindi svolgere più cicli in sequenza). Come abbiamo visto con l’esempio sopra indicato vengono svolti 3 cicli più semplici (creazione, generazione e somma) anzichè un unico grande ciclo complesso dove ad ogni iterazione si svolgono le 3 operazioni.
Questa metodologia di programmazione semplifica l’analisi e la modellizzazione del problema e la sua traduzione in codice, specie per le applicazioni di calcolo (intelligenza artificiale, videogiochi) ed interattive (web e mobile).3
La programmazione funzionale è stata implementata sia in linguaggi puramente funzionali (come Eiffel, Haskell, Scala) ed è restata per decenni un modello di nicchia. Ha trovato invece successo negli ultimi anni a causa dello sviluppo dei Big Data prima e della programmazione web-mobile dopo. Per questo tutte le ultime versioni dei linguaggi ad oggetti hanno introdotto caratteristiche funzionali come in Java (versione 8) e C# (con LINQ), Python, Javascript, Kotlin, Swidft.
Conclusioni
I paradigmi di programmazione rappresentano come visto evoluzioni per certi versi alternative del modo in cui si progetta e si sviluppa una applicazione. In particolare se la programmazione ad oggetti è in effetti una evoluzione del paradigma della programmazione strutturata, la programmazione funzionale è un modo alternativo di scrivere gli algoritmi. Oggi il paradigma funzionale tende ad essere usato oltre che nel calcolo anche nelle applicazioni interattive tramite una sua evoluzione, la programmazione reattiva, che estende questo paradigma ovunque ci sia bisogno di elaborare dati a seguito di azioni dell’utente, in particolare nelle applicazioni web e mobile.
I paradigmi di programmazione sono in evoluzione, come detto, e quindi ci dobbiamo aspettare nei prossimi anni e decenni nuovi tipi di paradigma che vanno ad evolvere (o discutere) quelli esistenti, anche in funzione delle nuove tecnologie e dei nuovi tipi di applicazioni che si andranno a realizzare in futuro.
- Nel corso degli ultimi decenni l’insieme di queste conoscenze sono diventate una vera e propria disciplina, chiamata ingegneria del software. ↩︎
- in realtà il C consente l’uso di goto, anche se deprecato. ↩︎
- La programmazione funzionale si adatta poi particolarmente al calcolo parallelo. Infatti ogni fase dell’elaborazione può essere demandata (in architetture multicore come i modern pc e smartphone) a processori differenti nella stessa elaborazione e quindi ottimizzare al massimo le prestazioni di un sistema direttamente a runtime, senza una progettazione preliminare da parte del programmatore. ↩︎