Benchmark

Sviluppare codice con le reti generative, ecco i test

Pubblicato il 22 Mar 2023

reti generative

L’invenzione delle reti generative, in particolare del modello linguistico denominato “Trasformatore Generativo pre-addestrato, o GPT”, ha permesso un salto impressionante nelle applicazioni di intelligenza artificiale in cui è richiesto di produrre un testo più somigliante possibile a quello che avrebbe potuto creare un essere umano.

ChatGPT è una delle prime implementazioni commerciali del modello GPT di terza generazione (GPT-3). Accanto a tali applicazioni generaliste stanno nascendo applicazioni più specializzate, in particolare per quanto riguarda la ricerca su Internet (Bing e Bard) e la produzione di software.

Github, azienda di proprietà di Microsoft e che gli informatici conoscono bene, ha di recente introdotto lo strumento chiamato Copilot, uno strumento, si legge nella pagina introduttiva, di Pair Programming che utilizza il modello Codex di OpenAI (l’azienda che ha realizzato ChatGPT) per suggerire codice e intere funzioni in Real Time, direttamente nell’IDE.

Codex è un discendente di GPT-3 ed è stato addestrato sia con linguaggio naturale che con miliardi di linee di codice sorgente proveniente da sorgenti pubblicamente disponibili, comprendenti, tra l’altro, GitHub stesso.

L’accoglienza per i due strumenti, ChatGPT e Copilot, è stata allo stesso tempo entusiastica da una parte e scettica dall’altra, per motivi condivisibili per entrambi gli schieramenti. Per tale motivo vogliamo toccare con mano le loro capacità.

Copilot messo alla prova

Seguendo professionalmente diversi progetti, ho avuto modo di testare per alcune settimane questi due strumenti, con metodo non propriamente sistematico né oggettivo, con l’obiettivo di stimare la praticità d’uso, la qualità del codice e i limiti, con il fine di verificare se essi portano effettivamente ad un aumento di produttività.

Dei vari progetti, ho focalizzato l’attenzione su un paio di essi; uno in particolare è un progetto piuttosto grosso con centinaia di file sorgenti e decine di strumenti sviluppati con diversi linguaggi e ambienti di programmazione, tra cui C++ e Python; l’altro, invece, è un progettino abbastanza semplice realizzato su Arduino.

Infine, ho provato i due strumenti nel contesto dello sviluppo di pezzettini di codice, funzioni o strumenti semplici in bash o rust. Questo per valutare sia l’utilizzo in task semplici, sia per valutarne l’efficacia con un linguaggio di cui non sono particolarmente esperto.

Ho utilizzato i miei strumenti abituali di lavoro, primariamente gli IDE di JetBrains (CLion, PyCharm, Intellij) con il relativo plug-in copilot, mentre per interfacciarmi con ChatGPT ho utilizzato il mio browser Brave.

Reti generative: ChatGPT, il test

Il primo impatto con ChatGPT è sempre abbastanza sorprendente. Prima di questo test con la scrittura di codice sorgente, l’avevo utilizzato per curiosità e per divertimento, interrogandolo su diversi argomenti al fine di misurarne la capacità effettiva di produrre testi di senso compiuto. A un certo punto l’ho persino utilizzato per scrivere un racconto pubblicato dalla rivista online “Forevera Books”…

Con il codice non è stato diverso: la prima impressione, di solito superficiale e prettamente “a pelle”, è di avere a che fare con uno strumento che è effettivamente in grado di interpretare correttamente le richieste e produrre risposte sostanzialmente sensate.

La mia prima richiesta è stata quella di suggerirmi un modo per effettuare il parsing di un file di testo separato da spazi in Rust. Il software mi ha proposto una serie di soluzioni basate su funzioni della libreria standard e su librerie (crate) specializzate (csv e rust-csv) con approcci più o meno idiomatici. Le tre soluzioni proposte sono accettabili e corrette.

Una richiesta più generica come quella su “come popolare un file xml in rust” ha portato a una risposta con la stessa impostazione, ovviamente con crate differenti. Entrambe le risposte riportavano esempi con strutture di file csv e xml ragionevoli, come si potrebbero trovare in un testo o un tutorial.

Un esempio di codice Rust per creare un file XML

