Guía de Estilo de Codificación de OpenResty

API7.ai

December 15, 2022

OpenResty (NGINX + Lua)

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 localizadas:

--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 localizados:

--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.