Структурированные объекты ошибок
Передавайте таблицы как объекты ошибок, чтобы сообщать их тип и контекст.
«Структурированные объекты ошибок» — бесплатный урок Lua Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Lua Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Lua Academy содержит 4 уроков всего.
Зачем нужны структурированные ошибки
Обычные строковые ошибки трудно обрабатывать программно. Структурированные объекты ошибок (таблицы) содержат сведения о типе и контекстные данные, поэтому вызывающий код может проверять их и принимать соответствующие меры. Это позволяет направлять обработку по ошибкам без разбора строк.
-- 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"
})Шаблон конструктора ошибки
Создайте фабричную функцию для каждого типа ошибок. Фабрика формирует таблицу с согласованными полями: типом, сообщением и любым релевантным контекстом. Метаметод __tostring позволяет красиво выводить ошибку.
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Проверка типов объектов ошибок
После перехвата ошибки проверьте, является ли она таблицей с известным полем типа. Это позволяет выбирать разные стратегии восстановления на основе типа ошибки, не полагаясь на сопоставление со строковыми шаблонами.
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Иерархия ошибок
Иерархию ошибок можно имитировать, проверяя поля is_a, или с помощью метатаблиц. Дочерние типы ошибок наследуют поля родительского типа и могут обрабатываться как родительские кодом, которому не нужны специфические сведения.
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Оборачивание ошибок
При перехвате и повторном возбуждении оберните исходную ошибку, чтобы добавить контекст, не потеряв её. У оболочки есть собственный тип, и она содержит исходную ошибку как причину.
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Коды ошибок и типы ошибок
Распространены два соглашения: коды ошибок (числовые, например коды состояния HTTP) и строковые типы ошибок (смысловые имена). Коды ошибок удобно сравнивать численно, а строковые типы сами описывают своё назначение. Многие системы используют оба варианта.
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"))Стек ошибок (цепочка причин)
Если одна ошибка вызвана другой, объедините их в цепочку. Это даёт полную картину произошедшего на каждом уровне приложения. Разверните цепочку, чтобы записать или показать всю историю ошибки.
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Ошибки в контексте обратного вызова
Если ошибка возникает внутри обратного вызова (обработчика события, итератора), она распространяется к вызывающему коду обратного вызова. Используйте pcall, чтобы перехватить её и сообщить, какой именно обратный вызов завершился ошибкой.
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Вывод сведений об ошибке
Вспомогательная функция, которая выводит объект ошибки в структурированном и удобном для чтения виде, обрабатывая как строковые ошибки, так и ошибки-таблицы. Это полезно на границах приложения, где ошибки записываются в журнал или показываются пользователям.
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Проверка с помощью структурированных ошибок
Создайте assertT (проверку с типизированными ошибками), которая возбуждает структурированную ошибку вместо обычной строки. Это упрощает проверку вызывающим кодом конкретных типов ошибок.
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Быстрая проверка
В чём главное преимущество передачи таблицы в error() вместо строки?
Итоги: структурированные ошибки
Краткое содержание:
- Передавайте таблицы в
error(), чтобы ошибки были структурированными и доступными для проверки - Включайте поля типа, сообщения и релевантного контекста
- Добавляйте
__tostringдля удобного вывода - Оборачивайте ошибки, чтобы добавить контекст, не потеряв причину
- В обработчиках направляйте обработку по типу: проверяйте
err.type, а не строковые шаблоны
Часто задаваемые вопросы
Урок «Структурированные объекты ошибок» бесплатный?
Да — полный текст урока «Структурированные объекты ошибок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Lua Academy, подпишись на CoddyKit PRO. Курс Lua Academy содержит 4 уроков всего.
Чему я научусь в уроке «Структурированные объекты ошибок»?
Передавайте таблицы как объекты ошибок, чтобы сообщать их тип и контекст. Ты практикуешь Lua Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Lua Academy?
Предыдущий опыт не требуется. Lua Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Структурированные объекты ошибок»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Lua Academy?
Да. Каждый урок Lua Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Функция error()
- Защищённые вызовы с pcall
- xpcall и обработчики сообщений
- Структурированные объекты ошибок