ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง
ส่งตารางเป็นออบเจกต์ข้อผิดพลาดเพื่อสื่อชนิดและบริบท
ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง เป็นบทเรียน Lua Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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ไม่ใช่รูปแบบสตริง
คำถามที่พบบ่อย
บทเรียน “ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Lua Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Lua Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง”
ส่งตารางเป็นออบเจกต์ข้อผิดพลาดเพื่อสื่อชนิดและบริบท คุณปฏิบัติ Lua Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Lua Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Lua Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Lua Academy นี้ได้ไหม
ได้ บทเรียน Lua Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ฟังก์ชัน error()
- การเรียกใช้แบบป้องกันด้วย pcall
- xpcall และตัวจัดการข้อความ
- ออบเจกต์ข้อผิดพลาดแบบมีโครงสร้าง