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.