Gestione degli Errori in Lua
La gestione degli errori e’ un aspetto fondamentale della programmazione. Lua offre un sistema semplice ma potente per gestire le situazioni di errore, basato sulle funzioni error(), pcall(), xpcall() e assert(). A differenza di altri linguaggi che usano eccezioni con try/catch, Lua adotta un approccio funzionale alla gestione degli errori.
La Funzione error()
La funzione error() interrompe l’esecuzione del programma e genera un messaggio di errore. Accetta come primo argomento un messaggio (stringa o qualsiasi valore) e come secondo argomento opzionale il livello dell’errore.
-- Errore semplice con stringa
error("Qualcosa e' andato storto")
-- Errore con un oggetto (tabella)
error({ codice = 404, messaggio = "Risorsa non trovata" })
L’esecuzione del programma si interrompe immediatamente al punto in cui viene chiamata error(), a meno che la chiamata non sia protetta da pcall() o xpcall().
Livelli di Errore
Il secondo parametro di error() indica il livello dell’errore, cioe’ a quale funzione nella catena di chiamate attribuire l’errore. Questo e’ utile per fare in modo che il messaggio di errore punti alla funzione che ha causato il problema, non a quella che lo ha rilevato.
-- Livello 1 (default): errore nella funzione corrente
local function valida(valore)
if type(valore) ~= "number" then
error("Atteso un numero, ricevuto: " .. type(valore), 1)
end
end
-- Livello 2: errore attribuito al chiamante
local function dividi(a, b)
if b == 0 then
error("Divisione per zero non consentita", 2)
end
return a / b
end
-- dividi(10, 0) -- L'errore punta alla riga che chiama dividi()
-- Livello 0: nessuna informazione sulla posizione
-- error("Errore generico", 0)
pcall: Chiamate Protette
La funzione pcall() (protected call) esegue una funzione in modalita protetta. Se la funzione viene eseguita senza errori, pcall restituisce true seguito dai valori di ritorno. Se si verifica un errore, restituisce false seguito dal messaggio di errore.
-- Sintassi base
local successo, risultato = pcall(funzione, arg1, arg2, ...)
-- Esempio pratico
local function dividi(a, b)
if b == 0 then
error("Divisione per zero")
end
return a / b
end
local ok, valore = pcall(dividi, 10, 2)
print(ok, valore) -- true 5.0
local ok, errore = pcall(dividi, 10, 0)
print(ok, errore) -- false input:4: Divisione per zero
pcall e’ l’equivalente di un blocco try/catch in altri linguaggi. Cattura qualsiasi errore generato all’interno della funzione protetta.
-- Pattern comune: gestione errore con if
local ok, risultato = pcall(function()
-- codice che potrebbe generare errori
local file = io.open("inesistente.txt", "r")
if not file then
error("File non trovato")
end
return file:read("*a")
end)
if ok then
print("Contenuto: " .. risultato)
else
print("Errore: " .. risultato)
end
xpcall: Chiamate Protette con Handler
La funzione xpcall() funziona come pcall(), ma accetta un secondo parametro: una funzione di gestione dell’errore (error handler). Questa funzione riceve il messaggio di errore e puo’ arricchirlo con informazioni aggiuntive, come lo stack trace.
-- Sintassi: xpcall(funzione, handler, arg1, arg2, ...)
local function handler(err)
return "ERRORE CATTURATO: " .. err .. "\n" .. debug.traceback()
end
local function funzioneRischiosa()
error("Qualcosa e' esploso")
end
local ok, messaggio = xpcall(funzioneRischiosa, handler)
if not ok then
print(messaggio)
-- ERRORE CATTURATO: input:2: Qualcosa e' esploso
-- stack traceback:
-- input:2: in function 'funzioneRischiosa'
-- ...
end
xpcall e’ particolarmente utile in produzione, dove lo stack trace aiuta a diagnosticare i problemi.
-- Handler personalizzato con logging
local function errorHandler(err)
local info = debug.getinfo(2, "Sl")
local posizione = ""
if info then
posizione = info.source .. ":" .. info.currentline .. " "
end
return {
messaggio = err,
posizione = posizione,
traceback = debug.traceback("", 2)
}
end
local ok, dettagli = xpcall(function()
error("Errore critico nel modulo")
end, errorHandler)
if not ok then
print("Messaggio: " .. dettagli.messaggio)
print("Traceback: " .. dettagli.traceback)
end
assert
La funzione assert() verifica una condizione e genera un errore se questa e’ falsa o nil. E’ ideale per le precondizioni e le validazioni.
-- Sintassi: assert(condizione, messaggio)
local nome = "Mario"
assert(type(nome) == "string", "Il nome deve essere una stringa")
-- Uso comune con io.open
local file = assert(io.open("config.txt", "r"), "Impossibile aprire config.txt")
local contenuto = file:read("*a")
file:close()
-- assert restituisce i suoi argomenti se la condizione e' vera
local valore = assert(tonumber("42"))
print(valore) -- 42
-- Errore se la conversione fallisce
-- local valore = assert(tonumber("abc"), "Conversione numerica fallita")
Errori come Oggetti
In Lua, error() non e’ limitata alle stringhe. Si possono lanciare oggetti complessi (tabelle) come errori, permettendo una gestione piu’ strutturata.
-- Definizione di errori personalizzati
local function creaErrore(tipo, messaggio, dettagli)
return {
tipo = tipo,
messaggio = messaggio,
dettagli = dettagli,
timestamp = os.time()
}
end
local function cercaUtente(id)
if type(id) ~= "number" then
error(creaErrore("VALIDAZIONE", "ID deve essere un numero", { id = id }))
end
if id <= 0 then
error(creaErrore("VALIDAZIONE", "ID deve essere positivo", { id = id }))
end
-- Simula un database
local utenti = { [1] = "Mario", [2] = "Lucia" }
if not utenti[id] then
error(creaErrore("NON_TROVATO", "Utente non trovato", { id = id }))
end
return utenti[id]
end
local ok, risultato = pcall(cercaUtente, 99)
if not ok then
if type(risultato) == "table" then
print("Tipo: " .. risultato.tipo)
print("Messaggio: " .. risultato.messaggio)
else
print("Errore: " .. tostring(risultato))
end
end
-- Tipo: NON_TROVATO
-- Messaggio: Utente non trovato
Pattern Comuni per la Gestione degli Errori
Pattern “ok, risultato”
Il pattern piu’ diffuso in Lua prevede che le funzioni restituiscano nil e un messaggio di errore in caso di fallimento.
local function leggiFile(percorso)
local file, err = io.open(percorso, "r")
if not file then
return nil, "Impossibile aprire: " .. err
end
local contenuto, err = file:read("*a")
file:close()
if not contenuto then
return nil, "Impossibile leggere: " .. err
end
return contenuto
end
local contenuto, err = leggiFile("dati.txt")
if not contenuto then
print("Errore: " .. err)
else
print("Letto: " .. #contenuto .. " bytes")
end
Wrapper di sicurezza
local function safe(fn)
return function(...)
local ok, risultato = pcall(fn, ...)
if ok then
return risultato
else
print("[ERRORE] " .. tostring(risultato))
return nil
end
end
end
local dividiSicuro = safe(function(a, b)
assert(b ~= 0, "Divisione per zero")
return a / b
end)
print(dividiSicuro(10, 2)) -- 5.0
print(dividiSicuro(10, 0)) -- [ERRORE] ... Divisione per zero
-- nil
Best Practices
Seguire queste regole aiuta a scrivere codice Lua robusto:
- Usa pcall per operazioni rischiose: accesso a file, rete, parsing di dati esterni.
- Preferisci il pattern nil/errore: per errori attesi e recuperabili, restituisci
nil, messaggio. - Usa error() per errori gravi: quando l’errore indica un bug o una situazione irrecuperabile.
- Usa assert() per le precondizioni: valida i parametri all’inizio delle funzioni.
- Non ignorare gli errori: controlla sempre il valore di ritorno di pcall e delle funzioni che possono fallire.
- Usa xpcall in produzione: lo stack trace e’ prezioso per il debugging.
- Documenta gli errori: indica chiaramente nella documentazione quali errori una funzione puo’ generare.
-- Esempio di funzione ben documentata
--- Carica e analizza un file JSON.
--- @param percorso string Il percorso del file
--- @return table I dati analizzati
--- @error "FILE_NON_TROVATO" se il file non esiste
--- @error "JSON_INVALIDO" se il contenuto non e' JSON valido
local function caricaJSON(percorso)
assert(type(percorso) == "string", "Il percorso deve essere una stringa")
local file, err = io.open(percorso, "r")
if not file then
error({ tipo = "FILE_NON_TROVATO", messaggio = err })
end
local contenuto = file:read("*a")
file:close()
-- In un caso reale qui si userebbe una libreria JSON
local ok, dati = pcall(function()
-- parsing JSON...
return {}
end)
if not ok then
error({ tipo = "JSON_INVALIDO", messaggio = dati })
end
return dati
end
Conclusione
La gestione degli errori in Lua e’ flessibile e potente. Il sistema basato su pcall/xpcall offre un controllo totale sugli errori senza appesantire la sintassi del linguaggio. Combinando il pattern nil/errore per le situazioni recuperabili con error() per i casi gravi, e proteggendo le operazioni rischiose con pcall, si ottiene codice robusto e manutenibile.