使用lua實現try-catch異常捕獲

lua原生並沒有提供try-catch的語法來捕獲異常處理,但是提供了pcall/xpcall等接口,可在保護模式下執行lua函數。

因此,可以通過封裝這兩個接口,來實現try-catch塊的捕獲機制。

我們可以先來看下,封裝後的try-catch使用方式:

try
{
    -- try 代碼塊
    function ()
        error("error message")
    end,

    -- catch 代碼塊
    catch 
    {
        -- 發生異常後,被執行
        function (errors)
            print(errors)
        end
    }
}

上面的代碼中,在try塊內部認爲引發了一個異常,並且拋出錯誤消息,在catch中進行了捕獲,並且將錯誤消息進行輸出顯示。

這裏除了對pcall/xpcall進行了封裝,用來捕獲異常信息,還利用了lua的函數調用語法特性,在只有一個參數傳遞的情況下,lua可以直接傳遞一個table類型,並且省略()

其實try後面的整個{...} 都是一個table而已,作爲參數傳遞給了try函數,其具體實現如下:

function try(block)

    -- get the try function
    local try = block[1]
    assert(try)

    -- get catch and finally functions
    local funcs = block[2]
    if funcs and block[3] then
        table.join2(funcs, block[2])
    end

    -- try to call it
    local ok, errors = pcall(try)
    if not ok then

        -- run the catch function
        if funcs and funcs.catch then
            funcs.catch(errors)
        end
    end

    -- run the finally function
    if funcs and funcs.finally then
        funcs.finally(ok, errors)
    end

    -- ok?
    if ok then
        return errors
    end
end

可以看到這裏用了pcall來實際調用try塊裏面的函數,這樣就算函數內部出現異常,也不會中斷程序,pcall會返回false表示運行失敗

並且返回實際的出錯信息,如果想要自定義格式化錯誤信息,可以通過調用xpcall來替換pcall,這個接口與pcall相比,多了個錯誤處理函數:

local ok, errors = xpcall(try, debug.traceback)

你可以直接傳入debug.traceback,使用默認的traceback處理接口,也可以自定義一個處理函數:

-- traceback
function my_traceback(errors)

    -- make results
    local level = 2    
    while true do    

        -- get debug info
        local info = debug.getinfo(level, "Sln")

        -- end?
        if not info or (info.name and info.name == "xpcall") then
            break
        end

        -- function?
        if info.what == "C" then
            results = results .. string.format("    [C]: in function '%s'\n", info.name)
        elseif info.name then 
            results = results .. string.format("    [%s:%d]: in function '%s'\n", info.short_src, info.currentline, info.name)    
        elseif info.what == "main" then
            results = results .. string.format("    [%s:%d]: in main chunk\n", info.short_src, info.currentline)    
            break
        else
            results = results .. string.format("    [%s:%d]:\n", info.short_src, info.currentline)    
        end

        -- next
        level = level + 1    
    end    

    -- ok?
    return results
end

-- 調用自定義traceback函數
local ok, errors = xpcall(try, my_traceback)

回到try-catch上來,通過上面的實現,會發現裏面其實還有個finally的處理,這個的作用是對於try{}代碼塊,不管是否執行成功,都會執行到finally塊中

也就說,其實上面的實現,完整的支持語法是:try-catch-finally模式,其中catch和finally都是可選的,根據自己的實際需求提供

例如:

try
{
    -- try 代碼塊
    function ()
        error("error message")
    end,

    -- catch 代碼塊
    catch 
    {
        -- 發生異常後,被執行
        function (errors)
            print(errors)
        end
    },

    -- finally 代碼塊
    finally 
    {
        -- 最後都會執行到這裏
        function (ok, errors)
            -- 如果try{}中存在異常,ok爲true,errors爲錯誤信息,否則爲false,errors爲try中的返回值
        end
    }
}

或者只有finally塊:

try
{
    -- try 代碼塊
    function ()
        return "info"
    end,

    -- finally 代碼塊
    finally 
    {
        -- 由於此try代碼沒發生異常,因此ok爲true,errors爲返回值: "info"
        function (ok, errors)
        end
    }
}

處理可以在finally中獲取try裏面的正常返回值,其實在僅有try的情況下,也是可以獲取返回值的:

-- 如果沒發生異常,result 爲返回值:"xxxx",否則爲nil
local result = try
{
    function ()
        return "xxxx"
    end
}

可以看到,這個基於pcall的try-catch-finally異常捕獲封裝,使用上還是非常靈活的,而且其實現相當的簡單

這也充分說明了lua是一門已非常強大靈活,又非常精簡的語言。

xmake的自定義腳本、插件開發中,也是完全基於此異常捕獲機制

這樣使得擴展腳本的開發非常的精簡可讀,省去了繁瑣的if err ~= nil then返回值判斷,在發生錯誤時,xmake會直接拋出異常進行中斷,然後高亮提示詳細的錯誤信息。

例如:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    -- 在編譯完ios程序後,對目標程序進行ldid簽名
    after_build(function (target))
        os.run("ldid -S %s", target:targetfile())
    end

只需要一行os.run就行了,也不需要返回值判斷是否運行成功,因爲運行失敗後,xmake會自動拋異常,中斷程序並且提示錯誤

如果你想在運行失敗後,不直接中斷xmake,繼續往下運行,可以自己加個try快就行了:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    after_build(function (target))
        try
        {
            function ()
                os.run("ldid -S %s", target:targetfile())
            end
        }
    end

如果還想捕獲出錯信息,可以再加個catch:

target("test")
    set_kind("binary")
    add_files("src/*.c")

    after_build(function (target))
        try
        {
            function ()
                os.run("ldid -S %s", target:targetfile())
            end,
            catch 
            {
                function (errors)
                    print(errors)
                end
            }
        }
    end

不過一般情況下,在xmake中寫自定義腳本,是不需要手動加try-catch的,直接調用各種api,出錯後讓xmake默認的處理程序接管,直接中斷就行了。。

最後附上try-catch-finally實現的相關完整源碼:


個人主頁:TBOOX開源工程
原文出處:http://tboox.org/cn/2016/12/14/try-catch/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章