tl;dr
Il linguaggio naturale, il pseudocodice e il codice non sono strumenti in competizione. Sono linguaggi diversi per livelli diversi di precisione.
Nell’era della programmazione assistita dall'intelligenza artificiale, imparare a programmare non è solo questione di scrivere ogni riga di codice a mano. È anche questione di sapere quando un'idea deve diventare più precisa, come controllare quella traduzione e come distinguere le decisioni prese da una persona da quelle integrate da un sistema di IA.
Una citazione da Richard Feynman
Per capire perché la programmazione non scompare semplicemente con l'IA, aiuta iniziare con una distinzione più generale: la differenza tra chiarezza e precisione.
Richard Feynman ha spiegato questa idea parlando di matematica:
Il problema reale nella comunicazione orale non è il linguaggio preciso. Il problema è il linguaggio chiaro. Il desiderio è di far comprendere l'idea chiaramente all'altra persona. È necessario essere preciso solo quando c'è dubbio sull'significato di una frase, e allora la precisione va posta esattamente dove c'è il dubbio.
È veramente impossibile dire qualsiasi cosa con precisione assoluta, a meno che quel concetto non sia così astratto dal mondo reale da non rappresentare alcun oggetto reale. La matematica pura è un'astrazione dal mondo reale, e la matematica pura ha un linguaggio speciale e preciso per trattare i suoi argomenti speciali e tecnici.
Ma questa lingua precisa non è precisa in alcun senso se si tratta di oggetti reali del mondo, ed è solo pedante e molto confuso usare questa lingua a meno che non ci siano alcune sottigliezze speciali che devono essere distinguite con cura.
L'interessante parted è che Feynman non critica la matematica. Critica l'idea che più formalismo significa più comprensione.
Il suo punto è semplice:
- la chiarezza è più importante della precisione;
- la precisione dovrebbe essere introdotta solo dove esiste un'ambiguità reale;
- la matematica pura può essere perfettamente precisa poiché si basa su astrazioni definite da sé stessa;
- quando parliamo del mondo reale, ci sono sempre approssimazioni, assunzioni e definizioni imperfette.
Sebbene Feynman stesse parlando di matematica, questa distinzioned è utile per pensare anche al programmazione. Non perché il programmazione è lo stesso della matematica pura, ma perché il programmazione introduce una precisione formale dove la lingua naturaled è troppo ambigua.
L'idea centrale di questo articolo è questa:
Imparare a programmared è ancora importante perché il problema difficile non è solo produrre codice. Il problema difficiled è controllare la traduzione tra un'intenzione umana ambigua e un sistema formale che esegue istruzioni senza senso comune.
La lingua delle persone e la lingua dei computer
Il programmazione è un modo di comunicare un procedimento. È un modo di dire a una macchina ciò che si vuole che faccia.
Una CPU, o unità di elaborazione centrale, non capisce l'inglese, lo spagnolo, Python o C++ come una persona. A livello più basso, esegue istruzioni molto semplici: leggere numeri, confrontarli, aggiungerli, copiare dati da un posto in memoria a un altro, e saltare da un'istruzione all'altra.
I linguaggi di programmazione sono uno strato molto più amichevole per descrivere procedimenti che saranno poi interpretati o compilati in istruzioni che la macchina può eseguire.
In una lingua naturale, una frase come:
Ordina questo mazzo di carte.
lascia molte cose aperte all'interpretazione.
Presuppone che la persona che riceve l'istruzione sappia cosa sia una carta, cosa significhi ordinare, quale criterio di ordinamento utilizzare, quali carte sono più alte delle altre, se le figure importano, e quanto tempo o sforzo è accettabile.
Nella programmazione, quelle decisioni devono essere definite da qualcuno in qualche punto del sistema.
Non scriviamo sempre ogni dettaglio a mano, perché utilizziamo linguaggi di programmazione, librerie, framework e astrazioni che già incapsulano molte decisioni. Ma qualcuno, a un certo livello, aveva comunque definito quelle decisioni in modo preciso.
Il linguaggio naturale e i linguaggi di programmazione non sono versioni migliori o peggiori della stessa cosa. Sono strumenti diversi. Chiedersi se l'IA significhi che non dobbiamo più imparare a programmared è un po' come chiedersi se, perché sappiamo come usare una lente d'ingrandimento, non dobbiamo più capire a che serve un microscopio.
Come viene creato il software in un’azienda di software
In una società di software, quasi tutto inizia con il linguaggio naturale.
Un cliente spiega cosa vuole:
Ho bisogno di un'app per gestire gli ordini.
O:
Ho bisogno di un sito web per vendere prodotti.
O:
Ho bisogno di un sistema per gestire gli appuntamenti.
Quella prima descrizioned è raramente precisa. Non perché il cliente si esprime male, ma perché sta cercando di comprimere una grande quantità di conoscenza del dominio in poche frasi.
Quella conoscenza potrebbe includere abitudini, eccezioni, regole aziendali, preferenze, casi estremi, problemi passati e decisioni implicite.
È per questo che esistono strati intermedi.
Un product manager, un analista aziendale o un analista funzionale parla con il cliente, fa domande, individua ambiguità e trasforma la richiesta iniziale in requisiti più chiari.
Poi un tech lead, un architetto o un programmatore traduce quelle richieste in decisioni tecniche sempre più specifiche, fino a che non si arriva al codice.
Ogni livello può scoprire problemi o proporre cambiamenti. Ma nessun strato dovrebbe silenziosamente sovrascrivere una decisione fatta a un livello superiore.
Se un programmatore trova una contraddizione, deve sollevarla. Se un product manager riformula una richiesta, quella riformulazione dovrebbe rimanere tracciabile alla richiesta iniziale del cliente.
Tre modi per tradurre un’idea in codice
Il diagramma seguente confronta tre flussi di lavoro: lo sviluppo software tradizionale, la generazione diretta di codice con un LLM e un flusso proposto con un livello intermedio esplicito.
Processo tradizionale
LLM senza metodologia
Black Box Approach
Cosa succede quando una persona usa un LLM per generare codice?
Quando una persona interagisce direttamente con un LLM per generare codice, il livello intermedio non scompare del tutto. Diventa implicito.
Il modello interpreta il prompt, integra i dettagli mancanti, assegna priorità alle istruzioni e prende decisioni implementative. Ma molte di queste decisioni non vengono registrate in un luogo stabile.
Per questo molti strumenti di programmazione con IA hanno qualcosa di simile a una modalità di pianificazione. L’idea è chiedere al modello di riflettere prima di implementare.
Il problema è che il piano è spesso temporaneo. Può rimanere intrappolato in una conversazione, scollegato dal codice finale o ignorato dall’utente.
Quando l’utente è un programmatore, spesso dedica molto tempo a scrivere specifiche, documenti dei requisiti, note architetturali e file di contesto per guidare l’LLM. Ma questo crea un altro problema: decine di file sparsi, ciascuno con livelli diversi di autorità, priorità e aggiornamento.
Anche quando le specifiche sono scritte con cura, il problema non scompare.
L’LLM può incontrare contraddizioni, colmare lacune o prendere decisioni che non corrispondono esattamente ai requisiti. A volte succede perché il linguaggio naturale è ambiguo. A volte dipende dall’ordine in cui il modello legge i file. A volte le istruzioni sono in conflitto. E a volte succede perché gli LLM sono sistemi probabilistici.
Il problema non è che l’LLM prenda decisioni.
Il problema è non sapere quali decisioni abbia preso, perché le abbia prese o se contraddicano qualcosa che una persona aveva già deciso.
Pseudocodice come livello intermedio
Il pseudocodice può svolgere il ruolo che, nello sviluppo software tradizionale, è spesso distribuito tra le conversazioni di clienti, responsabili, architetti e programmatori.
Non è puro linguaggio naturale, perché ha una struttura.
Ma non è nemmeno codice eseguibile, perché permette ancora di parlare in termini di intenti, moduli e responsabilità.
Questo livello intermedio è particolarmente utile con gli LLM perché separa due momenti che spesso vengono fusi troppo in fretta:
- decidere cosa deve fare il sistema;
- decidere come implementarlo.
Per esempio:
IF user selects a subscription plan
CALL create_checkout_session
IF payment succeeds
CALL activate_subscription
CALL send_confirmation_email
ELSE
CALL show_payment_error
Questo pseudocodice non decide ancora se useremo Stripe, Paddle, Mercado Pago, un’API personalizzata o una libreria specifica.
Ma definisce già una struttura:
- c’è un piano;
- c’è una sessione di pagamento;
- c’è un passaggio di attivazione dell’abbonamento;
- c’è un percorso per il pagamento riuscito;
- c’è un percorso per gli errori.
È già molto più preciso di una richiesta vaga come:
Aggiungi i pagamenti all’app.
Ma non è ancora rigido quanto il codice dell’implementazione finale.
La proposta: Black Box Approach
La metodologia proposta è semplice:
Tratta ogni parte del sistema come un blocco opaco affinabile, con confini chiari, pseudocodice intermedio e provenienza esplicita.
Ogni progetto di app riceve una cartella blackboxes/ contenente:
- blocchi YAML;
- strutture dati condivise;
- un file di istruzioni che l’LLM deve seguire quando viene richiamato esplicitamente;
- un registro della provenienza;
- facoltativamente, un visualizzatore che mostra l’albero dei blocchi.
L’obiettivo non è sostituire il codice.
L’obiettivo è rendere la traduzione dall’intenzione umana al codice visibile, modificabile e verificabile.
Cosa cerca di risolvere
1. Decisioni umane e decisioni dell’IA
Ogni blocco e ogni campo dati tiene traccia della provenienza:
human_definedai_definedmixed
Una volta che una persona conferma qualcosa, l’IA non può trasformarlo silenziosamente di nuovo in un’ipotesi.
Per esempio, se l’LLM decide di usare Stripe, inizialmente la decisione può essere contrassegnata come ai_defined.
Se una persona conferma quella decisione, diventa approvata da un essere umano. Da quel momento il modello non dovrebbe sostituire Stripe con un altro fornitore senza segnalare esplicitamente il cambiamento.
2. Pseudocodice come livello intermedio
I blocchi usano pseudocodice ibrido:
CALL create_checkout_session
IF payment succeeds
WHILE there are pending invoices
Questo fornisce abbastanza struttura per scomporre un sistema, senza sommergerci di dettagli implementativi prima che siamo pronti.
3. Confini modulari
Il sistema viene affinato un blocco opaco alla volta.
I blocchi antenati forniscono un contesto di sola lettura. Solo il sottoalbero selezionato può cambiare.
Questo conta perché una richiesta come:
Correggi il flusso di pagamento.
non dovrebbe permettere all’LLM di riscrivere il livello di autenticazione, lo schema del database e il sistema di notifiche, a meno che quei cambiamenti non siano esplicitamente collegati e approvati.
4. Una vista leggibile dalle persone
Un visualizzatore può mostrare l’albero dei blocchi in modo molto più semplice di decine di file di specifiche sparsi.
Il punto non è soltanto documentare. Il punto è preservare i confini delle decisioni.
Un esempio minimo
Ecco un esempio semplificato di un blocco opaco per un flusso di pagamento:
box: payment_flow
goal: allow the user to pay for a subscription
provenance: human_defined
inputs:
- selected_plan
- user_id
pseudocode:
- IF user selects plan
- CALL create_checkout_session
- IF payment succeeds
- CALL activate_subscription
- ELSE show_error_message
decisions:
payment_provider:
value: Stripe
provenance: ai_defined
status: needs_human_confirmation
constraints:
- Do not modify auth_flow without human approval
- Do not store sensitive card data
La parte interessante non è il formato YAML in sé.
La parte interessante è che ogni decisione ha un posto. Il sistema sa cosa è stato specificato da una persona, cosa è stato dedotto dall’IA e cosa richiede ancora una conferma.
Il livello intermedio non è burocrazia. È un modo per impedire all’IA di trasformare l’ambiguità in decisioni invisibili.
Cosa significa allora imparare a programmare oggi?
Forse imparare a programmare non significa più memorizzare la sintassi o scrivere ogni funzione da zero.
Forse significa imparare a ragionare a diversi livelli di precisione.
Significa sapere quando basta il linguaggio naturale, quando serve il pseudocodice, quando serve il codice eseguibile e quando una decisione deve essere registrata perché influenzerà l’intero sistema.
Imparare a programmare insegna anche:
- a scomporre i problemi;
- a individuare i casi limite;
- a modellare i dati;
- a leggere criticamente il codice generato;
- a correggere gli errori;
- a valutare i compromessi;
- a ragionare su vincoli come memoria, latenza, sicurezza, concorrenza, permessi e costi.
L’IA può accelerare enormemente la generazione di codice.
Ma più codice genera l’IA, più diventa importante leggerlo, metterlo in discussione e ricollegarlo alle decisioni umane che dovrebbe implementare.
Conclusione
La domanda «Dobbiamo ancora imparare a programmare?» potrebbe essere formulata male.
Se programmare significa memorizzare la sintassi e scrivere manualmente ogni riga di codice, probabilmente con il tempo dovremo farlo meno.
Ma se programmare significa capire come un’intenzione ambigua diventa un sistema formale che prende decisioni ed esegue azioni, allora la risposta è chiaramente sì.
L’IA può scrivere codice.
Ma qualcuno deve ancora controllare la traduzione.
E per controllare una traduzione, bisogna capire almeno una parte di entrambe le lingue.
Il Black Box Approach descritto in questo articolo è un esperimento come prova di concetto. github.com/amiune/theblackboxapproach