當前位置: 妍妍網 > 碼農

OpenResty中Lua編碼的最佳實踐與規範

2024-03-08碼農

OpenResty

OpenResty® 是一個基於 Nginx 與 Lua 的高效能 Web 平台,其內部整合了大量精良的 Lua 庫、第三方模組以及大多數的依賴項。用於方便地搭建能夠處理超高並行、擴充套件性極高的動態 Web 套用、Web 服務和動態閘道器。

OpenResty® 透過匯聚各種設計精良的 Nginx 模組(主要由 OpenResty 團隊自主開發),從而將 Nginx 有效地變成一個強大的通用 Web 套用平台。這樣,Web 開發人員和系統工程師可以使用 Lua 手稿語言調動 Nginx 支持的各種 C 以及 Lua 模組,快速構造出足以勝任 10K 乃至 1000K 以上單機並行連線的高效能 Web 套用系統。

Lua簡介

Lua 以其簡潔優雅的設計和卓越的效能,在全球程式語言家族中獨樹一幟。它是一門輕量級、可嵌入式的手稿語言,設計之初便以高效、靈活和易於擴充套件為目標。Lua名字來源於葡萄牙語中的「月亮」,寓意其小巧卻蘊含強大能量。

Lua語法清晰簡潔,學習曲線平緩,適合快速開發和原型驗證,尤其在遊戲開發、網路編程、配置檔解析等領域擁有廣泛的套用。同時,Lua的跨平台特性使得它能夠在Windows、Linux、Mac OS等多種作業系統上自由執行。

Lua 編碼規範

縮排

在 OpenResty 中使用 4 個空格作為縮排的標記,雖然 Lua 並沒有這樣的語法要求。

--No
if a then
ngx.say("hello Tinywan")
end
--yes
if a then
ngx.say("hello Tinywan")
end

你可以在使用的編輯器中,把 tab 改為 4 個空格,來簡化操作。

空格

在操作符的兩邊,都需要用一個空格來做分隔:

--No
local i=1
local s = "Tinywan"
--Yes
local i = 1
local s = "Tinywan"

空行

不少開發者會把其他語言的開發習慣帶到 OpenResty 中來,比如在行尾增加一個分號。

--No
if a then
ngx.say("hello Tinywan");
end;

增加分號會讓 Lua 程式碼顯得非常醜陋,也是沒有必要的。另外,不要為了節省程式碼的行數,後者為了顯得「簡潔」,而把多行程式碼變為一行。這樣會在定位錯誤的時候不知道到底那一段程式碼出了問題:

--No
if a then ngx.say("hello Tinywan") end
--yes
if a then
ngx.say("hello Tinywan")
end

函式之間需要用兩個空行來做分隔:

--No
localfunction foo()
end
localfunction bar()
end
--Yes
localfunction foo()
end

localfunction bar()
end

如果有多個 if elseif 的分支,它們之間需要一個空行來做分隔:

--No
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
--Yes
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end


每行最大長度

每行不能超過 80 個字元,超過的話,需要換行並對齊:

--No
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--Yes
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
conf.default_conn_delay)

在換行對齊的時候,要體現出上下兩行的對應關系。就上面的範例而言,第二行函式的參數,要在第一行左括弧的右邊。

如果是字串拼接的對齊,需要把 .. 放到下一行中:

--No
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn" ..
"plugin-limit-conn")
--Yes
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
.. "plugin-limit-conn")

變量

應該永遠使用局部變量,不要使用全域變量:

--No
i = 1
s = "Tinywan"
--Yes
local i = 1
local s = "Tinywan"

變量命名使用 snake_case 風格:

--No
local IndexArr = 1
local str_Name = "Tinywan"
--Yes
local index_arr = 1
local str_name = "Tinywan"

對於常量要使用全部大寫:

--No
local max_int = 65535
local server_name = "Tinywan"
--Yes
local MAX_INT = 65535
local SERVER_NAME = "Tinywan"

陣列

使用 table.new 來預先分配陣列:

--No
local t = {}
for i = 1, 100 do
t[i] = i
end
--Yes
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
t[i] = i
end

不要在陣列中使用 nil :

--No
local t = {1, 2, nil, 3}

如果一定要使用空值,請用 ngx.null 來表示:

--No
local t = {1, 2, ngx.null, 3}

字串

不要在熱程式碼路徑上拼接字串:

--No
local s = ""
for i = 1, 100000 do
s = s .. "a"
end
--Yes
local t = {}
for i = 1, 100000 do
t[i] = "a"
end
local s = table.concat(t, "")

函式

函式的命名也同樣遵循 snake_case :

--No
localfunction testNginx()
end
--Yes
localfunction test_nginx()
end

函式應該盡可能早的返回:

--No
localfunction check(age, name)
local ret = true
if age < 20 then
ret = false
end
if name == "a"then
ret = false
end
-- do something else
return ret
end
--Yes
localfunction check(age, name)
if age < 20 then
returnfalse
end
if name == "a"then
returnfalse
end
-- do something else
returntrue
end

模組

所有 require 的庫都要 local 化:

--No
localfunction foo()
local ok, err = ngx.timer.at(delay, handler)
end
--Yes
local timer_at = ngx.timer.at
localfunction foo()
local ok, err = timer_at(delay, handler)
end

為了風格的統一, require ngx 也需要 local 化:


--No
local core = require("apisix.core")
local timer_at = ngx.timer.at
localfunction foo()
local ok, err = timer_at(delay, handler)
end
--Yes
local ngx = ngx
local require = require
local core = require("apisix.core")
local timer_at = ngx.timer.at
localfunction foo()
local ok, err = timer_at(delay, handler)
end

錯誤處理

對於有錯誤資訊返回的函式,必須對錯誤資訊進行判斷和處理:

--No
local sock = ngx.socket.tcp()
local ok = sock:connect("www.google.com", 80)
ngx.say("successfully connected to google!")
--Yes
local sock = ngx.socket.tcp()
local ok, err = sock:connect("www.google.com", 80)
if not ok then
ngx.say("failed to connect to google: ", err)
return
end
ngx.say("successfully connected to google!")

自己編寫的函式,錯誤資訊要作為第二個參數,用字串的格式返回:

--No
localfunction foo()
local ok, err = func()
if not ok then
returnfalse
end
returntrue
end
--No
localfunction foo()
local ok, err = func()
if not ok then
returnfalse, {msg = err}
end
returntrue
end
--Yes
localfunction foo()
local ok, err = func()
if not ok then
returnfalse"failed to call func(): " .. err
end
returntrue
end