Руководство по стилю кодирования OpenResty
API7.ai
December 15, 2022
Многие языки разработки имеют свои стандарты написания кода, которые помогают разработчикам соблюдать общепринятые соглашения в данной области, поддерживать единообразие стиля написания кода и избегать распространенных ошибок. Python's PEP 8 — это отличный пример такого стандарта, и почти все разработчики Python читали эту спецификацию, написанную авторами языка.
У OpenResty пока нет собственного стандарта написания кода, и некоторые разработчики сталкиваются с тем, что их стиль кода подвергается повторным проверкам и запросам на изменение после отправки PR. Это отнимает много времени и сил, чего можно было бы избежать.
В OpenResty есть два инструмента Lint, которые могут помочь автоматически проверять стиль кода: luacheck и lj-releng. Первый — это распространенный инструмент Lint в мире Lua и OpenResty, а второй — инструмент Lint, написанный на Perl самими разработчиками OpenResty.
Лично я устанавливаю плагин luacheck в редакторе VS Code, чтобы получать автоматические подсказки при написании кода. В CI проекта я запускаю оба инструмента, например:
luacheck -q lua ./utils/lj-releng lua/*.lua lua/apisix/*.lua
В конце концов, лишний инструмент для тестирования никогда не помешает.
Однако эти два инструмента в основном проверяют глобальные переменные, длину строк и другие базовые аспекты стиля кода, что далеко от уровня детализации Python PEP 8, и документации для них нет.
Поэтому сегодня, основываясь на своем опыте работы с OpenResty и связанными с ним open-source проектами, я составил документацию по стилю написания кода для OpenResty. Этот стандарт также соответствует стилю кода таких API-шлюзов, как APISIX и Kong.
Отступы
В 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, а если необходимо указать пустое значение, используйте 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 -- do something else return ret
--Да 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 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 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 могли принять участие в его развитии.