Guide de style de codage OpenResty

API7.ai

December 15, 2022

OpenResty (NGINX + Lua)

De nombreux langages de développement ont leurs propres spécifications de codage pour indiquer aux développeurs certaines conventions dans le domaine, afin de maintenir un style de code cohérent et d'éviter certains pièges courants. Le PEP 8 de Python en est un excellent exemple, et presque tous les développeurs Python ont lu cette spécification de codage écrite par les auteurs de Python.

OpenResty n'a pas encore sa propre spécification de codage, et certains développeurs sont régulièrement revus et invités à modifier leur style de code après avoir soumis des PR, ce qui consomme beaucoup de temps et d'efforts qui pourraient être évités.

Il existe deux outils Lint dans OpenResty qui peuvent vous aider à détecter automatiquement le style de code : luacheck et lj-releng. Le premier est un outil Lint commun dans le monde Lua et OpenResty, et le second est un outil Lint écrit en Perl par OpenResty lui-même.

Personnellement, j'installe le plugin luacheck dans l'éditeur VS Code pour avoir un outil de suggestion automatique lorsque j'écris du code ; et dans le CI d'un projet, j'exécute les deux outils, par exemple :

luacheck -q lua

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

Après tout, un outil de test supplémentaire n'est jamais une mauvaise chose.

Cependant, ces deux outils se concentrent davantage sur la détection des variables globales, la longueur par ligne et d'autres styles de code de base, ce qui est encore loin du niveau détaillé du PEP 8 de Python, et il n'y a pas de documentation à laquelle se référer.

Aujourd'hui, en me basant sur mon expérience dans les projets open source liés à OpenResty, j'ai résumé la documentation du style de codage OpenResty. Cette spécification est également cohérente avec le style de code de certaines passerelles API comme APISIX et Kong.

Indentation

Dans OpenResty, nous utilisons 4 espaces comme marqueurs d'indentation, bien que Lua ne nécessite pas une telle syntaxe. Voici deux exemples de codes incorrect et correct.

--Non
if a then
ngx.say("hello")
end
--Oui
if a then
    ngx.say("hello")
end

Pour plus de commodité, nous pouvons simplifier l'opération en changeant la tabulation en 4 espaces dans l'éditeur que vous utilisez.

Espace

Des deux côtés de l'opérateur, un espace est nécessaire pour les séparer. Voici deux exemples de codes incorrect et correct.

--Non
local i=1
local s    =    "apisix"
--Oui
local i = 1
local s = "apisix"

Ligne vide

De nombreux développeurs apportent des conventions de développement d'autres langages à OpenResty, comme ajouter un point-virgule à la fin d'une ligne :

--Non
if a then
    ngx.say("hello");
end;

Mais en réalité, ajouter des points-virgules rend le code Lua très laid, ce qui est inutile. De plus, vous ne devriez pas transformer plusieurs lignes de code en une seule ligne pour économiser des lignes et être concis. Cela vous laissera sans idée de quelle section de code est en faute lorsque vous localiserez l'erreur.

--Non
if a then ngx.say("hello") end
--Oui
if a then
    ngx.say("hello")
end

De plus, les fonctions doivent être séparées par deux lignes vides.

--Non
local function foo()
end
local function bar()
end
--Oui
local function foo()
end


local function bar()
end

S'il y a plusieurs branches if elseif, elles doivent également être séparées par une ligne vide.

--Non
if a == 1 then
    foo()
elseif a== 2 then
    bar()
elseif a == 3 then
    run()
else
    error()
end
--Oui
if a == 1 then
    foo()

elseif a== 2 then
    bar()

elseif a == 3 then
    run()

else
    error()
end

Longueur maximale par ligne

Chaque ligne ne doit pas dépasser 80 caractères ; si elle dépasse cela, nous devons faire un saut de ligne et aligner. Et lors de l'alignement des sauts de ligne, nous devons refléter la correspondance entre les lignes supérieure et inférieure. Pour l'exemple ci-dessous, l'argument de la fonction sur la deuxième ligne doit être à droite de la parenthèse gauche sur la première ligne.

--Non
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--Oui
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
                    conf.default_conn_delay)

S'il s'agit de l'alignement de la concaténation de chaînes, nous devons mettre .. sur la ligne suivante.

--Non
return limit_conn_new("plugin-limit-conn" ..  "plugin-limit-conn" ..
                    "plugin-limit-conn")
--Oui
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
                    .. "plugin-limit-conn")

Variable

Ce point a également été souligné plusieurs fois dans des articles précédents : nous devrions toujours utiliser des variables locales plutôt que des variables globales.

--Non
i = 1
s = "apisix"
--Oui
local i = 1
local s = "apisix"

En ce qui concerne la nomination des variables, le style snake_case doit être utilisé.

--Non
local IndexArr = 1
local str_Name = "apisix"
--Oui
local index_arr = 1
local str_name = "apisix"

Pour les constantes, en revanche, le style all-caps doit être utilisé.

--Non
local max_int = 65535
local server_name = "apisix"

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

Table

Dans OpenResty, nous utilisons table.new pour pré-allouer la table.

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

De plus, notez que vous ne devez pas utiliser nil dans le tableau, et si vous devez utiliser null, utilisez ngx.null pour l'indiquer.

--Non
local t = {1, 2, nil, 3}

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

Chaîne de caractères

Ne jamais concaténer des chaînes sur un chemin de code critique.

--Non
local s = ""
for i = 1, 100000 do
    s = s .. "a"
end
--Oui
local t = {}
for i = 1, 100000 do
    t[i] = "a"
end
local s =  table.concat(t, "")

Fonction

La nomination des fonctions suit également snake_case.

--Non
local function testNginx()
end
--Oui
local function test_nginx()
end

Et la fonction doit retourner le plus tôt possible.

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

    if name == "a" then
        ret = false
    end
    -- faire quelque chose d'autre
    return ret
--Oui
local function check(age, name)
    if age < 20 then
        return false
    end

    if name == "a" then
        return false
    end
    -- faire quelque chose d'autre
    return true

Module

Toutes les bibliothèques require doivent être localisées :

--Non
local function foo()
    local ok, err = ngx.timer.at(delay, handler)
end
--Oui
local timer_at = ngx.timer.at

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

Pour la cohérence du style, require et ngx doivent également être localisés :

--Non
local core = require("apisix.core")
local timer_at = ngx.timer.at

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

Gestion des erreurs

Pour les fonctions qui retournent des informations d'erreur, les informations d'erreur doivent être jugées et traitées :

--Non
local sock = ngx.socket.tcp()
local ok = sock:connect("www.google.com", 80)
ngx.say("successfully connected to google!")
--Oui
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!")

Et dans le cas des fonctions que vous écrivez vous-même, les informations d'erreur doivent être retournées comme deuxième paramètre sous forme de chaîne :

--Non
local function foo()
    local ok, err = func()
    if not ok then
        return false
    end
    return true
end
--Non
local function foo()
    local ok, err = func()
    if not ok then
        return false, {msg = err}
    end
    return true
end
--Oui
local function foo()
    local ok, err = func()
    if not ok then
        return false, "failed to call func(): " .. err
    end
    return true
end

Résumé

Ceci est une version initiale du guide de style de codage, et nous la rendrons disponible sur GitHub pour des mises à jour et une maintenance continues. Vous êtes invités à partager cette spécification afin que plus d'utilisateurs d'OpenResty puissent s'impliquer.