Quattro concetti fondamentali spiegati con un piatto di spaghetti al pomodoro
C'è una domanda che chiunque si avvicini alla programmazione si sente fare prima o poi: "Ma tu come pensi?" Non nel senso filosofico, ovviamente. Nel senso pratico: come trasformi un problema confuso e caotico in qualcosa che una macchina — o qualsiasi altro esecutore — possa risolvere in modo affidabile?
La risposta si chiama pensiero computazionale, e contrariamente a quello che suggerisce il nome, non ha nulla di esclusivamente informatico. È un modo di affrontare i problemi che si applica alla fisica, alla cucina, alla logistica, all'organizzazione di un trasloco. Ovunque ci sia un problema da risolvere in modo sistematico.
Si basa su quattro fondamenti: decomposizione, riconoscimento di pattern, astrazione e algoritmo. In questo articolo li esploriamo tutti e quattro partendo da un problema volutamente semplice — preparare la pasta al pomodoro — per scoprire che anche in una ricetta banale si nasconde una complessità sorprendente.
Caricamento immagine
Il primo passo: scomporre il problema in pezzi gestibili
Caricamento immagine
La decomposizione è il fondamento più intuitivo, ma anche quello in cui è più facile sbagliare per eccesso di ottimismo. L'idea di base è semplice: un problema complesso si affronta molto meglio se lo si spezza in sotto-problemi più piccoli, ciascuno risolvibile quasi indipendentemente dagli altri.
Prendiamo la pasta al pomodoro. A prima vista sembra un problema unitario: "fai la pasta". Ma se provi a scriverlo come una sequenza di istruzioni complete, ti accorgi subito che è in realtà un insieme di problemi distinti che si intrecciano.
Una decomposizione affrettata potrebbe fermarsi a: prepara l'acqua, prepara il sugo, cuoci la pasta, impiatta. Tecnicamente corretto, ma incompleto. Perché manca tutta la fase che precede i fornelli:
- Definire gli ingredienti necessari
- Verificare cosa c'è in dispensa
- Fare la spesa per ciò che manca
- Solo allora cominciare a cucinare
Questo non è un dettaglio trascurabile. In informatica si chiama gestire le dipendenze: prima di eseguire qualsiasi operazione, devi assicurarti che tutte le risorse necessarie siano disponibili. Un programma che apre un file senza prima verificarne l'esistenza è esattamente come uno chef che accende il fornello prima di controllare se ha la pasta.
Ma la decomposizione non finisce qui. Scendendo più in profondità, anche "cuocere la pasta" non è un'azione atomica. Si scompone in:
- Attendere che l'acqua bolla
- Versare la pasta
- Attendere il tempo di cottura
- Verificare se è cotta
- Scolare
Quel quarto punto — verificare se è cotta — è cruciale. E ci porta subito a una domanda interessante: possiamo fondere "attendere il tempo di cottura" e "assaggiare" in un unico passo? La risposta è no, e la ragione è più profonda di quanto sembri.
Sono due cose concettualmente diverse. Attendere è un'attesa passiva, basata su un parametro oggettivo scritto sulla confezione. Verificare è un controllo attivo, basato su un criterio qualitativo. In programmazione corrispondono a due costrutti distinti: un timer e una condizione booleana. Fonderli significherebbe nascondere un ciclo fondamentale: se non è pronta, aspetta ancora un po' e ricontrolla. Tenerli separati rende l'algoritmo più chiaro, più corretto e più facile da modificare.
La regola generale della decomposizione è: fermati quando ogni sotto-problema è abbastanza semplice da essere risolto direttamente dall'esecutore. E qui emerge il punto più sottile di tutto il fondamento: la profondità giusta dipende da chi o cosa deve eseguire le istruzioni. "Soffriggi l'aglio" è un'istruzione atomica per uno chef esperto. Non lo è per un robot da cucina, che ha bisogno di sapere temperatura, durata, quantità di olio, intensità del fuoco.
Il timeout e il problema dell'attesa infinita
Caricamento immagine
Prima di passare ai pattern, vale la pena soffermarsi su una domanda che emerge quasi naturalmente dalla decomposizione: cosa succede se un'attesa non finisce mai?
Immagina questo scenario: stai aspettando che l'acqua bolla, ma nel frattempo cade la corrente. Il fornello a induzione si spegne. L'acqua non raggiungerà mai i 100°. E tu aspetti, aspetti, aspetti... per sempre.
In informatica questa situazione rappresenta un'attesa indefinita (indefinite wait), che si manifesta all'esterno come un hang. Per evitare che il sistema resti bloccato per sempre, si introduce un timeout. Se l'evento non si verifica entro un tempo ragionevole, si smette di aspettare e si gestisce l'errore.
Il pattern completo non è "Attesa + Verifica", ma "Attesa + Verifica + Timeout":
- Definisci un tempo massimo accettabile
- Attendi
- Verifica se la condizione è soddisfatta
- Se sì, prosegui
- Se no e il tempo non è scaduto, torna al punto 2
- Se il timeout è scaduto, gestisci l'errore
Ma come si gestisce l'errore? Le strategie sono diverse e ognuna ha il suo contesto:
- Retry: riprova automaticamente. Torni la corrente? Riprendi il riscaldamento. È la stessa logica delle chiamate HTTP che si ritentano automaticamente in caso di fallimento.
- Fallback: usa un'alternativa. Se non va la corrente passi al fornello a gas (o viceversa). In informatica significa avere un piano B: se un componente fallisce, il sistema prova una strada alternativa per continuare a funzionare.
- Fail fast: interrompi tutto e notifica. Spegni, avvisa, aspetta istruzioni. Preferibile quando continuare sarebbe pericoloso o privo di senso.
- Escalation: passa il problema a qualcuno di più competente. Chiama il tecnico. In programmazione significa propagare l'eccezione al chiamante con
throw.
I sistemi robusti dedicano spesso più codice alla gestione degli errori che alla logica principale. Non è un'inefficienza: è maturità progettuale.
Riconoscere i pattern: quando i problemi si assomigliano
Caricamento immagine
Una volta completata la decomposizione, il secondo fondamento entra in gioco in modo quasi automatico: cominci a notare che certi sotto-problemi hanno la stessa struttura. Questo è il riconoscimento di pattern.
Nella nostra ricetta emergono almeno sei pattern distinti:
- Il pattern Attesa + Verifica (+ Timeout): aspetta che l'acqua bolla, poi controlla; aspetta il tempo di cottura, poi assaggia. In informatica questo schema è ovunque: si attende un evento, si verifica se la condizione desiderata è stata raggiunta e, se l'attesa diventa eccessiva, interviene un timeout per evitare di restare bloccati all'infinito. È il meccanismo alla base dell'attesa di un file, del completamento di una compilazione o della risposta di un server remoto.
import requests
import time
start = time.time()
while True:
response = requests.get("https://api.example.com/status")
if response.json()["ready"]:
print("Elaborazione completata")
break
if time.time() - start > 60:
raise TimeoutError("Server troppo lento")
time.sleep(2)
- Il pattern "Verifica disponibilità prima di agire" appare tre volte: verifichi gli ingredienti prima di cucinare, verifichi che l'acqua bolla prima di buttare la pasta, verifichi che la pasta sia cotta prima di scolarla. In programmazione è il pattern della precondizione. Ecco un esempio in Python:
if acqua_bolle:
butta_pasta()
- Il pattern "Preparazione → Utilizzo → Chiusura". Prima prepari le risorse necessarie, poi le usi, infine le rilasci o sistemi tutto. In questo modo il sistema torna a uno stato noto, pronto per una nuova esecuzione. È lo stesso schema che trovi quando apri un file, usi una connessione al database o scrivi un test automatico. Ad esempio in Python per leggere un file:
def leggi_file(nome):
# Setup
f = open(nome)
# Process
contenuto = f.read()
# Teardown
f.close()
return contenuto
- Il pattern "Lista della spesa" — verificare tutte le risorse prima di iniziare — corrisponde in informatica al controllo delle dipendenze e alla validazione degli input. La verifica avviene all'inizio del processo, prima ancora di partire. Qui stai verificando la fattibilità complessiva. Ad esempio in Python prima di avviare una applicazione:
verifica_configurazione()
verifica_database()
verifica_chiavi_api()
avvia_applicazione()
- Il pattern delle dipendenze rigide rivela che certi passi non possono essere invertiti. Non puoi scolare prima che la pasta sia cotta, non puoi buttare la pasta prima che l'acqua bolla. In informatica queste dipendenze formano un grafo di prerequisiti, e l'ordine corretto delle operazioni si ottiene tramite un ordinamento topologico. In Python avremo ad esempio questo preciso ordine di chiamate a funzioni:
prepara_acqua()
cuoci_pasta()
scola()
Questo pattern si differenzia dal pattern 2 della precondizione in cui si esegue un controllo locale prima di eseguire un'azione. Nel pattern delle dipendenze rigide il focus è sulla struttura globale delle dipendenze tra azioni. Questo pattern è più "architetturale", il primo più "operativo". Una buona analogia è:
Le precondizioni sono i semafori agli incroci. L'ordinamento topologico è la mappa stradale dell'intero percorso. Puoi avere una strada correttamente progettata (dipendenze), ma devi comunque controllare il semaforo prima di attraversare (precondizione).
- Infine, il pattern della parallelizzazione: mentre aspetti che l'acqua bolla, puoi preparare il sugo. Sono task indipendenti eseguibili contemporaneamente. In programmazione: thread, processi asincroni,
Promise.all(). Riconoscere questo pattern può fare la differenza tra un programma che impiega 30 secondi e uno che ne impiega 10. In Python, un esempio semplice è scaricare due file da Internet.
Versione sequenziale
download_file("file1.zip")
download_file("file2.zip")
Output:
Scarico file1.zip
... 10 secondi ...
file1.zip pronto
Scarico file2.zip
... 10 secondi ...
file2.zip pronto
Tempo totale:
10s + 10s = 20s
Versione parallela (asyncio)
import asyncio
async def download_file(nome):
print(f"Scarico {nome}")
await asyncio.sleep(10) # simula download
print(f"{nome} pronto")
async def main():
await asyncio.gather(
download_file("file1.zip"),
download_file("file2.zip")
)
asyncio.run(main())
Output:
Scarico file1.zip
Scarico file2.zip
... 10 secondi ...
file1.zip pronto
file2.zip pronto
Tempo totale:
circa 10 secondi
Il valore del riconoscimento dei pattern è esattamente questo: una volta identificata la struttura ricorrente, puoi riutilizzare soluzioni già collaudate invece di reinventarle ogni volta. È la base dei design pattern in ingegneria del software.
L'astrazione: l'arte di ignorare ciò che non serve
Caricamento immagine
Il terzo fondamento è il più sottile e, per molti versi, il più potente. Astrarre significa concentrarsi solo sulle informazioni rilevanti per il livello a cui stai lavorando, nascondendo tutto il resto.
Torna alla pasta al pomodoro, e nota quante astrazioni usi senza nemmeno accorgertene.
Quando dici "cuoci in acqua bollente" stai ignorando la composizione chimica dell'acqua, la fisica del trasferimento di calore, la struttura molecolare della pasta. Ti interessa solo: acqua a 100°, pasta dentro, aspetta N minuti. Hai nascosto tutta la fisica sottostante. In informatica è la stessa cosa che succede quando usi array.sort(): non ti interessa se usa quicksort o mergesort, usi l'interfaccia e ignori l'implementazione.
Quando dici "pasta" stai trattando come equivalenti spaghetti, rigatoni, fusilli e penne — oggetti con forme diverse, tempi di cottura diversi, grammature diverse — perché condividono le proprietà rilevanti per il tuo problema: si cuociono in acqua bollente e si scolano. In informatica è il concetto di classe o interfaccia: oggetti diversi che condividono un comportamento comune.
Quando nella fase di mantecatura dici "unisci pasta e sugo", non ti interessa se il sugo è fresco o in barattolo, piccante o no, con aglio o cipolla. Quella fase tratta il sugo come una scatola nera con una sola proprietà rilevante: è pronto. In informatica si chiama information hiding.
E poi c'è l'astrazione più grande di tutte: la ricetta stessa. Una ricetta non descrive quanto sei stanco mentre cucini, il rumore di fondo in cucina, la marca della pentola. Estrae solo le informazioni necessarie e sufficienti per replicare il risultato. È esattamente la definizione di algoritmo — una descrizione astratta di un processo, indipendente dall'hardware che lo eseguirà.
Il filo conduttore dell'astrazione è sempre lo stesso: nascondere ciò che non è rilevante al livello corrente, esporre solo ciò che serve per ragionare a quel livello. Ogni strato della decomposizione che abbiamo costruito era già un atto di astrazione: scegliere cosa includere e cosa ignorare.
L'algoritmo: quando tutto si mette insieme
Caricamento immagine
Arriviamo al quarto fondamento, quello che dà nome alla disciplina. Un algoritmo è una sequenza di passi finita, non ambigua, ordinata ed efficace per risolvere un problema. Non è ancora codice — è la logica formale che il codice poi tradurrà.
Per un esecutore umano, come nel caso della nostra ricetta, l'algoritmo si esprime in linguaggio naturale o pseudocodice informale. Non è necessario scrivere una riga di JavaScript o Python. Il linguaggio di rappresentazione cambia a seconda di chi deve eseguire le istruzioni; la logica sottostante rimane la stessa.
L'algoritmo completo della pasta al pomodoro mette in fila tutto ciò che abbiamo analizzato:
Precondizioni: lista ingredienti, strumenti disponibili, tempo stimato, timeout definiti.
Fase 1 — Verifica risorse: per ogni ingrediente, se non disponibile aggiungi alla lista della spesa. Se la lista non è vuota, vai a fare la spesa prima di procedere.
Fase 2 — Esecuzione parallela: avvia contemporaneamente il processo dell'acqua e quello del sugo. Entrambi hanno i propri timeout e le proprie condizioni di uscita.
Fase 3 — Cottura con ciclo: versa la pasta, avvia il timer, entra nel ciclo di verifica. Ogni minuto controlla: pronta? Esci. Non pronta ma entro il timeout? Ricontrolla. Oltre il timeout? Errore.
Fase 4 — Conclusione: scola, manteca, impiatta, servi.
Gestione errori: per ogni punto critico, una strategia definita.
Quello che emerge guardando l'algoritmo completo è qualcosa di bello: i quattro fondamenti non sono concetti separati ma facce dello stesso prisma. La decomposizione ci ha dato i passi. Il riconoscimento dei pattern ha suggerito cicli, timeout e parallelismo. L'astrazione ha permesso di ragionare per livelli senza perdersi nei dettagli. L'algoritmo è la sintesi formale di tutto questo lavoro.
Il codice viene dopo, non prima
C'è un'ultima considerazione che vale la pena fissare, e riguarda il rapporto tra pensiero computazionale e programmazione.
In questo percorso non abbiamo scritto una riga di codice. Non ne avevamo bisogno, perché il nostro esecutore era un umano. Se invece avessimo affrontato un problema diverso — ordinare gli elementi di un array, per esempio — il codice sarebbe entrato in scena come passo finale: la traduzione formale in un linguaggio che la macchina capisce.
Questo ordine non è casuale. Il pensiero computazionale precede sempre la scrittura del codice. Chi apre l'editor prima di aver chiarito la logica sta costruendo su fondamenta di sabbia. Il codice è solo il passo terminale di un ragionamento che dovrebbe essere già solido prima di toccare la tastiera.
È una lezione che i programmatori esperti imparano spesso a proprie spese, e che i principianti faticano ad accettare perché sembra controintuitiva: per scrivere meno codice — e scriverlo meglio — bisogna pensare di più prima di scriverne.
Quello che la pasta ci ha insegnato
Siamo partiti da un piatto di spaghetti e abbiamo attraversato dipendenze, timeout, gestione degli errori, parallelismo e astrazione. Non è un percorso strano: è esattamente il punto.
Il pensiero computazionale non è una skill riservata ai programmatori. È un modo di guardare i problemi che chiunque può sviluppare, e che si affina proprio partendo dagli esempi più familiari. Più il problema di partenza è concreto e quotidiano, più i concetti astratti diventano comprensibili e memorabili.
La prossima volta che decomporrai un problema — di lavoro, di vita, di codice — ricordati dell'acqua che non bolle perché hanno tolto la corrente. E ricordati di definire il timeout prima di iniziare ad aspettare.
Caricamento immagine


