Guia de Estilo de Codificação do OpenResty
API7.ai
December 15, 2022
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 local
izadas:
--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 local
izados:
--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.