Metafora visiva che contrappone un documento di testo piatto a una ricca struttura a grafo interconnessa che emerge dal codice legacy, specifica per la migrazione da COBOL a Java.
Artificial IntelligenceSoftware EngineeringTechnology

L'IA Ha Tradotto Perfettamente 30 Anni di COBOL. Poi Ha Mandato in Crash il Database.

Ashutosh SinghalAshutosh Singhal5 febbraio 202616 min

Era un martedì sera e fissavo uno stack trace che non aveva alcun senso.

Stavamo lavorando con un team di servizi finanziari che cercava di migrare un modulo di transazioni core da COBOL a Java. L'IA aveva svolto il suo compito — o almeno così pensavamo. Il codice Java generato era pulito, ben strutturato, compilato senza un singolo errore. Gli unit test passavano. Tutti in call erano cautamente ottimisti. Poi lo hanno distribuito nell'ambiente di test, e il primo bonifico ha corrotto il database.

Il bug non era nel Java. Il Java era sintatticamente perfetto. Il bug era in ciò che l'IA non ha mai visto.

Una variabile chiamata TRN-LIMIT — definita non nel file sorgente che l'IA ha tradotto, ma in un COPYBOOK incluso migliaia di righe prima nella catena di esecuzione — conteneva una clausola REDEFINES. Si tratta di un costrutto COBOL in cui lo stesso indirizzo di memoria viene interpretato come due diversi tipi di dati a seconda di un flag impostato in un modulo completamente diverso. L'IA vedeva TRN-LIMIT come un semplice campo numerico. Non lo era. Era un decimale compresso mascherato da intero a seconda del contesto di runtime. L'IA ha allucinato una definizione standard, e l'applicazione Java ha scritto dati binari corrotti in una colonna del database.

Quella notte, seduto in una sala conferenze con il mio team a sviscerare cosa fosse andato storto, ho capito qualcosa che avrebbe rimodellato tutto ciò che abbiamo costruito in VeriPrajna: l'IA non ha fallito perché fosse stupida. Ha fallito perché era cieca.

Il problema da 1,52 trilioni di dollari di cui nessuno vuole parlare

Ecco la scomoda realtà dell'economia globale nel 2025: il 43% dei sistemi bancari gira ancora su COBOL, e quei sistemi elaborano il 95% di tutte le transazioni ATM. Il software che fa funzionare le aziende Fortune 500? Circa il 70% è stato scritto oltre due decenni fa. Il debito tecnico nei soli Stati Uniti è lievitato fino a una stima di 1,52 trilioni di dollari.

E le persone che hanno scritto questo codice stanno andando in pensione. Non "potrebbero andare in pensione un giorno" — se ne stanno andando adesso, portando con sé decenni di conoscenza istituzionale. Nel frattempo, l'80% dei budget IT federali va al mantenimento in vita dei sistemi legacy, lasciando appena il 20% per qualsiasi cosa nuova.

Mi sono seduto al tavolo di fronte a CTO che descrivono la loro situazione di modernizzazione come descriveresti una casa con fondamenta che si sgretolano: sai che devi ripararla, sai che aspettare la peggiora, ma ogni appaltatore che ci ha provato ha reso le cose più costose senza risolvere davvero il problema.

I numeri lo confermano. Tra il 70% e l'80% dei progetti di modernizzazione dei sistemi legacy non riesce a raggiungere i propri obiettivi. Ed era vero prima che l'IA generativa entrasse in scena.

Perché tutti pensavano che GPT potesse risolvere tutto questo?

Lo capisco. Davvero. Quando è uscito GPT-4, il mercato della consulenza software è andato in overdrive. All'improvviso ogni azienda aveva un "acceleratore di migrazione COBOL" — che, se guardavi sotto il cofano, era un sottile wrapper attorno a un foundation model. Incolli il tuo paragrafo COBOL, ottieni un metodo Java. Magia.

Io e il mio co-fondatore abbiamo passato settimane a valutare questi strumenti. Fornivamo loro codice legacy reale proveniente da ambienti dei clienti e controllavamo l'output. La sintassi era quasi sempre corretta. Il codice compilava. E poi falliva in modi incredibilmente difficili da diagnosticare, perché la forma del codice sembrava giusta anche quando il significato era sbagliato.

Il bug più pericoloso non è quello che manda in crash il tuo sistema. È quello che corrompe silenziosamente i tuoi dati per sei mesi prima che qualcuno se ne accorga.

