lua錯誤處理 - 藝 -_錯誤處理

本文件詳細介紹Lua中的錯誤處理機制,基於error_handling.lua示例文件。

1. 基本的錯誤檢查

基本錯誤檢查是錯誤處理的第一步,通過類型檢查和條件檢查來驗證輸入參數的有效性。

function divide(a, b)
    -- 基本的錯誤檢查
    if type(a) ~= "number" or type(b) ~= "number" then
        print("錯誤: 輸入必須是數字")
        return nil
    end
    
    if b == 0 then
        print("錯誤: 除數不能為零")
        return nil
    end
    
    return a / b
end

print("測試基本錯誤檢查:")
print("10 / 2 = ", divide(10, 2))
print("10 / 0 = ", divide(10, 0))
print("10 / 'a' = ", divide(10, 'a'))

2. assert 斷言

assert函數用於在開發階段檢查邏輯錯誤,當條件為假時拋出錯誤並中斷程序執行。

function squareRoot(x)
    -- 使用 assert 檢查參數
    assert(type(x) == "number", "參數必須是數字")
    assert(x >= 0, "參數必須是非負數")
    
    return math.sqrt(x)
end

print("測試 assert:")
print("sqrt(16) = ", squareRoot(16))

-- 注意:下面的調用會導致程序中斷,這裏我們註釋掉
print("\n以下是使用 assert 的錯誤示例(註釋掉避免程序中斷):")
print([[
-- squareRoot(-1)  -- 會拋出錯誤: assertion failed!  參數必須是非負數
-- squareRoot('a')  -- 會拋出錯誤: assertion failed!  參數必須是數字
]])

3. error 函數

error函數允許開發者主動拋出自定義錯誤,可以指定錯誤消息和調用層級。

function validateName(name)
    if type(name) ~= "string" then
        error("名稱必須是字符串類型", 2)  -- 2 表示錯誤位置指向調用 validateName 的地方
    end
    
    if string.len(name) == 0 then
        error("名稱不能為空", 2)
    end
    
    print("名稱驗證通過: " .. name)
    return true
end

4. pcall 保護調用

pcall(protected call)允許在保護模式下執行函數,捕獲可能發生的錯誤而不中斷程序執行。

function safeSquareRoot(x)
    -- pcall 返回兩個值:成功標誌和結果(或錯誤消息)
    local success, result = pcall(squareRoot, x)
    
    if success then
        return result  -- 成功時返回計算結果
    else
        -- 失敗時處理錯誤
        print("捕獲到錯誤: " .. result)
        return nil  -- 返回 nil 表示操作失敗
    end
end

print("使用 pcall 安全計算平方根:")
print("sqrt(25) = ", safeSquareRoot(25))
print("sqrt(-5) = ", safeSquareRoot(-5))
print("sqrt('text') = ", safeSquareRoot('text'))

5. xpcall 保護調用

xpcallpcall 的增強版,可以提供自定義的錯誤處理函數來獲取更詳細的錯誤信息。

-- 自定義錯誤處理函數
local function errorHandler(err)
    -- 獲取完整的錯誤堆棧信息
    local stackTrace = debug.traceback(err, 2)
    print("\n錯誤處理函數捕獲到錯誤:")
    print(stackTrace)
    -- 返回自定義的錯誤消息
    return "錯誤已處理: " .. err
end

function safeValidateName(name)
    -- xpcall 需要一個錯誤處理函數作為第二個參數
    local success, result = xpcall(validateName, errorHandler, name)
    
    if success then
        return result
    else
        print("驗證失敗: " .. result)
        return false
    end
end

print("使用 xpcall 安全驗證名稱:")
safeValidateName("張三")  -- 成功
safeValidateName(123)     -- 失敗,會調用錯誤處理函數
safeValidateName("")      -- 失敗,會調用錯誤處理函數

6. 自定義錯誤類型

在複雜應用中,可以創建自定義錯誤類型和錯誤對象,提供更豐富的錯誤信息和更靈活的錯誤處理。

-- 定義錯誤類型常量
local ErrorTypes = {
    ARGUMENT_ERROR = "參數錯誤",
    RUNTIME_ERROR = "運行時錯誤",
    IO_ERROR = "輸入輸出錯誤",
    NETWORK_ERROR = "網絡錯誤"
}

-- 創建錯誤對象
function createError(type, message)
    return {
        type = type,
        message = message,
        timestamp = os.time()
    }
end

-- 使用自定義錯誤的函數
function processUserData(userData)
    -- 參數檢查
    if type(userData) ~= "table" then
        return nil, createError(ErrorTypes.ARGUMENT_ERROR, "用户數據必須是表類型")
    end
    
    if not userData.name then
        return nil, createError(ErrorTypes.ARGUMENT_ERROR, "用户名不能為空")
    end
    
    -- 模擬處理成功
    print("處理用户數據: " .. userData.name)
    return {status = "success", userId = 1001}, nil
end

print("測試自定義錯誤類型:")

