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.