Il problema è architetturale, e si riduce a come i large language model elaborano le informazioni. Gli LLM usano un meccanismo di attenzione per pesare l'importanza delle diverse parti del loro input. I modelli moderni vantano finestre di contesto fino a un milione di token. Ma la ricerca ha dimostrato un fenomeno chiamato effetto "Lost in the Middle": gli LLM mostrano una curva di prestazioni a forma di U, ricordando bene le informazioni all'inizio e alla fine di un prompt ma degradando in modo significativo per qualsiasi cosa si trovi nel mezzo.

In un progetto di modernizzazione, un singolo programma COBOL può essere lungo migliaia di righe, facendo riferimento a copybook che sono a loro volta lunghi migliaia di righe. Se la definizione di MAX-TRANSACTION-LIMIT si trova nel mezzo di quel contesto enorme, è statisticamente probabile che l'IA la manchi. E quando manca qualcosa, non si ferma a chiedere. Allucina. Inventa una definizione plausibile e va avanti.

Cosa succede quando tratti il codice come testo?

Un diagramma di confronto affiancato che mostra come il recupero standard con vector RAG manchi dipendenze di codice critiche rispetto a come il recupero basato su grafo traccia le connessioni logiche attraverso i file.

Questo è l'errore centrale che vedo commettere all'intero ecosistema dei "wrapper di IA", ed è la discussione che ho continuato ad avere con un potenziale investitore agli inizi. Ha guardato il nostro approccio — costruire knowledge graph di repository di codice — e ha detto: "Perché non usare semplicemente una finestra di contesto più grande? GPT-5 risolverà il problema."

Ho aperto un programma COBOL sul mio laptop. "Trovami la definizione di ACCOUNT-BALANCE," ho detto.

Ha cercato nel file. Non è riuscito a trovarla. Perché non era in quel file. Era in un copybook, incluso tramite un'istruzione alla riga 47, che a sua volta faceva riferimento a una data division condivisa mantenuta da un team completamente diverso.

"Ora immagina di essere un LLM," ho detto. "Stai facendo una ricerca di similarità vettoriale per il codice relativo all'"elaborazione dei pagamenti". Troverai cinque chunk che menzionano la parola "pagamento". Ti sfuggirà completamente il file chiamato GlobalVarDef.cbl che definisce l'aliquota fiscale usata dalla logica di pagamento — perché quel file non menziona da nessuna parte la parola "pagamento"."

La Retrieval-Augmented Generation standard, o RAG — la tecnica usata dalla maggior parte degli strumenti di coding basati su IA per aggiungere conoscenza agli LLM — recupera il contesto in base alla similarità testuale. Converte il codice in vettori e trova vettori simili. Questo funziona magnificamente per i chatbot di FAQ. È catastroficamente insufficiente per il codice.

Il codice non è linguaggio naturale. "Il gatto era seduto sul tappetino" significa più o meno la stessa cosa indipendentemente da ciò che hai letto cinquanta pagine fa. Ma x = y + 1 non significa nulla a meno che non conosca le definizioni, i tipi e gli stati correnti di x e y — che potrebbero essere definiti in un file diverso, in un modulo diverso, o ereditati da una classe genitore.

Ho scritto in modo approfondito su questo problema strutturale nella versione interattiva della nostra ricerca. In breve: il software non è testo. Il software è un grafo.

La notte in cui abbiamo smesso di costruire un wrapper migliore

C'è stato un momento — lo ricordo chiaramente — in cui il mio team stava discutendo la nostra architettura. Avevamo due strade. Strada uno: costruire una pipeline RAG più intelligente. Chunking migliore, embedding migliori, prompt migliori. Iterare sull'approccio wrapper finché non funzionasse abbastanza bene. Strada due: buttare via completamente il paradigma basato sul testo e trattare il codice per ciò che è realmente — un sistema relazionale di logica.

La strada uno era più veloce. La strada uno era ciò che gli investitori capivano. La strada uno aveva già una dozzina di concorrenti a dimostrare la domanda di mercato.

Il mio lead engineer ha aperto una lavagna e ha disegnato un programma COBOL come un grafo. Nodi per variabili, funzioni, copybook, tabelle di database. Archi per CALLS, READS, UPDATES_TABLE, IMPORTS_COPYBOOK. Poi ha tracciato una catena di dipendenze: il Modulo A chiama il Modulo B, che modifica la Variabile X, che viene letta dal Modulo C in una directory completamente diversa.

"Chiedi a una ricerca vettoriale di trovare quella catena," ha detto.

Nessuno ci è riuscito.

