![](https://www.codemotion.com/magazine/wp-content/uploads/2024/11/DALL·E-2024-11-18-12.58.45-A-horizontal-digital-illustration-depicting-a-senior-and-junior-developer-working-together-in-a-modern-office-environment.-The-senior-developer-a-man-896x504.webp)
Cosa sia l’AI e cosa sia ChatGPT lo sappiamo tutti. Ognuno di noi ha scritto valanghe di prompt per ottenere risposte da ChatGPT, con risultati più o meno soddisfacenti.
Per capire al meglio come ChatGPT possa aiutarci a diventare programmatori senior, dobbiamo prima comprendere cosa sia l’ingegneria dei prompt e perché sia così importante e quali siano gli ambiti in cui un LLM (Large Language Model) come ChatGPT o altri possano aiutarci a crescere professionalmente.
Esperienza sul campo
Quello che sto per scrivere nasce dall’esperienza di mesi di utilizzo di ChatGPT, GitHub copilot, Claudie e altri LLM, e da una serie di letture e studi sull’argomento, sono sicuro che fra qualche mese verrò smentito ed usciranno, o sono già usciti e non li utilizzo, strumenti migliori che aiutano in modo ancora più importante gli sviluppatori (e non solo) a crescere professionalmente.
Sicuramente qualcuno fra di voi utilizza strumenti diversi da quelli che uso io, ma proviamo a concentrarci su quello che possiamo far fare a tool di questo genere e non su quale sia il migliore, altrimenti si innesca una guerra infinita su quale sia il migliore e sono sicuro che nessuno ne uscirebbe vincitore.
Comprendere ChatGPT e in modo più ampio i LLM
Partiamo dal principio: ChatGPT è un chatbot di intelligenza artificiale (AI) che utilizza l’elaborazione del linguaggio naturale per creare un dialogo conversazionale simile a quello umano. Il modello linguistico può rispondere a domande e comporre vari contenuti scritti, tra cui articoli, post sui social media, saggi, codice e e-mail, rendendolo uno strumento entusiasmante per programmatori di tutti i livelli.
Si tratta quindi di un modello che non ragiona, ma è in grado di applicare in maniera molto efficace le regole che gli sono state insegnate durante il training. Questo significa che se gli diamo un input sbagliato, otterremo un output sbagliato, ma se diamo un input corretto, otterremo un output corretto.
Allo stesso modo, se utilizziamo un LLM che ha utilizzato dei dataset con errori, di conseguenza avremo un output con errori.
Oltre a questo i modelli tentano sempre di dare delle risposte e di conseguenza esiste una percentuale di risposte, più o meno grandi, che saranno sempre sbagliate, inventate o comunque non corrette.
Fissato questo punto iniziale, il cui scopo è quello di focalizzare il tipo di strumento che stiamo tentanto di utilizzare, proviamo a capire quali sono gli ambiti in cui la soglia d’errore è più alta e quali invece quelli in cui la soglia d’errore si abbassa e possiamo utilizzarlo con maggiore successo.
Gli LLM non sono pensati per risolvere problemi o indovinelli
Gli LLM sono stati creati per rispondere a domande, scrivere articoli, scrivere codice, ma non sono stati creati per risolvere problemi o indovinelli. Questo significa che se gli diamo un problema, non saprà risolverlo, ma se gli diamo un problema già risolto, saprà darci la soluzione.
Ogni volta che vedo un video dove qualcuno si diverte a proporre un indovinello a ChatGPT e poi si stupisce che non riesca a risolverlo, mi viene da ridere, perché è come chiedere ad un bambino di 5 anni di risolvere un problema di fisica quantistica: sono test che non tengono conto delle capacità dell’interlocutore e del modo col quale è stato pensato. Non aggiungono nessun valore e non dicono che il modello sia inadeguato, ma piuttosto stanno dimostrando che il “creator” di turno non ha capito come utilizzare il modello.
Limiti di conoscenza
Un altro punto importante da tenere in considerazione è che gli LLM non hanno una conoscenza infinita, ma hanno una conoscenza limitata a quello che è stato inserito nei dataset di training. Questo significa che se gli diamo un input che non è presente nei dataset, non saprà rispondere.
Quando questa cosa accade spesso? Quando proviamo a chiedere a un LLM di darci informazioni rispetto a prodotti che non esistevano o erano appena usciti nel momento in cui il modello ha iniziato ad essere addestrato.
Iniziano ad uscire delle mitigazioni a questo aspetto, come l’interrogazione in tempo reale della rete, il training continuo, ma sono tutte soluzioni che non risolvono il problema, ma al momento la mitigano, riuscendo in parte a dare delle risposte, ma in parte ancora maggiore ad inventarsele.
Non aspettatevi quindi di avere risposte da ChatGPT su prodotti che sono usciti da poco, su tecnologie che sono state rilasciate da poco, su eventi che sono accaduti da poco, perché non saprà rispondervi o se lo farà sarà con delle risposte inventate o di bassa qualità.
Dove possiamo avere dei risultati migliori?
Esistono una serie di modelli che non si basano solo su prompt, ma che si basano su una serie di dati che vengono forniti al modello e che permettono di avere delle risposte migliori. Questi modelli sono in grado di dare delle risposte migliori, perché hanno una conoscenza più ampia e più dettagliata rispetto a quella che può avere un modello basato solo su prompt.
Un esempio di questi modelli è GitHub Copilot, che è in grado di dare delle risposte migliori rispetto a ChatGPT, perché ha una conoscenza più ampia e più dettagliata rispetto a quella che può avere ChatGPT.
La conoscenza più ampia è data dal fatto che per generare un suggerimento viene analizzato il codice prima e dopo la posizione del cursore all’interno dell’editor, vengono utilizzati tutti i file aperti in quel momento dall’editor ed aggiungendo i repositori GitHub referenziati nel contesto. Tutti questi dati fanno parte dell’input inviato a Copilot e di conseguenza il codice suggerito non è un semplice copia a incolla di qualcosa trovato nei dati di allenamento, ma è codice che è fortemente contestualizzato rispetto al contesto in cui viene utilizzato.
Facciamo un piccolo esempio, partendo da questo codice, che potrebbe essere scritto in qualsiasi linguaggio di programmazione:
public static final int UNO = 1;
public static final int DUE = 2;
public static final int TRE = 3;
public static final int QUATTRO = 4;
public static final int CINQUE = 5;
public static void main(String[] args) {
// Dato il parametro cerco la corrispondenza rispetto alle statiche }
Code language: PHP (php)
Soffermiamoci sul commento, che potrebbe essere scritto in qualsiasi linguaggio di programmazione, ma che è molto generico e non ci dice nulla di cosa si aspetta il programmatore che ha scritto il codice e non andrebbe bene se lo scrivessimo in un prompt per ChatGPT, perché non ci darebbe una risposta corretta.
In questo contesto invece Copilot è in grado di capire che il commento è riferito alle variabili statiche e di conseguenza ci suggerirà un codice che fa esattamente quello che ci aspettiamo.
int parametro = Integer.parseInt(args[0]);
switch (parametro) {
case UNO:
System.out.println("UNO");
break;
case DUE:
System.out.println("DUE");
break;
case TRE:
System.out.println("TRE");
break;
case QUATTRO:
System.out.println("QUATTRO");
break;
case CINQUE:
System.out.println("CINQUE");
break;
default:
System.out.println("Nessuna corrispondenza");
}
Code language: JavaScript (javascript)
Capisco che a questo punto moltissimi di voi si staranno dicendo: “il codice è troppo semplice”, “si poteva fare di meglio” e avete ragione, ma il punto non è quello, il punto è che Copilot è in grado di capire il contesto e di dare una risposta corretta, mentre ChatGPT non sarebbe in grado di farlo, a meno di non scrivere un prompt molto più esteso di quel commento minimale inserito all’interno del codice.
Contestualizzare la domanda o l’aiuto che si vuole ottenere da un enorme vantaggio rispetto a scrivere un prompt generico, perché permette al modello di capire meglio cosa si aspetta da lui e di conseguenza di dare una risposta migliore.
Comprensione del codice
Spesso ci troviamo di fronte a codice che non abbiamo scritto noi e che non conosciamo, ma che dobbiamo modificare o estendere. In questi casi è molto utile avere un modello che ci aiuti a capire cosa fa il codice e come funziona.
Un programmatore senior normalmente non ha grossi problemi nella comprensione del codice, ma abbassando il livello a junior o semplicemente uscendo dalla confort zone del senior, ci si rende conto che non è così scontato capire cosa fa un pezzo di codice scritto da qualcun altro.
I modelli LLM ci possono aiutare in questa fase, perché sono in grado di analizzare il codice e di darci una spiegazione di cosa fa e come funziona. Questo ci permette di capire meglio il codice e di riuscire a modificarlo o estenderlo in modo più efficace.
Anche in questo caso la contestualizzazione è molto importante, ma proviamo comunque ad affrontare il problema con un prompt generico e vediamo cosa succede.
Partiamo da questa funzione presa dal repository https://github.com/jetty/jetty.project
public static boolean matchesURI(URI uri1, URI uri2)
{
String scheme = uri1.getScheme();
if (scheme.equalsIgnoreCase(uri2.getScheme()))
{
if (uri1.getHost().equalsIgnoreCase(uri2.getHost()))
{
// Handle default HTTP ports.
int thisPort = HttpClient.normalizePort(scheme,
uri1.getPort());
int thatPort = HttpClient.normalizePort(scheme,
uri2.getPort());
if (thisPort == thatPort)
{
// Use decoded URI paths.
return uri2.getPath().startsWith(uri1.getPath());
}
}
}
return false;
}
Code language: JavaScript (javascript)
Proviamo ora a chiedere a Claude (ma sarebbe la stessa cosa con altri LLM) di spiegarci cosa fa questa funzione.
Mi descrivi questo codice:
<codice>
In risposta avremo:
Questo codice definisce un metodo statico chiamato `matchesURI` che confronta due oggetti URI (Uniform Resource Identifier) per determinare se corrispondono secondo criteri specifici. Ecco una descrizione dettagliata del funzionamento:
- Il metodo prende due parametri: `uri1` e `uri2`, entrambi di tipo URI.
- Confronta prima gli schemi (protocolli) dei due URI:
- Estrae lo schema di `uri1`
- Confronta gli schemi di `uri1` e `uri2` ignorando le maiuscole/minuscole
- Se gli schemi corrispondono, procede a confrontare gli host:
- Confronta gli host di `uri1` e `uri2` ignorando le maiuscole/minuscole
- Se anche gli host corrispondono, passa al confronto delle porte:
- Utilizza un metodo `HttpClient.normalizePort()` per gestire le porte predefinite HTTP
- Confronta le porte normalizzate di entrambi gli URI
- Se le porte corrispondono, controlla infine i percorsi:
- Verifica se il percorso decodificato di `uri2` inizia con il percorso decodificato di `uri1`
- Il metodo restituisce `true` se tutte queste condizioni sono soddisfatte, altrimenti restituisce `false`.
In sintesi, questo metodo considera due URI corrispondenti se hanno lo stesso schema, host, porta (considerando le porte HTTP predefinite) e se il percorso di `uri2` inizia con il percorso di `uri1`.
Capite che, in tutti i contesti dove ci troviamo di fronte a codice che non conosciamo, un LLM può essere molto utile per capire cosa fa e come funziona.
Creazione dei test
Allo stesso modo ci troviamo spesso di fronte alla scrittura di test per il codice che abbiamo scritto o che dobbiamo scrivere. Scrivere test non è sempre facile e richiede una certa esperienza e conoscenza delle best practice.
In questo caso un LLM può esserci d’aiuto, perché è in grado di generare dei test per il codice che abbiamo scritto o che dobbiamo scrivere. Questo ci permette di avere dei test di qualità e di coprire meglio il codice che stiamo scrivendo.
Chiaramente non si tratta di un prodotto finito, ma permette di velocizzare molto la scrittura, soprattutto in contesti dove dobbiamo scrivere molti test o dove non abbiamo molta esperienza nella scrittura dei test.
Partendo sempre dall’esempio di qui sopra possiamo chiedere:
Mi scrivi i test per questa funzione:
<codice>
Sempre con Claude come modello, otterremo:
Certamente, posso aiutarti a scrivere alcuni test per questa funzione. Userò JUnit, un framework di test ampiamente utilizzato per Java, per creare questi test. Ecco una serie di test che coprono diversi scenari:
Questi test coprono diversi scenari per la funzione matchesURI:
- URI corrispondenti
- Schemi diversi
- Host diversi
- Corrispondenza con porta predefinita
- Porte diverse
- Percorsi non corrispondenti
- Insensibilità alle maiuscole/minuscole per schema e host
- URI con query e frammento
Questi test dovrebbero fornire una buona copertura per la funzione matchesURI. Tuttavia, potresti voler aggiungere ulteriori test per casi limite o scenari specifici del tuo uso della funzione.
Di seguito riporto il pezzo di codice creato che rappresenta un’ottima base di partenza :
import org.junit.Test;
import static org.junit.Assert.*;
import java.net.URI;
public class URIMatcherTest {
@Test
public void testMatchingURIs() throws Exception {
URI uri1 = new URI("http://example.com/path");
URI uri2 = new URI("http://example.com/path/subpath");
assertTrue(URIMatcher.matchesURI(uri1, uri2));
}
@Test
public void testNonMatchingScheme() throws Exception {
URI uri1 = new URI("http://example.com/path");
URI uri2 = new URI("https://example.com/path");
assertFalse(URIMatcher.matchesURI(uri1, uri2));
}
@Test
public void testNonMatchingHost() throws Exception {
URI uri1 = new URI("http://example.com/path");
URI uri2 = new URI("http://example.org/path");
assertFalse(URIMatcher.matchesURI(uri1, uri2));
}
Code language: PHP (php)
@Test
public void testMatchingWithDefaultPort() throws Exception {
URI uri1 = new URI("http://example.com/path");
URI uri2 = new URI("http://example.com:80/path/subpath");
assertTrue(URIMatcher.matchesURI(uri1, uri2));
}
@Test
public void testNonMatchingPort() throws Exception {
URI uri1 = new URI("http://example.com:8080/path");
URI uri2 = new URI("http://example.com:9090/path");
assertFalse(URIMatcher.matchesURI(uri1, uri2));
}
@Test
public void testNonMatchingPath() throws Exception {
URI uri1 = new URI("http://example.com/path1");
URI uri2 = new URI("http://example.com/path2");
assertFalse(URIMatcher.matchesURI(uri1, uri2));
}
@Test
public void testCaseInsensitiveSchemeAndHost() throws Exception {
URI uri1 = new URI("HTTP://EXAMPLE.COM/path");
URI uri2 = new URI("http://example.com/path/subpath");
assertTrue(URIMatcher.matchesURI(uri1, uri2));
}
@Test
public void testWithQueryAndFragment() throws Exception {
URI uri1 = new URI("http://example.com/path");
URI uri2 = new URI("http://example.com/path/subpath?
query=value#fragment");
assertTrue(URIMatcher.matchesURI(uri1, uri2));
}
}
Code language: PHP (php)
Chiaramente il codice non è da prendere come oro colato, ma come base di partenza per scrivere i test che ci servono. Sicuramente non ci dobbiamo aspettare che il modello ci dia dei test che coprano tutti i casi possibili e che sia in grado di farlo per qualsiasi parte di codice che abbiamo scritto, ma se manteniamo il nostro approccio al codice di tipo KISS (Keep It Simple Stupid) possiamo ottenere dei risultati molto buoni.
Sicurezza
Un altro ambito in cui un LLM può essere molto utile è la sicurezza. Scrivere codice sicuro non è sempre facile e richiede una certa esperienza e conoscenza delle best practice.
Esistono tool che aiutano in tutte le direzioni a scrivere codice migliore, ma poter avere qualcosa di integrato o alla peggio alimentabile a prompt, è sicuramente un passo avanti.
Per esempio, sempre partendo dal metodo che stiamo analizzando ho provato a chiedere
Mi dai dei suggerimenti per rendere il codice più sicuro?
E ho ottenuto qualcosa che mi ero già chiesto mentre guardavo il metodo.
Gestione dei null:
Il metodo attuale non controlla se gli URI in input sono null. Aggiungi controlli all’inizio del metodo:
if (uri1 == null || uri2 == null) {
throw new IllegalArgumentException("URIs cannot be null");
}
Code language: JavaScript (javascript)
In questo caso ho tagliato i suggerimenti dati: è però importante notare che il modello è in grado di dare dei suggerimenti molto utili per migliorare la sicurezza del codice e in questo caso si è focalizato subito sulla normalizzazione dei parametri, oltre ad altri suggerimenti.
Refactoring
Anche in caso di refactoring un LLM può aiutarci: non è detto che il risultato sia effettivamente più efficiente rispetto al codice di partenza, ma avere delle idee su come migliorare il codice è sempre utile.
In questo caso ho provato a chiedere a ChatGPT:
Puoi fare un refactoring per velocizzare questo codice?
E ho ottenuto:
public static boolean matchesURI(URI uri1, URI uri2) {
// Memorizza lo schema, l'host e la porta una sola volta.
String scheme1 = uri1.getScheme();
String scheme2 = uri2.getScheme();
if (scheme1 == null || !scheme1.equalsIgnoreCase(scheme2)) {
return false;
}
String host1 = uri1.getHost();
String host2 = uri2.getHost();
if (host1 == null || !host1.equalsIgnoreCase(host2)) {
return false;
}
// Porta normalizzata una volta.
int port1 = HttpClient.normalizePort(scheme1, uri1.getPort());
int port2 = HttpClient.normalizePort(scheme2, uri2.getPort());
if (port1 != port2) {
return false;
}
// Confronta il percorso alla fine.
String path1 = uri1.getPath();
String path2 = uri2.getPath();
return path2.startsWith(path1);
}
Code language: JavaScript (javascript)
Probabilmente non abbiamo guadagnato nulla dal punto di vista prestazionale, ma dal punto della chiarezza il metodo ora è molto più chiaro e facile da leggere perché mette in fila tutti i controlli ed evita un codice innestato a tre livelli.
Anche in questo caso stiamo parlando di codice molto semplice, dove il guadagno non è molto, ma in codice più complesso il guadagno può essere molto maggiore ed avere un codice più chiaro potrebbe aiutarci, così come potrebbero aiutarci le riflessioni fatte dal modello rispetto a quanto scritto nel metodo che stiamo analizzando.
Boilerplate
Quanti di noi si sono trovati davanti a un nuovo progetto e hanno dovuto scrivere il boilerplate per farlo partire? Quanti di noi si sono trovati a dover scrivere il boilerplate per una nuova funzionalità? Quanti di noi si sono trovati a dover scrivere il boilerplate per una nuova classe?
Credo chiunque e ogni volta il passo iniziale richiede tempo e attenzione, perché dobbiamo stare attenti a non dimenticare nulla e a scrivere tutto correttamente. Occorre quindi studiare, provare, sbagliare e spesso riscrivere più volte fino a che non otteniamo il risultato che ci aspettiamo.
Per questo motivo esistono raccolte di boilerplate, che ci permettono di avere un punto di partenza per scrivere il codice che ci serve. Un esempio è questo progetto GitHub https://github.com/melvin0008/awesome-projects-boilerplates che raccoglie molti esempi di progetti minimali dai quali partire.
Anche in questo caso un LLM può aiutarci a scrivere il boilerplate che ci serve, partendo da un prompt generico come:
puoi scrivermi un progetto minimale VUE, composto da una prima maschera di login, che interroga il servizio https://reqres.in/api/login passando un json composto da due parametri “email” e “password” e in caso di risposta con codice 200 mi permetta di accedere ad un backend composto da: un pannello a sinistra con un menu e un pannello centrale nel quale visualizzare dei dati.
Nel pannello di sinistra deve essere riportato il tasto di logout
Evito di scrivere qui tutta la risposta: ho incluso il risultato in un progetto Github https://github.com/matteobaccan/VUEdashboard.
Quanto ottenuto è primordiale, ma permette di partire avendo qualche idea in testa per i junior e permette di velocizzare il lavoro per i senior. Nel mio caso ho preso quanto prodotto, creato un primo progetto VUE 3, adattato il codice e creato per un paio di problemi di nomenclatura ed ho pronto un esempio minimale col minimo dello sforzo.
Questo è un punto di partenza: ma quante volte ci siamo bloccato su questo punto? Quante volte abbiamo perso tempo a cercare di capire come fare partire un progetto? Quante volte abbiamo perso tempo a cercare di capire come fare partire una funzionalità? I modelli LLM dovrebbero accellerare questa fase e permetterci di concentrarci su quello che ci serve veramente.
La scrittura dei prompt
Si parla spesso di come scrivere i prompt per ottenere il risultato migliore, ma non sempre si capisce cosa significhi. Scrivere un prompt significa dare delle indicazioni chiare e precise su cosa ci aspettiamo dal modello e cosa vogliamo ottenere.
La prime e più banali indicazioni che vengono date in questo senso sono:
- Sii chiaro e conciso
- Sii specifico
- Includi il contesto e i dettagli necessari
- Definisci input e output
Se ribaltiamo queste indicazioni nell’ambito della programmazione, si capisce ben presto che non è così semplice come sembra. Non è semplice perché definire un prodotto software in modo chiaro, coinciso e specifico non è semplice e richiede una certa esperienza e conoscenza delle best practice. Oltre a questo, più il progetto software è articolato e più capiamo che non è possibile definirlo in questo modo.
Quante volte vi siete trovato di fronte a delle specifiche che sono variate nel tempo, che non erano chiare, che non erano specifiche e che non includevano il contesto e i dettagli necessari? Quante volte vi siete trovati a dover scrivere del codice senza sapere esattamente cosa si aspettava il cliente?
In questo ambito poter chiedere a un LLM un risultato che sia quanto voluto dal cliente diventa una operazione impossibile.
Quali prompt scrivere?
Abbiamo capito che un prompt gigante diventa un obiettivo impossibile da raggiungere, ma un prompt troppo piccolo non ci permette di ottenere il risultato che vogliamo. Quindi cosa fare?
La soluzione è quella di scrivere prompt che siano il più possibile specifici e che includano il contesto e i dettagli necessari, ma che siano anche il più possibile chiari e concisi. Un esempio di prompt che potremmo scrivere per ottenere il risultato che vogliamo è:
Scrivi una funzione C# per trovare il fattoriale di un numero dato.
In questo caso abbiamo specificato il linguaggio di programmazione, abbiamo specificato la funzionalità richiesta e abbiamo incluso il contesto e i dettagli necessari. Questo ci permette di ottenere il risultato che vogliamo in modo più efficace e preciso.
Oltre a questi ricordiamoci che i modelli fanno tesoro di quanto appreso. Per questo motivo entrare in un contesto sicuramente affrontato da altri programmatori aumenta la probabilità di ottenere un risultato migliore.
Diversamente, più il contesto è specifico e più è probabile che il modello non sia in grado di dare una risposta corretta, pur avendo a disposizione tutti i dati necessari e una descrizione chiara e concisa.
In questo caso, più che un LLM alimentato a prompt è preferibile usare un Copilot, che è in grado di analizzare il codice e di darci una risposta più inerente al nostro contesto e alle nostre necessità.
Quanto è fastidioso commentare il codice
Alzi la mano chi non ha scritto una o più volte del codice senza inserire una riga di commento. Alzi la mano chi ha dovuto commentare del codice scritto da altri. Alzi la mano chi ha dovuto commentare del codice scritto da altri e non ha capito cosa facesse.
Anche in questo caso un LLM può aiutarci a commentare il codice, partendo da un prompt generico come:
Aggiungi dei commenti al seguente codice:
private String readFile(String fileName){
BufferedReader br;
try {
br = new BufferedReader(new FileReader(fileName));
final StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
}
br.close();
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
Code language: JavaScript (javascript)
Come risultato avremo:
/**
* Legge il contenuto di un file e lo restituisce come stringa.
* @param fileName Il nome del file da leggere
* @return Il contenuto del file come stringa, o una stringa vuota in caso di errore
*/
private String readFile(String fileName) {
BufferedReader br;
try {
// Inizializza un BufferedReader per leggere il file
br = new BufferedReader(new FileReader(fileName));
// Crea un StringBuilder per costruire il contenuto del file
final StringBuilder sb = new StringBuilder();
// Legge la prima linea del file
String line = br.readLine();
// Continua a leggere finché ci sono linee nel file
while (line != null) {
// Aggiunge la linea corrente al StringBuilder
sb.append(line);
// Aggiunge un separatore di linea del sistema
sb.append(System.lineSeparator());
// Legge la prossima linea
line = br.readLine();
}
// Chiude il BufferedReader
br.close();
// Restituisce il contenuto del file come stringa
return sb.toString();
} catch (IOException e) {
// Gestisce eventuali errori di I/O
e.printStackTrace();
}
// Restituisce una stringa vuota in caso di errore
return "";
}
Code language: JavaScript (javascript)
In questo modo il codice ne risulta commentato e decisamente più chiaro e comprensibile. L’esempio che ho preso è molto semplice, ma il concetto di fondo è: avete un software che non conoscete e che dovete commentare? Chiedete a un LLM di farlo per voi e questo potrebbe trasformare la vostra codebase da un inferno a un paradiso.
Codereview sulle Pull Request
Allo stesso modo: immaginate di dover gestire le PR di un repository e volete velocizzare il lavoro. Su GitHub esistono una serie di action che possiamo creare per fare delle code review automatiche. Una di queste, ma ce ne sono molte altre simili, è https://github.com/magnificode-ltd/chatgpt-code-reviewer.
Questo action permette di fare una code review automatica delle PR, in modo ad indicare in modo più preciso cosa contiene e come migliorare la singola PR. Si tratta di un esempio, ma la cosa interessante è che possiamo studiare delle nostre action, da attivarsi alle PR, che possono aiutarci a velocizzare il lavoro e a migliorare la qualità del codice.
Analisi
Analizzare un progetto è sempre un’operazione complessa e che richiede tempo e attenzione. Un tempo approcciavo questa fase con lo studio dei documenti di progetto e delle grandi ricerche su internet, nella speranza di trovare qualcosa che mi aiutasse a capire meglio il progetto.
Queste ricerche mi portavano molte idee, anche se erano abbastanza dispersive: per quanto tu possa essere bravo a cercare, è molto facile che ti perda in un mare di informazioni e che non riesca a trovare quello che cerchi.
Ultimamente ho cambiato approccio: dopo una prima fase nella quale provo a inquadrare il progetto, inizio a farmi una chiacchierata vocale con ChatGTP.
Chiacchierare con un LLM ha alcuni vantaggi:
- è molto più veloce rispetto alla ricerca su internet: gli argomenti sono molto focalizzati ed è possibile ottenere delle risposte molto precise
- è possibile fare brainstorming con il modello: la ricerca su libri o siti web, oltre ad essere dispendiosa, non riesce sempre a farti fare dei collegamenti orizzontali tra le informazioni: spesso occorre aprire un nuovo tab e partire con una nuova ricerca
- funziona anche in auto: a volte passo ore in macchina verso un cliente e parlare con un assistente virtuale, anche se non è perfetto, è molto meglio che stare in silenzio
- informazioni continue: mi porta informazioni continue che, anche se non posso immediatamente utilizzare, mi permettono di avere un discreto numero di nuove idee
- parlato vs lettura: ultimo, ma non meno importante, il parlato ad alta voce è più facile da memorizzare rispetto alla semplice lettura
Da altri punti di vista, chi mi vede fermo al semaforo con la macchina che parla da sola, potrebbe pensare che sono pazzo, ma nella mia testa mi sento come Michael Knight che parla con KITT.
Traduzioni
Una degli aspetti più fastidiosi che possono capitare a un programmatore è quello di “tradurre” la documentazione del progetto in un’altra lingua. Questo è un lavoro noioso e che richiede tempo e attenzione, ma che è necessario per far capire il progetto a chi non parla la nostra lingua.
Da qualche anno ho iniziato a seguire produrre tutto il materiale che mi serve in formato markdown. Il markdown è un formato di scrittura leggero che permette di scrivere documenti in modo semplice e veloce, senza dover preoccuparsi troppo della formattazione.
Questo mi permette di scrivere la documentazione in modo più veloce e dalla documentazione estrarre velocemente delle slide con prodotti tipo Marp. Oltre a questo è molto semplice avere il diff fra versioni di documentazione e capire cosa è variato da un momento all’altro.
Di conseguenza mi sono trovato, mesi fa, nella situazione di tradurre la documentazione di un progetto in inglese. Fare una cosa del genere in modo manuale a colpi di copia e incolla da un file a un LLM e viceversa è un lavoro noioso e che richiede tempo e attenzione.
Stavo quasi per scrivermi un programma da solo, quando ho scoperto che esistono delle estensioni per GitHub in grado di automatizzare la traduzione della documentazione tramite semplici Action. Sto parlando di GTP-Translate, https://github.com/3ru/gpt-translate.
Sono sicuro esistano altre estensioni di questo genere, ma questa si integrava perfettamente nel ciclo di vita dei progetti che gestisco e mi ha permesso di velocizzare il lavoro di traduzione della documentazione.
Tramite una semplice issue è possibile attivare l’action di GTP translate e indicare quale documento markdown tradurre e in quale lingua. In risposta, viene prodotta una PR, col documento tradotto, che deve essere semplicemente approvata.
Conclusioni
Quando si approccia un LLM è importante capire che non si tratta di una bacchetta magica che risolve tutti i problemi, ma di uno strumento che può aiutarci a velocizzare il lavoro e a migliorare la qualità del codice. Scrivere un prompt e sperare che, per magia, quello che abbiamo nella nostra testa sia magicamente convertito in codice esattamente come serve a noi, è qualcosa di impensabile.
Allo stesso modo pensare che esista un solo strumento di AI che risolve tutti i nostri problemi, ad ora, è qualcosa di sbagliato.
Il modo migliore col quale in programmatore dovrebbe approcciare un LLM è quello di capire quali sono i limiti dello strumento e quali sono i contesti in cui può essere utilizzato con successo e iniziare a catalogare mentalmente questi ambiti in modo che per ognuno di essi esista una tecnica o uno strumento che possa essere utilizzato con successo.
Allo stesso modo è impensabile poter riunire in un solo prompt un progetto: è molto più efficace dividere il progetto in parti e chiedere al modello di lavorare su queste parti, in modo da ottenere un risultato migliore e più preciso.
Anche nei contesti nei quali un modello non è in grado di generare del codice, è molto probabile sia in grado di documentarlo, di commentarlo, di tradurlo e di fare molte altre operazioni che possono velocizzare il nostro lavoro.
Non siamo ancora in un momento storico in cui questi tool sono in grado di fare tutto in modo automatico, ma un programmatore che sa come utilizzarli può ottenere dei risultati molto buoni e migliorare la qualità del suo lavoro.
Migliorare la propria qualità vuol dire essere sempre più efficaci e toglierci dalla testa il cappello da junior per metterci quello più pesante e sgualcito di un programmatore senior.