0Pricing
Lua Academy · Lekcja

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:42

Sprawdzanie 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
end

Hierarchia 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"))     -- false

Opakowywanie 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))
end

Kody 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 denied

Błą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)
end

Wyś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
end

Asercje 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) end

Szybkie 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

  1. Funkcja error()
  2. Wywołania chronione za pomocą pcall
  3. xpcall i programy obsługi komunikatów
  4. Strukturalne obiekty błędów
← Powrót do Lua Academy