local result1, error1 = processUserData({name = "李四", age = 25})
if result1 then
    print("處理成功: userId = " .. result1.userId)
else
    print("處理失敗: [" .. error1.type .. "] " .. error1.message)
end

local result2, error2 = processUserData(123)
if result2 then
    print("處理成功")
else
    print("處理失敗: [" .. error2.type .. "] " .. error2.message)
end

7. 錯誤處理的最佳實踐

7.1 返回錯誤代碼/消息

對於可恢復的錯誤,返回錯誤信息而非拋出異常是一種良好實踐。

function openConfigFile(path)
    local file, err = io.open(path, "r")
    if not file then
        return nil, "無法打開配置文件: " .. err
    end
    return file, nil
end

7.2 分層錯誤處理

在不同層級添加上下文信息,使錯誤消息更有針對性。

function loadConfig()
    local file, err = openConfigFile("config.lua")
    if not file then
        -- 添加上下文信息
        return nil, "加載配置失敗: " .. err
    end
    
    -- 在這裏處理文件內容...
    file:close()
    return {status = "ok"}, nil
end

7.3 模擬try-catch模式

使用pcall可以模擬其他語言中的try-catch模式。

function try(block, catchBlock)
    local success, result = pcall(block)
    if not success then
        catchBlock(result)
    end
    return success, result
end

print("\n模擬 try-catch 模式:")
try(
    function()
        -- 可能拋出錯誤的代碼
        local a = nil
        a.someField = 10  -- 這裏會出錯,因為 a 是 nil
    end,
    function(errorMsg)
        -- 錯誤處理代碼
        print("捕獲到錯誤: " .. errorMsg)
    end
)

8. 日誌記錄與錯誤

日誌系統是錯誤處理的重要組成部分,可以記錄錯誤信息、調試信息和操作記錄。

-- 簡單的日誌系統
local Logger = {
    level = "INFO",  -- 日誌級別: DEBUG, INFO, WARNING, ERROR
    
    log = function(self, level, message)
        local levels = {DEBUG = 1, INFO = 2, WARNING = 3, ERROR = 4}
        
        -- 只有當日志級別高於或等於設置的級別時才記錄
        if levels[level] >= levels[self.level] then
            local timestamp = os.date("%Y-%m-%d %H:%M:%S")
            print("[" .. timestamp .. "] [" .. level .. "] " .. message)
        end
    end,
    
    debug = function(self, message)
        self:log("DEBUG", message)
    end,
    
    info = function(self, message)
        self:log("INFO", message)
    end,
    
    warning = function(self, message)
        self:log("WARNING", message)
    end,
    
    error = function(self, message)
        self:log("ERROR", message)
    end
}

-- 使用日誌系統
Logger:info("程序啓動")
Logger:debug("這是調試信息")  -- 不會顯示,因為默認級別是 INFO
Logger:warning("注意:配置文件未找到,使用默認配置")
Logger:error("致命錯誤:無法連接到數據庫")

9. 調試信息獲取

Lua提供了debug庫,可以獲取函數信息、調用堆棧和局部變量等調試信息。

function debugExample()
    -- 獲取當前函數名
    local funcName = debug.getinfo(1, "n").name
    print("當前函數名: " .. funcName)
    
    -- 獲取調用者信息
    local callerInfo = debug.getinfo(2, "nl")
    if callerInfo then
        print("調用者名稱: " .. (callerInfo.name or "<匿名>") .. ", 行號: " .. callerInfo.currentline)
    end
    
    -- 獲取局部變量信息
    print("\n局部變量:")
    local i = 1
    while true do
        local name, value = debug.getlocal(1, i)
        if not name then break end
        print("  " .. name .. " = " .. tostring(value))
        i = i + 1
    end
    
    -- 獲取堆棧跟蹤
    print("\n堆棧跟蹤:")
    print(debug.traceback())
end

debugExample()

10. 錯誤處理最佳實踐總結

10.1 錯誤恢復策略

  • 對於可恢復的錯誤,返回錯誤代碼/消息而非拋出異常
  • 對於不可恢復的錯誤,使用 error 函數拋出

10.2 保護性調用應用

  • 使用 pcall/xpcall 保護關鍵代碼段,避免整個程序崩潰
  • 為複雜操作提供安全執行包裝器

10.3 錯誤消息增強

  • 添加上下文信息使錯誤消息更有用
  • 使用結構化錯誤對象攜帶更多元數據

10.4 日誌記錄建議

  • 記錄錯誤日誌而非僅打印到控制枱
  • 使用不同日誌級別區分錯誤嚴重程度

10.5 開發階段實踐

  • 使用斷言檢查開發階段的邏輯錯誤
  • 利用調試庫獲取詳細的運行時信息

學習總結

錯誤處理是Lua程序設計中非常重要的一部分。通過本文件介紹的各種錯誤處理機制,你可以編寫更健壯、更可靠的Lua程序。在實際開發中,應根據具體場景選擇合適的錯誤處理策略,平衡程序的健壯性和開發效率。