Ho colto l’occasione anche per chiedere informazioni sull’uso dell’IDE, nella fattispecie, come eseguire l’applicativo specificando dei parametri sulla linea di comando. La risposta è stata corretta e dettagliata, tuttavia relativa ad una versione precedente dell’IDE. La cosa ovviamente non deve stupire, in quanto la “conoscenza del mondo” di ChatGPT è limitata al 2021. Tuttavia, la cosa curiosa è che il programma ha pensato di aggiungere come alternativa anche le istruzioni su come accedere ai parametri dal programma, cosa evidentemente senza senso, in quanto non si tratta certo di un metodo alternativo. La cosa mi ha lasciato un po’ perplesso.

Come per il testo, anche con il codice è possibile “raffinare” la soluzione proposta aggiungendo dei dettagli nelle richieste successive. Il programma non perde il contesto e arricchisce o modifica la risposta di conseguenza.

Un inghippo si è verificato quando ho richiesto a ChatGPT di ricordarmi come settare un valore di un vettore a un indice oltre la lunghezza del vettore stesso. Oltre alla soluzione corretta di usare “resize()” per ridimensionare il vettore, ChatGPT mi ha proposto l’uso di una funzione che il mio compilatore rust non era in grado di risolvere. Alla mia richiesta di spiegazione, ChatGPT ha affermato che tale funzione era disponibile a partire dalla versione 1.53 di rust. Ma il mio compilatore era la versione 1.64! Quando ho chiesto spiegazioni, il modello mi ha risposto che si era confuso: infatti la funzione è disponibile solo a partire dalla versione 1.53. Ossia restava fermo sulla sua posizione. Non sono riuscito a ricavarne una informazione sensata.

Passando a Bash, in generale gli script prodotti da ChatGPT sono stati sostanzialmente corretti e abbastanza ben articolati. Tuttavia, ho notato una totale mancanza di gestione degli errori; ad esempio, richiesto di prendere un elenco di nomi di file dalla prima colonna di un file csv, e rinominarli con il nome specificato nella seconda, non vi era alcun controllo sul risultato dell’operazione o sull’effettiva corretta formattazione del csv; il che può esporre a spiacevoli problemi.

Reti generative: Copilot, il test

Copilot è uno strumento decisamente più specializzato, e oltre ad accettare richieste in formato di testo libero, analizza anche il contesto del codice su cui si sta lavorando e propone automaticamente una o più versioni di codice che possa in qualche modo “completare” quando iniziato.

