OpenResty コーディングスタイルガイド
API7.ai
December 15, 2022
多くの開発言語には、開発者にその分野での慣習を伝え、コードのスタイルを一貫させ、一般的な落とし穴を避けるためのコーディング規約があります。PythonのPEP 8はその良い例で、ほぼすべてのPython開発者がこのPythonの作者によって書かれたコーディング規約を読んでいます。
OpenRestyにはまだ独自のコーディング規約がなく、一部の開発者はPRを提出した後、コードスタイルを変更するよう繰り返しレビューされ、多くの時間と労力を消費していますが、これは避けられるものです。
OpenRestyには、コードスタイルを自動的に検出するための2つのLintツールがあります: luacheck
と lj-releng
です。前者はLuaおよびOpenRestyの世界で一般的なLintツールであり、後者はOpenResty自身がPerl
で書いたLintツールです。
私自身は、VS Codeエディタにluacheck
プラグインをインストールして、コードを書く際に自動提案を行うツールを利用しています。また、プロジェクトのCIでは両方のツールを実行しています。例えば、
luacheck -q lua
./utils/lj-releng lua/*.lua lua/apisix/*.lua
結局のところ、テスト用のツールがもう一つあっても悪いことはありません。
しかし、これらの2つのツールは、グローバル変数の検出、1行あたりの長さ、その他の最も基本的なコードスタイルの検出に重点を置いており、PythonのPEP 8のような詳細なレベルにはまだ遠く、参照するためのドキュメントもありません。
そこで今日、OpenResty関連のオープンソースプロジェクトでの経験に基づいて、OpenRestyのコーディングスタイルドキュメントをまとめました。この規約は、APISIXやKongなどのAPIゲートウェイのコードスタイルとも一致しています。
インデント
OpenRestyでは、4つのスペースをインデントマーカーとして使用します。Luaにはそのような構文は必要ありませんが、以下に間違ったコードと正しいコードの2つの例を示します。
--No
if a then
ngx.say("hello")
end
--yes
if a then
ngx.say("hello")
end
便利のために、使用しているエディタでタブを4つのスペースに変更することで操作を簡略化できます。
スペース
演算子の両側には、スペースを入れて区切る必要があります。以下に間違ったコードと正しいコードの2つの例を示します。
--No
local i=1
local s = "apisix"
--Yes
local i = 1
local s = "apisix"
空白行
多くの開発者は、他の言語からの開発慣習をOpenRestyに持ち込みます。例えば、行末にセミコロンを追加するなどです。
--No
if a then
ngx.say("hello");
end;
しかし、実際にはセミコロンを追加するとLuaのコードが非常に見苦しくなり、これは不要です。また、複数行のコードを1行にまとめて簡潔にしようとするべきではありません。これを行うと、エラーを特定する際にどのセクションのコードが問題なのかわからなくなります。
--No
if a then ngx.say("hello") end
--yes
if a then
ngx.say("hello")
end
さらに、関数は2つの空白行で区切る必要があります。
--No
local function foo()
end
local function bar()
end
--Yes
local function foo()
end
local function 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
1行あたりの最大長
各行は80文字を超えてはいけません。それを超える場合は、改行して揃える必要があります。また、改行を揃える際には、上下の行の対応関係を反映させる必要があります。以下の例では、2行目の関数の引数は、1行目の左括弧の右側に配置する必要があります。
--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"
一方、定数の場合は、all-caps
スタイルを使用するべきです。
--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 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
を使用してはいけません。nullを使用する必要がある場合は、ngx.null
を使用して示します。
--No
local t = {1, 2, nil, 3}
--Yes
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
local function testNginx()
end
--Yes
local function test_nginx()
end
また、関数はできるだけ早く返すべきです。
--No
local function 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
--Yes
local function check(age, name)
if age < 20 then
return false
end
if name == "a" then
return false
end
-- 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.at
local function foo()
local ok, err = timer_at(delay, handler)
end
スタイルの一貫性のために、require
とngx
もlocal
化する必要があります。
--No
local core = require("apisix.core")
local timer_at = ngx.timer.at
local 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.at
local 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 then
ngx.say("failed to connect to google: ", err)
return
end
ngx.say("successfully connected to google!")
また、自分で書いた関数の場合、エラー情報は文字列形式で2番目のパラメータとして返すべきです。
--No
local function foo()
local ok, err = func()
if not ok then
return false
end
return true
end
--No
local function foo()
local ok, err = func()
if not ok then
return false, {msg = err}
end
return true
end
--Yes
local function foo()
local ok, err = func()
if not ok then
return false, "failed to call func(): " .. err
end
return true
end
まとめ
これはコーディングスタイルガイドの初期バージョンであり、GitHubで公開して継続的に更新とメンテナンスを行います。この規約を共有して、より多くのOpenRestyユーザーが参加できるようにしてください。