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.