Guía de Estilo de Codificación de OpenResty
API7.ai
December 15, 2022
Muchos lenguajes de desarrollo tienen sus especificaciones de codificación para indicar a los desarrolladores algunas convenciones en el campo, mantener un estilo de código consistente y evitar errores comunes. El PEP 8 de Python es un excelente ejemplo de esto, y casi todos los desarrolladores de Python han leído esta especificación de codificación escrita por los autores de Python.
OpenResty aún no tiene su propia especificación de codificación, y algunos desarrolladores son revisados repetidamente y se les pide que cambien su estilo de código después de enviar PRs, lo que consume mucho tiempo y esfuerzo que podría evitarse.
Existen dos herramientas de Lint en OpenResty que pueden ayudarte a detectar automáticamente el estilo de código: luacheck
y lj-releng
. La primera es una herramienta de Lint común en el mundo de Lua y OpenResty, y la segunda es una herramienta de Lint escrita en Perl
por el propio OpenResty.
Personalmente, instalo el plugin de luacheck
en el editor VS Code para tener una herramienta que sugiera automáticamente mientras escribo código; y en el CI de un proyecto, ejecuto ambas herramientas, por ejemplo:
luacheck -q lua
./utils/lj-releng lua/*.lua lua/apisix/*.lua
Después de todo, una herramienta más para pruebas nunca es algo malo.
Sin embargo, estas dos herramientas se centran más en detectar variables globales, la longitud por línea y otros estilos de código básicos, lo que aún está lejos del nivel detallado de Python PEP 8, y no hay documentación a la que puedas referirte.
Así que hoy, basándome en mi experiencia en proyectos de código abierto relacionados con OpenResty, he resumido la documentación del estilo de codificación de OpenResty. Esta especificación también es consistente con el estilo de código de algunas pasarelas API como APISIX y Kong.
Indentación
En OpenResty, utilizamos 4 espacios como marcadores de indentación, aunque Lua no requiere tal sintaxis. Aquí hay dos ejemplos de código incorrecto y correcto.
--No
if a then
ngx.say("hello")
end
--Sí
if a then
ngx.say("hello")
end
Para mayor comodidad, podemos simplificar la operación cambiando la tabulación a 4 espacios en el editor que estés utilizando.
Espacios
A ambos lados del operador, se necesita un espacio para separarlos. A continuación, se muestran dos ejemplos de código incorrecto y correcto.
--No
local i=1
local s = "apisix"
--Sí
local i = 1
local s = "apisix"
Línea en blanco
Muchos desarrolladores traen convenciones de desarrollo de otros lenguajes a OpenResty, como agregar un punto y coma al final de una línea:
--No
if a then
ngx.say("hello");
end;
Pero en realidad, agregar puntos y comas hace que el código Lua se vea muy feo, lo cual es innecesario. Además, no debes convertir múltiples líneas de código en una sola línea para ahorrar líneas y ser conciso. Hacer esto te dejará sin idea de qué sección del código está fallando cuando localices el error.
--No
if a then ngx.say("hello") end
--Sí
if a then
ngx.say("hello")
end
Además, las funciones deben estar separadas por dos líneas en blanco.
--No
local function foo()
end
local function bar()
end
--Sí
local function foo()
end
local function bar()
end
Si hay múltiples ramas if elseif
, también deben estar separadas por una línea en blanco.
--No
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
--Sí
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
Longitud máxima por línea
Cada línea no debe exceder los 80 caracteres; si lo hace, necesitamos hacer un salto de línea y alinear. Y al alinear los saltos de línea, debemos reflejar la correspondencia entre las líneas superiores e inferiores. Para el ejemplo a continuación, el argumento de la función en la segunda línea debe estar a la derecha del paréntesis izquierdo en la primera línea.
--No
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--Sí
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
conf.default_conn_delay)
Si es la alineación de cadenas concatenadas, debemos poner ..
en la siguiente línea.
--No
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn" ..
"plugin-limit-conn")
--Sí
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
.. "plugin-limit-conn")
Variable
Este punto también se enfatizó varias veces en artículos anteriores: siempre debemos usar variables locales en lugar de variables globales.
--No
i = 1
s = "apisix"
--Sí
local i = 1
local s = "apisix"
En cuanto a la nomenclatura de las variables, se debe usar el estilo snake_case
.
--No
local IndexArr = 1
local str_Name = "apisix"
--Sí
local index_arr = 1
local str_name = "apisix"
Para las constantes, por otro lado, se debe usar el estilo all-caps
.
--No
local max_int = 65535
local server_name = "apisix"
--Sí
local MAX_INT = 65535
local SERVER_NAME = "apisix"
Tabla
En OpenResty, usamos table.new
para pre-asignar la tabla.
--No
local t = {}
for i = 1, 100 do
t[i] = i
end
--Sí
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
t[i] = i
end
Además, ten en cuenta que no debes usar nil
en el array, y si debes usar un valor nulo, usa ngx.null
para indicarlo.
--No
local t = {1, 2, nil, 3}
--Sí
local t = {1, 2, ngx.null, 3}
Cadena
Nunca concatenes cadenas en una ruta de código crítica.
--No
local s = ""
for i = 1, 100000 do
s = s .. "a"
end
--Sí
local t = {}
for i = 1, 100000 do
t[i] = "a"
end
local s = table.concat(t, "")
Función
La nomenclatura de las funciones también sigue snake_case
.
--No
local function testNginx()
end
--Sí
local function test_nginx()
end
Y la función debe retornar lo antes posible.
--No
local function check(age, name)
local ret = true
if age < 20 then
ret = false
end
if name == "a" then
ret = false
end
-- hacer algo más
return ret
--Sí
local function check(age, name)
if age < 20 then
return false
end
if name == "a" then
return false
end
-- hacer algo más
return true
Módulo
Todas las bibliotecas require
deben ser local
izadas:
--No
local function foo()
local ok, err = ngx.timer.at(delay, handler)
end
--Sí
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
Para la consistencia del estilo, require
y ngx
también deben ser local
izados:
--No
local core = require("apisix.core")
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
--Sí
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
Manejo de errores
Para las funciones que retornan información de error, la información de error debe ser juzgada y procesada:
--No
local sock = ngx.socket.tcp()
local ok = sock:connect("www.google.com", 80)
ngx.say("successfully connected to google!")
--Sí
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!")
Y en el caso de funciones escritas por ti mismo, la información de error debe ser retornada como un segundo parámetro en formato de cadena:
--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
--Sí
local function foo()
local ok, err = func()
if not ok then
return false, "failed to call func(): " .. err
end
return true
end
Resumen
Esta es una versión inicial de la guía de estilo de codificación, y la pondremos disponible en GitHub para actualizaciones y mantenimiento continuos. Te invitamos a compartir esta especificación para que más usuarios de OpenResty puedan involucrarse.