Quella è stata la notte in cui ci siamo impegnati a costruire ciò che ora chiamiamo Repository-Aware Knowledge Graph — un database a grafo unificato che combina la struttura statica del codice (alberi sintattici astratti, call graph) con il significato semantico della logica di business (documentazione, commenti, intento delle variabili). Non stavamo per costruire un traduttore migliore. Stavamo per costruire una mappa.

Come si trasformano trent'anni di COBOL in una mappa?

Un diagramma di pipeline a quattro fasi che mostra il processo di costruzione del knowledge graph: parsing strutturale, estrazione di entità/relazioni, risoluzione dei simboli cross-repository e calcolo della chiusura transitiva.

Il processo ha quattro fasi, e ti risparmierò i dettagli di implementazione — puoi trovarli nel nostro approfondimento tecnico completo. Ma i concetti contano, perché spiegano perché questo approccio funziona dove i wrapper falliscono.

Primo, analizziamo il codice in modo strutturale, non testuale. Le pipeline RAG standard usano lo "splitting ingenuo" — tagliano un file ogni 500 token, spesso separando la firma di una funzione dal suo corpo. Noi usiamo parser come Tree-sitter per generare Abstract Syntax Tree, che rispettano i confini logici del codice. Una funzione viene trattata come un'unità completa di logica, non come una porzione casuale di testo.

Secondo, estraiamo entità e relazioni. Classi, paragrafi, variabili, tabelle di database, endpoint API — questi diventano nodi. Gli archi tra di essi — CALLS, UPDATES_TABLE, DEFINES_VARIABLE — diventano il tessuto connettivo. Ora possiamo interrogare il grafo: "Mostrami ogni paragrafo che aggiorna il campo CUSTOMER-ID." Risultati esatti, all'istante. Prova a farlo con grep.

Terzo — ed è qui che diventa interessante — risolviamo i simboli attraverso l'intero repository. Un parser standard vede ACCT-NUM nel File A e ACCT-NUM nel File B come due stringhe diverse. Il nostro sistema determina che entrambi si riferiscono alla stessa voce in un copybook condiviso e li unisce in un unico nodo. Uniamo anche documentazione e codice: se un documento di requisiti in PDF descrive la "User API" e il codice contiene una classe chiamata UserAPI, il sistema collega l'intento con l'implementazione.

Quarto, calcoliamo la chiusura transitiva. Ricordi il fallimento della banca? A dipende da B, B dipende da C, e l'IA ha visto A ma ha mancato C. Il nostro grafo attraversa in profondità — da A a B a C — per identificare la definizione radice di ogni variabile. Quando l'IA genera codice per il Modulo A, importa le definizioni corrette dal Modulo C, anche se il Modulo C si trova in una directory o in un repository completamente diverso.

Perché la RAG standard fallisce per la migrazione del codice?

Le persone mi contestano sempre su questo. "La RAG funziona bene per il codice," dicono. "Basta usare embedding migliori."

Lascia che ti dia tre scenari in cui la ricerca di similarità vettoriale crolla completamente:

Uno sviluppatore rinomina Account in Acct. La similarità semantica cala, anche se la logica è identica. Una funzione chiamata FNC-001 esegue il calcolo degli interessi ma non contiene commenti — cercare "calcolo degli interessi" non la troverà mai. E il fallimento più comune: la vector RAG recupera un unit test e un commento dell'interfaccia utente che menzionano "pagamento", ma manca la logica di business core perché i nomi delle variabili non corrispondono ai termini della query.

Il recupero basato su grafo non chiede "quale testo sembra simile?" Chiede "cosa è logicamente connesso?" — e quella distinzione è la differenza tra codice che compila e codice che funziona.

Ciò che chiamiamo GraphRAG opera sulla struttura, non sulla similarità. Quando qualcuno chiede "rifattorizza la logica di pagamento", il sistema usa la ricerca vettoriale per trovare il punto di ingresso — ad esempio, il paragrafo ProcessPayment. Ma poi, invece di fermarsi, attraversa gli archi del grafo. Recupera le subroutine tramite gli archi CALLS, le definizioni delle variabili tramite gli archi READS, i copybook tramite gli archi INCLUDES. Questi pezzi connessi possono essere testualmente dissimili ma sono logicamente inseparabili.

