很多開發語言都有自己的編碼規范,來告訴開發者這個領域內一些約定俗成的東西,讓大家寫的代碼風格保持一致,并且避免一些常見的陷阱。這對于新手來說是非常友好的,可以讓初學者快速準確地上手。比如 Python 的 PEP 80,就是其中的典范,幾乎所有的 Python 開發者都閱讀過這份 Python 作者執筆的編碼規范。
**讓開發者統一思想,按照規范來寫代碼,是一件非常重要的事情。**OpenResty 還沒有自己的編碼規范,有些開發者在提交 PR 后,會在代碼風格上被反復 review 和要求修改,消耗了大量本可避免的時間和精力。
其實,在 OpenResty 中,也有兩個可以幫你自動化檢測代碼風格的工具:luacheck 和 lj-releng。前者是 Lua 和 OpenResty 世界通用的檢測工具,后者則是 OpenResty 自己用 perl 寫的代碼檢測工具。
對我自己來說,我會在 VS Code 編輯器中安裝 luacheck 的插件,這樣在我寫代碼的時候就有工具來自動提示;而在項目的 CI 中,則是會把這兩個工具都運行一遍,比如:
luacheck -q lua./utils/lj-releng lua/*.lua lua/apisix/*.lua
畢竟,多一個工具的檢測總不是壞事。
但是,這兩個工具更多的是檢測全局變量、每行長度等這些最基礎的代碼風格,離 Python PEP 80 的詳細程度還有遙遠的距離,并且也沒有文檔給你參考。
所以今天,我就根據自己在OpenResty 相關開源項目中的經驗,總結了一下 OpenResty 的編碼風格文檔,這個規范也和一些常見的 API 網關比如 Kong、APISIX 的代碼風格是一致的。
縮進
在 OpenResty 中,我們使用 4 個空格作為縮進的標記,雖然 Lua 并沒有這樣的語法要求。下面是錯誤和正確的兩段代碼示例:
--No
if a then
ngx.say("hello")
end--yes
if a thenngx.say("hello")
end
為了方便,你可以在使用的編輯器中,把 tab 改為 4 個空格,來簡化操作。
空格
在操作符的兩邊,都需要用一個空格來做分隔。下面是錯誤和正確的兩段代碼示例:
--No
local i=1
local s = "apisix"--Yes
local i = 1
local s = "apisix"
空行
不少開發者會把其他語言的開發習慣帶到 OpenResty 中來,比如在行尾增加一個分號:
--No
if a thenngx.say("hello");
end;
但事實上,增加分號會讓 Lua 代碼顯得非常丑陋,也是沒有必要的。同時,你也不要為了節省代碼的行數,追求所謂的“簡潔”,而把多行代碼變為一行。這樣做會讓你在定位錯誤的時候,不知道到底是哪一段代碼出了問題:
--No
if a then ngx.say("hello") end--yes
if a thenngx.say("hello")
end
另外,函數之間需要用兩個空行來做分隔:
--No
local function foo()
endlocal function bar()
end--Yes
local function foo()
endlocal function bar()
end
如果有多個 if elseif 的分支,它們之間也需要一個空行來做分隔:
--No
if a == 1 thenfoo()
elseif a== 2 thenbar()
elseif a == 3 thenrun()
elseerror()
end--Yes
if a == 1 thenfoo()elseif a== 2 thenbar()elseif a == 3 thenrun()elseerror()
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 = "apisix"--Yes
local i = 1
local s = "apisix"
至于變量的命名,應該使用 snake_case 風格:
--No
local IndexArr = 1
local str_Name = "apisix"--Yes
local index_arr = 1
local str_name = "apisix"
而對于常量,則是要使用全部大寫的形式:
--No
local max_int = 65535
local server_name = "apisix"--Yes
local MAX_INT = 65535
local SERVER_NAME = "apisix"
數組
在OpenResty中,我們使用table.new 來預先分配數組:
--No
local t = {}
for i = 1, 100 dot[i] = iend--Yes
local new_tab = require "table.new"local t = new_tab(100, 0)for i = 1, 100 dot[i] = iend
另外注意,一定不要在數組中使用 nil:
--No
local t = {1, 2, nil, 3}
如果一定要使用空值,請用 ngx.null 來表示:
--Yes
local t = {1, 2, ngx.null, 3}
字符串
千萬不要在熱代碼路徑上拼接字符串:
--No
local s = ""
for i = 1, 100000 dos = s .. "a"
end--Yes
local t = {}
for i = 1, 100000 dot[i] = "a"
end
local s = table.concat(t, "")
函數
函數的命名也同樣遵循 snake_case:
--No
local function testNginx()
end--Yes
local function test_nginx()
end
并且,函數應該盡可能早地返回:
--No
local function check(age, name)local ret = trueif age < 20 thenret = falseendif name == "a" thenret = falseend-- do something else return ret --Yes
local function check(age, name)if age < 20 thenreturn falseendif name == "a" thenreturn falseend-- do something else return true
模塊
所有 require 的庫都要 local 化:
--No
local function foo()local ok, err = ngx.timer.at(delay, handler)
end--Yes
local timer_at = ngx.timer.atlocal function foo()local ok, err = timer_at(delay, handler)
end
為了風格的統一,require 和 ngx 也需要 local 化:
--No
local core = require("apisix.core")
local timer_at = ngx.timer.atlocal function 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.atlocal function 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 thenngx.say("failed to connect to google: ", err)returnendngx.say("successfully connected to google!")
而如果是自己編寫的函數,錯誤信息要作為第二個參數,用字符串的格式返回:
--No
local function foo()local ok, err = func()if not ok thenreturn falseendreturn true
end--No
local function foo()local ok, err = func()if not ok thenreturn false, {msg = err}endreturn true
end--Yes
local function foo()local ok, err = func()if not ok thenreturn false, "failed to call func(): " .. errendreturn true
end
原文
https://github.com/apache/apisix/blob/v1.3/CODE_STYLE.md