00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.