Strukturalne obiekty błędów
Proszę przekazywać tabele jako obiekty błędów, aby przekazywać typ i kontekst.
Strukturalne obiekty błędów to bezpłatna lekcja Lua Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Lua Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Lua Academy zawiera 4 lekcji w sumie.
Dlaczego ustrukturyzowane błędy?
Zwykłe błędy tekstowe trudno obsługiwać programowo. Ustrukturyzowane obiekty błędów (tabele) zawierają informacje o typie i dane kontekstowe, a kod wywołujący może je analizować i odpowiednio na nie reagować. Umożliwia to wybór obsługi błędu bez analizowania tekstu.
-- Plain string: hard to handle programmatically
error("database error: connection refused")
-- Structured: type + data
error({
type = "DatabaseError",
code = "CONN_REFUSED",
host = "localhost",
port = 5432,
message = "connection refused"
})Wzorzec konstruktora błędów
Należy utworzyć funkcję fabrykującą dla każdego typu błędu. Fabryka buduje tabelę ze spójnymi polami: type, message oraz odpowiednim kontekstem. Metoda metatabeli __tostring sprawia, że błąd jest czytelnie wyświetlany.
local ErrorMT = {__tostring = function(e)
return string.format("[%s] %s", e.type, e.message)
end}
local function makeError(errType, msg, data)
local e = {type=errType, message=msg}
if data then for k,v in pairs(data) do e[k]=v end end
return setmetatable(e, ErrorMT)
end
local E = {
notFound = function(name) return makeError("NOT_FOUND","not found: "..name,{name=name}) end,
badInput = function(msg,field) return makeError("BAD_INPUT",msg,{field=field}) end,
}
local ok, err = pcall(error, E.notFound("user:42"))
print(tostring(err)) -- [NOT_FOUND] not found: user:42Sprawdzanie typów obiektów błędów
Po przechwyceniu błędu należy sprawdzić, czy jest on tabelą zawierającą znane pole type. Umożliwia to wybór różnych strategii odzyskiwania na podstawie typu błędu bez polegania na dopasowywaniu wzorców tekstowych.
local function handleRequest(fn)
local ok, err = pcall(fn)
if ok then return true end
if type(err) == "table" then
if err.type == "NOT_FOUND" then
print("404: " .. err.message)
elseif err.type == "BAD_INPUT" then
print("400: " .. err.message .. " (field: " .. (err.field or "?") .. ")")
else
print("500: unhandled error: " .. tostring(err))
end
else
print("500: " .. tostring(err))
end
return false
endHierarchia błędów
Hierarchię błędów można symulować, sprawdzając pola is_a lub używając metatabel. Potomne typy błędów dziedziczą pola typu nadrzędnego i mogą być traktowane jak typ nadrzędny przez kod, który nie potrzebuje szczegółowych informacji.
local function isError(e, errType)
if type(e) ~= "table" then return false end
return e.type == errType or e.parentType == errType
end
local function makeDbError(code, msg)
return {type="DbError:"..code, parentType="DbError", code=code, message=msg}
end
local err = makeDbError("TIMEOUT","query timed out")
print(isError(err, "DbError")) -- true
print(isError(err, "DbError:TIMEOUT")) -- true
print(isError(err, "NetworkError")) -- falseOpakowywanie błędów
Podczas przechwytywania i ponownego zgłaszania błędu należy opakować oryginalny błąd, aby dodać kontekst bez jego utraty. Opakowanie ma własny typ i przechowuje oryginalny błąd jako przyczynę.
local function wrapError(msg, cause)
return {
type = "WrappedError",
message = msg,
cause = cause,
}
end
local function loadConfig(path)
local ok, err = pcall(function()
local f = assert(io.open(path,"r"))
local content = f:read("a")
f:close()
return content
end)
if not ok then
error(wrapError("failed to load config: "..path, err))
end
end
local ok2, e = pcall(loadConfig, "missing.cfg")
if not ok2 then
print(e.message)
print("Caused by:", tostring(e.cause))
endKody błędów a typy błędów
Dwie popularne konwencje to kody błędów (liczbowe, takie jak kody statusu HTTP) oraz tekstowe typy błędów (nazwy opisujące znaczenie). Kody błędów łatwo porównywać liczbowo, a tekstowe typy są samodokumentujące. Wiele systemów używa obu rozwiązań.
local STATUS = {OK=200, NOT_FOUND=404, SERVER_ERROR=500, BAD_REQUEST=400}
local function makeStatusError(status, msg)
return {status=status, message=msg, type="HTTPError"}
end
local function handleError(e)
if e.status == STATUS.NOT_FOUND then
print("Resource not found:", e.message)
elseif e.status >= 500 then
print("Server error:", e.message)
else
print("Error", e.status, e.message)
end
end
handleError(makeStatusError(404, "user not found"))Stos błędów (łańcuch przyczyn)
Gdy jeden błąd jest spowodowany innym, należy połączyć je w łańcuch. Zapewnia to pełny obraz tego, co poszło nie tak na każdej warstwie aplikacji. Łańcuch można rozwinąć, aby zarejestrować lub wyświetlić pełną historię błędu.
local function unwindCause(e, depth)
depth = depth or 0
local pad = string.rep(" ", depth)
if type(e) == "table" then
print(pad .. (e.type or "Error") .. ": " .. (e.message or "?"))
if e.cause then unwindCause(e.cause, depth+1) end
else
print(pad .. tostring(e))
end
end
local inner = {type="IoError", message="permission denied"}
local outer = {type="ConfigError", message="cannot load config", cause=inner}
unwindCause(outer)
-- ConfigError: cannot load config
-- IoError: permission deniedBłąd w kontekście callbacku
Gdy błędy występują wewnątrz callbacków (modułów obsługi zdarzeń, iteratorów), propagują się do kodu wywołującego callback. Należy użyć pcall, aby je przechwycić i zgłosić wraz z informacją, który callback się nie powiódł.
local function runCallbacks(callbacks, data)
local errors = {}
for name, fn in pairs(callbacks) do
local ok, err = pcall(fn, data)
if not ok then
errors[#errors+1] = {callback=name, error=err}
end
end
return errors
end
local cbs = {
validate = function(d) assert(d.name, "name required") end,
transform = function(d) d.name = d.name:upper() end,
}
local errs = runCallbacks(cbs, {})
for _, e in ipairs(errs) do
print(e.callback, "->", e.error)
endWyświetlanie szczegółów błędu
Funkcja pomocnicza, która wyświetla obiekt błędu w ustrukturyzowany i czytelny sposób, obsługując zarówno błędy tekstowe, jak i błędy będące tabelami. Jest to przydatne na granicach aplikacji, gdzie błędy są rejestrowane lub wyświetlane użytkownikom.
local function printError(err, prefix)
prefix = prefix or "Error"
if type(err) ~= "table" then
print(prefix .. ": " .. tostring(err))
return
end
print(prefix .. " [" .. (err.type or "unknown") .. "]")
print(" Message: " .. (err.message or "?"))
for k, v in pairs(err) do
if k ~= "type" and k ~= "message" and k ~= "cause" then
print(" " .. k .. ": " .. tostring(v))
end
end
if err.cause then printError(err.cause, " Caused by") end
endAsercje z ustrukturyzowanymi błędami
Należy utworzyć assertT (assert z typowanymi błędami), który zgłasza ustrukturyzowany błąd zamiast zwykłego tekstu. Ułatwia to kodowi wywołującemu testowanie konkretnych typów błędów.
local function assertT(cond, errType, msg, data)
if not cond then
local e = {type=errType, message=msg}
if data then for k,v in pairs(data) do e[k]=v end end
error(e, 2)
end
return cond
end
local function createUser(name, age)
assertT(type(name)=="string", "BAD_INPUT", "name must be string", {field="name"})
assertT(age >= 0 and age <= 150, "BAD_INPUT", "invalid age", {field="age", value=age})
return {name=name, age=age}
end
local ok, err = pcall(createUser, "Alice", -5)
if not ok then print(err.type, err.field, err.value) endSzybkie sprawdzenie
Jaka jest główna zaleta przekazania tabeli do error() zamiast tekstu?
Podsumowanie: ustrukturyzowane błędy
Podsumowanie:
- Do
error()należy przekazywać tabele, aby tworzyć ustrukturyzowane błędy, które można analizować - Należy uwzględnić: type, message oraz odpowiednie pola kontekstowe
- Należy dodać
__tostring, aby uzyskać czytelne dane wyjściowe - Błędy należy opakowywać, aby dodać kontekst bez utraty przyczyny
- W modułach obsługi należy wybierać obsługę na podstawie typu: sprawdzać
err.type, a nie wzorce tekstowe
Często zadawane pytania
Czy lekcja „Strukturalne obiekty błędów” jest bezpłatna?
Tak — pełny tekst „Strukturalne obiekty błędów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Lua Academy, przejdź na CoddyKit PRO. Kurs Lua Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Strukturalne obiekty błędów”?
Proszę przekazywać tabele jako obiekty błędów, aby przekazywać typ i kontekst. Ćwiczysz Lua Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Lua Academy?
Nie wymagamy żadnego doświadczenia. Lua Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Strukturalne obiekty błędów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Lua Academy?
Tak. Każda lekcja Lua Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Funkcja error()
- Wywołania chronione za pomocą pcall
- xpcall i programy obsługi komunikatów
- Strukturalne obiekty błędów