OpenResty 코딩 스타일 가이드
API7.ai
December 15, 2022
많은 개발 언어들은 개발자들에게 해당 분야의 관례를 알려주고, 코드 작성 스타일을 일관되게 유지하며, 일반적인 함정을 피할 수 있도록 코딩 규격을 가지고 있습니다. Python의 PEP 8은 이에 대한 훌륭한 예시이며, 거의 모든 Python 개발자들은 Python 저자가 작성한 이 코딩 규격을 읽어봤을 것입니다.
OpenResty는 아직 자신만의 코딩 규격을 가지고 있지 않으며, 일부 개발자들은 PR을 제출한 후 코드 스타일을 변경하라는 리뷰를 반복적으로 받아 시간과 노력을 낭비하고 있습니다. 이는 피할 수 있는 일입니다.
OpenResty에는 코드 스타일을 자동으로 감지할 수 있는 두 가지 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
결국, 테스트를 위한 도구가 하나 더 있는 것은 나쁜 일이 아닙니다.
그러나 이 두 도구는 전역 변수, 줄당 길이 및 기타 가장 기본적인 코드 스타일을 감지하는 데 더 초점이 맞춰져 있으며, Python PEP 8의 세부 수준과는 거리가 멀고 참조할 문서도 없습니다.
그래서 오늘, OpenResty 관련 오픈소스 프로젝트에서의 경험을 바탕으로 OpenResty 코딩 스타일 문서를 정리했습니다. 이 규격은 APISIX 및 Kong과 같은 일부 API 게이트웨이의 코드 스타일과도 일치합니다.
들여쓰기
OpenResty에서는 4개의 공백을 들여쓰기 표시로 사용합니다. Lua는 이러한 구문을 요구하지 않지만, 다음은 잘못된 코드와 올바른 코드의 두 가지 예시입니다.
--아니오
if a then
ngx.say("hello")
end
--예
if a then
ngx.say("hello")
end
편의를 위해 사용 중인 편집기에서 탭을 4개의 공백으로 변경하여 작업을 단순화할 수 있습니다.
공백
연산자 양쪽에는 공백이 필요하여 구분해야 합니다. 다음은 잘못된 코드와 올바른 코드의 두 가지 예시입니다.
--아니오
local i=1
local s = "apisix"
--예
local i = 1
local s = "apisix"
빈 줄
많은 개발자들이 다른 언어에서의 개발 관습을 OpenResty로 가져와 줄 끝에 세미콜론을 추가합니다:
--아니오
if a then
ngx.say("hello");
end;
하지만 실제로 세미콜론을 추가하면 Lua 코드가 매우 보기 흉해지며, 이는 불필요합니다. 또한, 여러 줄의 코드를 한 줄로 만들어 줄을 절약하려고 해서는 안 됩니다. 이렇게 하면 오류를 찾을 때 어떤 코드 섹션이 문제인지 알 수 없게 됩니다.
--아니오
if a then ngx.say("hello") end
--예
if a then
ngx.say("hello")
end
또한, 함수 사이에는 두 개의 빈 줄로 구분해야 합니다.
--아니오
local function foo()
end
local function bar()
end
--예
local function foo()
end
local function bar()
end
여러 if elseif
분기가 있는 경우에도 빈 줄로 구분해야 합니다.
--아니오
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
--예
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
줄당 최대 길이
각 줄은 80자를 초과해서는 안 되며, 이를 초과할 경우 줄을 나누고 정렬해야 합니다. 줄을 나눌 때는 상하 줄 간의 대응 관계를 반영해야 합니다. 아래 예시에서 두 번째 줄의 함수 인수는 첫 번째 줄의 왼쪽 괄호 오른쪽에 위치해야 합니다.
--아니오
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--예
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
conf.default_conn_delay)
문자열을 연결할 때 정렬하는 경우, ..
를 다음 줄에 위치시켜야 합니다.
--아니오
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn" ..
"plugin-limit-conn")
--예
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
.. "plugin-limit-conn")
변수
이전 글에서도 여러 번 강조한 바와 같이, 항상 전역 변수보다는 지역 변수를 사용해야 합니다.
--아니오
i = 1
s = "apisix"
--예
local i = 1
local s = "apisix"
변수 이름은 snake_case
스타일을 사용해야 합니다.
--아니오
local IndexArr = 1
local str_Name = "apisix"
--예
local index_arr = 1
local str_name = "apisix"
반면, 상수는 all-caps
스타일을 사용해야 합니다.
--아니오
local max_int = 65535
local server_name = "apisix"
--예
local MAX_INT = 65535
local SERVER_NAME = "apisix"
테이블
OpenResty에서는 table.new
를 사용하여 테이블을 미리 할당합니다.
--아니오
local t = {}
for i = 1, 100 do
t[i] = i
end
--예
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
을 사용해야 합니다.
--아니오
local t = {1, 2, nil, 3}
--예
local t = {1, 2, ngx.null, 3}
문자열
핫 코드 경로에서 문자열을 연결해서는 안 됩니다.
--아니오
local s = ""
for i = 1, 100000 do
s = s .. "a"
end
--예
local t = {}
for i = 1, 100000 do
t[i] = "a"
end
local s = table.concat(t, "")
함수
함수 이름도 snake_case
를 따라야 합니다.
--아니오
local function testNginx()
end
--예
local function test_nginx()
end
또한, 함수는 가능한 한 빨리 반환해야 합니다.
--아니오
local function check(age, name)
local ret = true
if age < 20 then
ret = false
end
if name == "a" then
ret = false
end
-- 다른 작업 수행
return ret
--예
local function check(age, name)
if age < 20 then
return false
end
if name == "a" then
return false
end
-- 다른 작업 수행
return true
모듈
모든 require
된 라이브러리는 local
화해야 합니다:
--아니오
local function foo()
local ok, err = ngx.timer.at(delay, handler)
end
--예
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
스타일의 일관성을 위해 require
와 ngx
도 local
화해야 합니다:
--아니오
local core = require("apisix.core")
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
--예
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
오류 처리
오류 정보를 반환하는 함수의 경우, 오류 정보를 판단하고 처리해야 합니다:
--아니오
local sock = ngx.socket.tcp()
local ok = sock:connect("www.google.com", 80)
ngx.say("successfully connected to google!")
--예
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!")
또한, 직접 작성한 함수의 경우 오류 정보를 문자열 형식의 두 번째 매개변수로 반환해야 합니다:
--아니오
local function foo()
local ok, err = func()
if not ok then
return false
end
return true
end
--아니오
local function foo()
local ok, err = func()
if not ok then
return false, {msg = err}
end
return true
end
--예
local function foo()
local ok, err = func()
if not ok then
return false, "failed to call func(): " .. err
end
return true
end
요약
이것은 코딩 스타일 가이드의 초기 버전이며, 지속적인 업데이트와 유지 관리를 위해 GitHub에서 제공할 예정입니다. 이 규격을 공유하여 더 많은 OpenResty 사용자들이 참여할 수 있도록 해주세요.