Guia de Estilo de Codificação do OpenResty

API7.ai

December 15, 2022

OpenResty (NGINX + Lua)

Muitas linguagens de desenvolvimento possuem suas especificações de codificação para informar aos desenvolvedores algumas convenções no campo, manter o estilo do código escrito consistente e evitar algumas armadilhas comuns. O PEP 8 do Python é um excelente exemplo disso, e quase todos os desenvolvedores Python já leram essa especificação de codificação escrita pelos autores do Python.

O OpenResty ainda não possui sua própria especificação de codificação, e alguns desenvolvedores são repetidamente revisados e solicitados a alterar o estilo de seu código após enviarem PRs, o que consome muito tempo e esforço que poderiam ser evitados.

Existem duas ferramentas de Lint no OpenResty que podem ajudá-lo a detectar automaticamente o estilo do código: luacheck e lj-releng. A primeira é uma ferramenta de Lint comum no mundo Lua e OpenResty, e a segunda é uma ferramenta de Lint escrita em Perl pelo próprio OpenResty.

No meu caso, instalei o plugin luacheck no editor VS Code para ter uma ferramenta de sugestão automática ao escrever código; e no CI de um projeto, executo ambas as ferramentas, por exemplo:

luacheck -q lua

./utils/lj-releng lua/*.lua lua/apisix/*.lua

Afinal, mais uma ferramenta para testes nunca é demais.

No entanto, essas duas ferramentas são mais focadas em detectar variáveis globais, comprimento por linha e outros estilos de código mais básicos, o que ainda está longe do nível detalhado do Python PEP 8, e não há documentação para você consultar.

Portanto, hoje, com base na minha experiência em projetos de código aberto relacionados ao OpenResty, compilei a documentação de estilo de codificação do OpenResty. Esta especificação também é consistente com o estilo de código de alguns gateways de API como APISIX e Kong.

Indentação

No OpenResty, usamos 4 espaços como marcadores de indentação, embora o Lua não exija essa sintaxe. Aqui estão dois exemplos de códigos incorretos e corretos.

--Não
if a then
ngx.say("hello")
end
--Sim
if a then
    ngx.say("hello")
end

Para conveniência, podemos simplificar a operação alterando a tabulação para 4 espaços no editor que você está usando.

Espaço

Em ambos os lados do operador, é necessário um espaço para separá-los. A seguir estão dois exemplos de códigos incorretos e corretos.

--Não
local i=1
local s    =    "apisix"
--Sim
local i = 1
local s = "apisix"

Linha em branco

Muitos desenvolvedores trazem convenções de desenvolvimento de outras linguagens para o OpenResty, como adicionar um ponto e vírgula ao final de uma linha:

--Não
if a then
    ngx.say("hello");
end;

Mas, na verdade, adicionar ponto e vírgula faz o código Lua parecer muito feio, o que é desnecessário. Além disso, você não deve transformar várias linhas de código em uma única linha para economizar linhas e ser conciso. Fazer isso deixará você sem saber qual seção do código está com erro ao localizar o problema.

--Não
if a then ngx.say("hello") end
--Sim
if a then
    ngx.say("hello")
end

Além disso, as funções precisam ser separadas por duas linhas em branco.

--Não
local function foo()
end
local function bar()
end
--Sim
local function foo()
end


local function bar()
end

Se houver vários ramos if elseif, eles também precisam ser separados por uma linha em branco.

--Não
if a == 1 then
    foo()
elseif a== 2 then
    bar()
elseif a == 3 then
    run()
else
    error()
end
--Sim
if a == 1 then
    foo()

elseif a== 2 then
    bar()

elseif a == 3 then
    run()

else
    error()
end

Comprimento máximo por linha

Cada linha não deve exceder 80 caracteres; se exceder, precisamos quebrar a linha e alinhar. E ao alinhar as quebras de linha, precisamos refletir a correspondência entre as linhas superiores e inferiores. Para o exemplo abaixo, o argumento da função na segunda linha deve estar à direita do parêntese esquerdo na primeira linha.

--Não
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--Sim
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
                    conf.default_conn_delay)

Se for o alinhamento de concatenação de strings, precisamos colocar .. na próxima linha.

--Não
return limit_conn_new("plugin-limit-conn" ..  "plugin-limit-conn" ..
                    "plugin-limit-conn")
--Sim
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
                    .. "plugin-limit-conn")

Variável

Este ponto também foi enfatizado várias vezes em artigos anteriores: devemos sempre usar variáveis locais em vez de variáveis globais.

--Não
i = 1
s = "apisix"
--Sim
local i = 1
local s = "apisix"

Quanto à nomenclatura das variáveis, o estilo snake_case deve ser usado.

--Não
local IndexArr = 1
local str_Name = "apisix"
--Sim
local index_arr = 1
local str_name = "apisix"

Para constantes, por outro lado, o estilo all-caps deve ser usado.

--Não
local max_int = 65535
local server_name = "apisix"

--Sim
local MAX_INT = 65535
local SERVER_NAME = "apisix"

Tabela

No OpenResty, usamos table.new para pré-alocar a tabela.

--Não
local t = {}
for i = 1, 100 do
   t[i] = i
 end
--Sim
local new_tab = require "table.new"
 local t = new_tab(100, 0)
 for i = 1, 100 do
   t[i] = i
 end

Além disso, observe que você nunca deve usar nil no array, e se precisar usar nulo, use ngx.null para indicar isso.

--Não
local t = {1, 2, nil, 3}

--Sim
local t = {1, 2, ngx.null, 3}

String

Nunca concatene strings em um caminho de código quente.

--Não
local s = ""
for i = 1, 100000 do
    s = s .. "a"
end
--Sim
local t = {}
for i = 1, 100000 do
    t[i] = "a"
end
local s =  table.concat(t, "")

Função

A nomenclatura das funções também segue o snake_case.

--Não
local function testNginx()
end
--Sim
local function test_nginx()
end

E a função deve retornar o mais cedo possível.

--Não
local function check(age, name)
    local ret = true
    if age < 20 then
        ret = false
    end

    if name == "a" then
        ret = false
    end
    -- fazer algo mais
    return ret
--Sim
local function check(age, name)
    if age < 20 then
        return false
    end

    if name == "a" then
        return false
    end
    -- fazer algo mais
    return true

Módulo

Todas as bibliotecas require devem ser localizadas:

--Não
local function foo()
    local ok, err = ngx.timer.at(delay, handler)
end
--Sim
local timer_at = ngx.timer.at

local function foo()
    local ok, err = timer_at(delay, handler)
end

Para consistência de estilo, require e ngx também precisam ser localizados:

--Não
local core = require("apisix.core")
local timer_at = ngx.timer.at

local function foo()
    local ok, err = timer_at(delay, handler)
end
--Sim
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

Tratamento de erros

Para funções que retornam com informações de erro, a informação de erro deve ser julgada e processada:

--Não
local sock = ngx.socket.tcp()
local ok = sock:connect("www.google.com", 80)
ngx.say("conectado com sucesso ao google!")
--Sim
local sock = ngx.socket.tcp()
local ok, err = sock:connect("www.google.com", 80)
if not ok then
    ngx.say("falha ao conectar ao google: ", err)
    return
end
ngx.say("conectado com sucesso ao google!")

E no caso de funções escritas por você mesmo, a informação de erro deve ser retornada como um segundo parâmetro no formato de string:

--Não
local function foo()
    local ok, err = func()
    if not ok then
        return false
    end
    return true
end
--Não
local function foo()
    local ok, err = func()
    if not ok then
        return false, {msg = err}
    end
    return true
end
--Sim
local function foo()
    local ok, err = func()
    if not ok then
        return false, "falha ao chamar func(): " .. err
    end
    return true
end

Resumo

Esta é uma versão inicial do guia de estilo de codificação, e vamos disponibilizá-lo no GitHub para atualizações e manutenções contínuas. Você é bem-vindo para compartilhar esta especificação para que mais usuários do OpenResty possam se envolver.