Se ad esempio, nel nostro editor scrivessimo “int main(“, Copilot lo completerebbe così:

Il completamento del main proposto da Copilot

Tutto come da manuale. L’uso del plugin è banale e si integra bene con gli automatismi e l’interfaccia grafica degli IDE jetbrains. Una volta installato il plugin è sufficiente collegare il proprio account attivando le api (in sostanza si apre una pagina web di autenticazione e si inserisce un codice numerico generato dall’IDE) e attivare il completamento. A questo punto la finestra dell’editor propone i pezzi di codice a mano a mano che si digita (occorre circa uno o due secondi di pausa).

Ad esempio, inserendo un commento come: “Questa funzione fa il setup della porta seriale in modo da ricevere asincronicamente i dati e accodarli in un buffer”, il software produce la funzione richiesta.

Codice Rust prodotto da copilot da un commento in linguaggio naturale

Nella finestra a cassetto laterale titolata “github copilot“, sono disponibili le alternative proposte, con un comodo pulsante (la cui posizione potrebbe essere migliorabile però) per incollare automaticamente l’alternativa scelta.

La finestra laterale con le alternative proposte da Copilot

Le soluzioni proposte variano quanto a stile e funzionalità: in effetti la prima proposta è completamente assente di una parte importante del requisito (“Ricevere asincronicamente i dati e accodarli”), ma alcune delle alternative proposte sono abbastanza complete.

Anche in un progetto Arduino la proposta è stata pertinente, ma a volte commette errori grossolani, che tuttavia possono portare a guai. In un caso ha sostanzialmente copiato, adattandola, una funzione che faceva una cosa analoga, ma ha arbitrariamente introdotto un cambiamento che è risultato in un funzionamento errato.

Una proposta di copilot per una funzione da integrare in un progetto Arduino. Il codice contiene un errore piuttosto pur grossolano ma difficile da identificare a una analisi superficiale

Nell’ambito del progetto più grosso, le cose si fanno però più complicate.

Come detto, il progetto consiste di un gran numero di moduli in forma di librerie ed eseguibili da utilizzarsi sia sul dispositivo oggetto di installazione (un sistema Linux embedded), sia sul dispositivo di sviluppo. Pertanto, vi sono diverse dipendenze da librerie esterne, in particolare boost e Qt5.

In molti casi i suggerimenti sono stati molto pertinenti: ad esempio è in grado di intuire che si sta realizzando una coda a priorità e suggerisce il corretto operatore di confronto; oppure che una funzione chiamata “postQuit” deve utilizzare una coda a messaggi con i corretti strumenti di locking tra thread.

Un suggerimento molto pertinente su come implementare una coda a priorità con un tipo di dati non banale

Una funzione chiamata “postQuit” deve utilizzare i corretti strumenti di locking

In altri casi il risultato è stato un po’ bizzarro. Ad esempio, se si completa un blocco switch, ci si aspetta che i simboli enumerativi utilizzati nei vari case siano esistenti. Invece in diversi casi i simboli suggeriti sono del tutto inventati, e quindi il blocco è una pura invenzione. Nessuno dei suggerimenti si è rivelato utile.

Ma il punto più dolente nei casi dei progetti più articolati, è che il contesto preso in considerazione da Copilot è solo quello del file; pertanto, talvolta ci troviamo con suggerimenti che non tengono conto delle risorse, in termini di dipendenze, librerie o di funzioni già disponibili nel progetto, suggerimenti quindi che non sono utili in quanto “reinventano la ruota” o non utilizzano funzioni già disponibili. È il caso, per esempio, di funzioni, ad esempio di parsing di stringhe, che possono agevolmente essere implementate utilizzando una o due righe con l’aiuto della librerie boost::algorithm, che invece vengono implementate utilizzando le funzioni base del C o C++.

Conclusioni: l’uso delle reti generative per produrre codice può avere riflessi positivi nella produttività, ma richiede un aumento dell’impegno nella revisione

Da questa breve esperienza con gli strumenti di sviluppo basati su modelli linguistici mi ha confermato alcune impressioni che avevo già avuto a livello intuitivo utilizzando ChatGPT per generare testi.

Come in molti non hanno mancato di rilevare, i testi prodotti da reti generative pur essendo stilisticamente ben impostati, mancano il più delle volte di concretezza. Non sono rari i casi, anzi, quelli raccontati su twitter sono più che frequenti, di testi prodotti da ChatGPT completamente avulsi dalla realtà, e i primi racconti delle esperienze con il nuovo motore AI di Bing non sono diversi.

Nel codice la situazione è similare, ma con differenze importanti. Probabilmente la statistica dei costrutti e della semantica del linguaggio naturale e dei linguaggi di programmazione sono intrinsecamente molto differenti, e pertanto le reti generative ottengono performance allo stesso modo differenti nei due casi.

Nel codice infatti – ma è una sensazione a pelle – mi è sembrato che ChatGPT e Copilot riuscissero a centrare mediamente la risposta attesa più di frequente che nella generazione del testo naturale, purché però la richiesta sia ben circostanziata e contenga sufficienti dettagli. Tuttavia, è proprio in alcuni dettagli che le reti falliscono in modo clamoroso, come nell’esempio di Copilot più sopra con il costrutto “if” invertito. Penso che in buona sostanza ciò sia dovuto al fatto che tali reti non hanno la percezione del significato del testo che producono, ma solo della correzione statistica tra i gruppi di costrutti, correlazione ottenuta dalla quantità di codice “introiettata” durante la fase di addestramento e anche, nel caso di Copilot, di quella interna al file stesso che l’utente sta editando.

Questa incertezza e variabilità nella qualità del codice prodotto si rivela il maggior limite di questi strumenti. Come detto per i testi, il contenuto è plausibile, il più delle volte compila correttamente, ma è indispensabile provvedere ad una revisione completa e attenta del codice. Per un uso proficuo è quindi assolutamente indispensabile, secondo me, prevedere l’uso sistematico di test automatizzati con adeguato controllo della copertura del codice, prevedendo in particolar modo di testare i “corner case”, che per loro stessa non vengono in generale presi in considerazione dai generatori di codice. E ancor più, mancando in generale la produzione di codice per la gestione degli errori, aspetto che in generale è una delle maggiori fonti di problemi.

Per non citare poi, l’aspetto tutt’altro che trascurabile, della sicurezza del codice.

Per concludere, mi sento di dire che con le adeguate precauzioni in termini di metodologia di sviluppo, l’uso di reti generative per produrre codice può avere un qualche riflesso positivo nella produttività, mitigato però purtroppo da un aumento dell’impegno nella revisione del codice stesso; ma probabilmente analoghi benefici si possono ottenere con l’uso di strumenti intelligenti già presenti in molti IDE avanzati.

Valuta la qualità di questo articolo

La tua opinione è importante per noi!

Articoli correlati

Articolo 1 di 3