La ricerca mostra che GraphRAG supera significativamente la vector RAG nel ragionamento multi-hop — connettere fatti separati da diversi passaggi. Nel software, quasi ogni bug serio è un fallimento del ragionamento multi-hop. Se cambio la logica del tasso di interesse nel Modulo A, quali schermate di reportistica nel Modulo Z si rompono? La vector RAG non può rispondere a questo. Il grafo sì, perché attraversa la catena di chiamate di funzione che li collega.

Il problema del GOTO (Ovvero: perché il COBOL fa allucinare i loop all'IA)

Un diagramma che mostra come i salti GOTO del COBOL vengono mappati come archi in un Control Flow Graph e poi riconosciuti tramite pattern matching in strutture di controllo Java corrette (loop, condizionali, return).

Voglio raccontarti di una specifica sfida tecnica che ci ha quasi distrutti, perché illustra perché questo lavoro è molto più difficile di quanto la gente presuma.

Il COBOL ha un'istruzione GOTO. Java no. GOTO permette all'esecuzione del programma di saltare ovunque — avanti, indietro, nel mezzo di un altro blocco. Crea lo "spaghetti code" contro cui ogni professore di informatica ti mette in guardia. Tradurre GOTO non è un problema di sintassi. È un problema di topologia.

Abbiamo osservato tre diversi strumenti di IA commerciali tentare di tradurre un modulo COBOL con un uso massiccio di GOTO. Uno ha generato una chiamata di funzione ricorsiva che avrebbe causato uno StackOverflowError in produzione. Un altro ha prodotto un loop while(true) senza condizione di uscita. Il terzo — il mio preferito personale — ha semplicemente inventato un flusso di controllo che non esisteva nel codice originale. Sembrava plausibile. Era completamente sbagliato.

Il nostro approccio: mappare le destinazioni GOTO come archi in un Control Flow Graph. Poi usare il riconoscimento di pattern sul grafo. Un GOTO che salta indietro a un'etichetta precedente? Quello è un loop. Un GOTO che salta un blocco? Quello è un condizionale. Un GOTO verso un paragrafo di uscita? Quello è un'istruzione return. L'IA, guidata dalla struttura del grafo, rifattorizza questi salti in loop while, blocchi if/else, o istruzioni break/continue.

Senza il grafo, l'IA sta tirando a indovinare. Con il grafo, sta facendo ingegneria.

La differenza tra un chatbot e un agente

Noi non costruiamo chatbot. Devo essere chiaro su questo, perché il mercato è inondato di strumenti che ti permettono di "chattare con la tua codebase", e non sono la stessa cosa.

Un chatbot prende la tua domanda, la invia a GPT-4, e restituisce qualunque cosa torni indietro. Se l'output è sbagliato, lo debughi manualmente. Questo è il workflow di ogni wrapper di IA sul mercato.

Ciò che noi distribuiamo sono agenti autonomi che pianificano, eseguono e si auto-correggono. L'agente analizza l'AST del file COBOL target, identifica le dipendenze, interroga il knowledge graph, genera codice Java, poi lo compila in una sandbox. Se il compilatore lancia un errore — "variabile non trovata" — l'agente legge l'errore, interroga il grafo per la dipendenza mancante, e rigenera. Poi esegue gli unit test derivati dagli execution trace COBOL originali per verificare l'equivalenza comportamentale.

Questo loop di compilazione-correzione sposta l'onere della validazione dall'umano al sistema. Ma — e questo conta enormemente nei settori regolamentati — il knowledge graph fornisce piena interpretabilità. Uno sviluppatore può vedere esattamente perché l'IA ha preso ogni decisione: "L'IA ha importato com.bank.logic perché ha trovato una dipendenza da COPYBOOK-X." Non "fidati, sono l'IA." Bensì: ecco la catena di citazioni per questa logica.

Nel settore bancario, ogni riga di codice deve essere sottoponibile ad audit. Non puoi distribuire una scatola nera che "probabilmente" ha fatto la cosa giusta. Hai bisogno di un sistema che possa mostrare il proprio lavoro.

E il codice morto?

Una cosa che mi ha sorpreso: i sistemi legacy sono pieni di codice che nessuno usa più. Vecchie promozioni, prodotti ritirati, routine di debug del 1997. Un'IA basata sul testo migra tutto ciò che le viene dato — non riesce a distinguere il codice attivo dal codice morto.

Il nostro call graph identifica i nodi irraggiungibili — paragrafi o file senza archi in entrata, il che significa che nulla li chiama. Segnaliamo questo codice morto per l'eliminazione prima che inizi la migrazione. Nella nostra esperienza, questo tipicamente riduce la codebase del 20-30%. Non è un'ottimizzazione minore. È eliminare un quarto del lavoro e un quarto della superficie di attacco.

"Finestre di contesto più grandi non risolveranno tutto questo?"

Ricevo ancora questa domanda di continuo. Il presupposto è che se GPT-5 o Claude 4 possono gestire dieci milioni di token, il problema del "Lost in the Middle" scompare.

Non scomparirà. Ed ecco perché.

Anche se il degrado dell'attenzione migliora — e migliorerà — stai comunque facendo recupero testuale. Stai comunque cercando stringhe simili invece di attraversare connessioni logiche. Una finestra di contesto da un milione di token non aiuta se la variabile di cui hai bisogno è definita in un file che condivide zero keyword con il file che stai traducendo. Il problema non è la dimensione della finestra. Il problema è che la finestra sta guardando la cosa sbagliata.

L'altra obiezione che sento: "I knowledge graph sono costosi da costruire." Lo sono. Analizzare un intero repository, risolvere i simboli, calcolare la chiusura transitiva — è un investimento iniziale significativo. Ma considera l'alternativa. La migrazione manuale di un grande sistema COBOL costa decine di milioni di dollari e richiede anni. La migrazione con IA basata su wrapper costa meno all'inizio ma genera un flusso costante di bug indotti da allucinazioni che richiedono un costoso debugging umano. L'approccio basato su grafo ha un costo di setup più alto e un costo di rilavorazione drasticamente più basso. I dati di McKinsey suggeriscono che la GenAI può ridurre i compiti di coding del 50%, ma solo se distribuita correttamente. Abbiamo visto miglioramenti della produttività degli sviluppatori da 2x a 3x rispetto agli strumenti di IA standard, proprio perché gli sviluppatori smettono di passare ore a cercare dove è definita una variabile.

La mappa è l'asset

Ecco cosa avrei voluto capire all'inizio: il knowledge graph non è solo uno strumento per la migrazione. È un asset permanente.

Una volta che la tua codebase esiste come grafo, rimane una rappresentazione viva del tuo sistema. Man mano che il nuovo codice Java evolve, il grafo si aggiorna. Ottieni una documentazione automatizzata sempre aggiornata. Ottieni il rilevamento della deriva architetturale — il sistema ti avvisa se il nuovo codice viola le regole di modularità che hai definito. Ottieni l'analisi d'impatto su richiesta: "Se cambio questo metodo, cosa si rompe?"

La modernizzazione non è un evento una tantum. È un ciclo di vita. Le organizzazioni che la trattano come un progetto — con una data di inizio e una data di fine — sono quelle che finiscono per tornare al punto di partenza in cinque anni, annegando in una nuova generazione di debito tecnico.

Il codice non è testo

La lezione a cui continuo a tornare — quella di quel martedì sera a fissare uno stack trace — è ingannevolmente semplice: il codice non è testo, e gli strumenti che lo trattano come testo produrranno risultati che sembrano giusti e si comportano in modo sbagliato.

L'intera economia dei "wrapper di IA" è costruita su un errore di categoria. Presume che, poiché gli LLM sono straordinari nell'elaborare il linguaggio, debbano essere straordinari nell'elaborare il codice. Ma il codice non è linguaggio. Il codice è un grafo — un sistema denso e interconnesso di dipendenze, flussi di dati e cambiamenti di stato che esiste simultaneamente in più dimensioni. Cercare di modernizzarlo con strumenti basati sul testo è come navigare in una città usando un elenco di nomi di strade ma senza mappa. Ti "perderai nel mezzo".

Noi abbiamo costruito la mappa. E funziona — non perché siamo più intelligenti dei team che costruiscono i foundation model, ma perché ci siamo posti una domanda diversa. Loro si sono chiesti: "Come facciamo a far comprendere meglio il testo all'IA?" Noi ci siamo chiesti: "E se il problema non fosse affatto il testo?"

Il futuro della modernizzazione dei sistemi legacy non è un language model più grande. È un sistema che comprende il software nel modo in cui il software funziona realmente — come struttura, non come stringhe.

Questa è la scommessa che abbiamo fatto in VeriPrajna. Ogni giorno, un'altra organizzazione scopre che il suo Java generato dall'IA compila magnificamente e fallisce catastroficamente. Ogni giorno, il divario tra traduzione sintattica e comprensione semantica diventa più costoso da ignorare. Le organizzazioni che colmeranno quel divario non si limiteranno a modernizzare il loro codice. Lo comprenderanno finalmente — molte di esse per la prima volta.

Related Research

Also Published On