Lua Academy · Oppitunti

Rakenteiset virheoliot

Välittäkää taulukoita virheolioina tyypin ja kontekstin välittämistä varten.

Oppitunti 4/412 vaihetta

Rakenteiset virheoliot on ilmainen Lua Academy-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Lua Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Lua Academy-kurssilla on yhteensä 4 oppituntia.

Miksi rakenteisia virheitä?

Pelkkien merkkijonomuotoisten virheiden ohjelmallinen käsittely on hankalaa. Rakenteiset virheoliot (taulut) sisältävät tyyppitiedot ja kontekstidataa, ja kutsujat voivat tutkia niitä ja toimia niiden perusteella. Näin virheiden perusteella voidaan ohjata suoritus ilman merkkijonojen jäsentämistä.

-- 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"
})

Virheen muodostimen malli

Luo jokaiselle virhetyypille virhetehtaan funktio. Tehdas muodostaa taulun, jossa on yhdenmukaiset kentät: tyyppi, viesti ja olennainen konteksti. __tostring-metametodi saa virheen tulostumaan selkeässä muodossa.

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

Virheolioiden tyypin tarkistaminen

Kun olette ottaneet virheen kiinni, tarkistakaa, onko se taulu, jolla on tunnettu tyyppikenttä. Näin voitte valita eri palautumisstrategian virhetyypin perusteella ilman merkkijonojen hahmontunnistusta.

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

Virhehierarkia

Voitte jäljitellä virhehierarkiaa tarkistamalla is_a-kenttiä tai käyttämällä metatauluja. Lapsivirhetyypit perivät ylätason virhetyypin kentät, ja koodi, joka ei tarvitse tarkempia tietoja, voi käsitellä niitä ylätason virheinä.

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

Virheiden kääriminen

Kun otatte virheen kiinni ja heitätte sen uudelleen, käärikää alkuperäinen virhe kontekstin lisäämiseksi menettämättä alkuperäistä virhettä. Kääreellä on oma tyyppinsä, ja se sisältää alkuperäisen virheen aiheuttajana.

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

Virhekoodit ja virhetyypit

Kaksi yleistä käytäntöä ovat virhekoodit (numeeriset, kuten HTTP-tilakoodit) ja virhetyyppimerkkijonot (semanttiset nimet). Virhekoodeja on helppo verrata numeerisesti, kun taas tyyppimerkkijonot kuvaavat itse itseään. Monet järjestelmät käyttävät molempia.

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

Virhepino (aiheuttajien ketju)

Kun virheen aiheuttaa toinen virhe, ketjuttakaa ne. Näin saatte kokonaiskuvan siitä, mikä meni vikaan sovelluksen kullakin tasolla. Purkakaa ketju, kun haluatte kirjata tai näyttää virheiden koko tapahtumaketjun.

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

Virheet callback-kontekstissa

Kun virheitä tapahtuu callback-funktioissa, kuten tapahtumankäsittelijöissä tai iteraattoreissa, ne välittyvät callback-funktion kutsujalle. Ottakaa ne kiinni pcall-kutsulla ja raportoikaa samalla konteksti siitä, mikä callback epäonnistui.

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

Virheen tietojen tulostaminen

Apufunktio, joka tulostaa virheolion rakenteisessa ja luettavassa muodossa ja käsittelee sekä merkkijono- että taulumuotoiset virheet. Tämä on hyödyllistä sovelluksen rajoilla, joissa virheet kirjataan tai näytetään käyttäjille.

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

Rakenteiset virheet assert-tarkistuksissa

Luo assertT (tyypitettyjä virheitä käyttävä assert), joka nostaa pelkän merkkijonon sijaan rakenteisen virheen. Näin kutsujien on helppo testata tiettyjä virhetyyppejä.

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

Pikakertaus

Mikä on taulun välittämisen tärkein etu merkkijonon välittämiseen verrattuna funktiolle error()?

Kertaus: rakenteiset virheet

Yhteenveto:

  • Välittäkää tauluja funktiolle error(), jotta virheet ovat rakenteisia ja tutkittavissa
  • Sisällyttäkää tyyppi, viesti ja olennaiset kontekstikentät
  • Lisätkää __tostring helposti luettavaa tulostusta varten
  • Käärikää virheet kontekstin lisäämiseksi menettämättä aiheuttajaa
  • Ohjatkaa käsittelyä tyypin perusteella: tarkistakaa err.type, älkää merkkijonohahmoja
Aloita maksutta

Opi Lua tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
40
Oppitunnit
159

Usein kysytyt kysymykset

Onko oppitunti ”Rakenteiset virheoliot” ilmainen?

Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Lua Academy-oppimispolun 3 oppituntia, myös oppitunnin “Rakenteiset virheoliot”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Lua Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Rakenteiset virheoliot”?

Välittäkää taulukoita virheolioina tyypin ja kontekstin välittämistä varten. Harjoittelet Lua Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Lua Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Lua Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.

Kuinka kauan ”Rakenteiset virheoliot”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Lua Academy-oppitunnilla?

Kyllä. Jokainen Lua Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. error()-funktio
  2. Suojatut kutsut pcall-funktiolla
  3. xpcall ja viestinkäsittelijät
  4. Rakenteiset virheoliot
← Takaisin: Lua Academy