0Pricing
Lua Academy · درس

كائنات الأخطاء المهيكلة

مرّر الجداول بوصفها كائنات أخطاء لتوضيح النوع والسياق

كائنات الأخطاء المهيكلة درس مجاني في 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"
})

نمط مُنشئ الأخطاء

أنشئ دالة مصنع لكل نوع من أنواع الأخطاء. ينشئ المصنع جدولًا يحتوي على حقول متسقة: النوع، والرسالة، وأي سياق ذي صلة. تجعل دالة metamethod المسماة __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 يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الدالة error()
  2. الاستدعاءات المحمية باستخدام pcall
  3. ‏xpcall ومعالجات الرسائل
  4. كائنات الأخطاء المهيكلة
← العودة إلى Lua Academy