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ユーザーが参加できるようにしてください。