OpenResty Coding Style Guide

API7.ai

December 15, 2022

OpenResty (NGINX + Lua)

Viele Entwicklungssprachen haben ihre eigenen Codierungsrichtlinien, um Entwicklern einige Konventionen in diesem Bereich mitzuteilen, den Stil des geschriebenen Codes konsistent zu halten und einige häufige Fallstricke zu vermeiden. Pythons PEP 8 ist ein hervorragendes Beispiel dafür, und fast alle Python-Entwickler haben diese von den Autoren von Python geschriebene Codierungsrichtlinie gelesen.

OpenResty hat noch keine eigene Codierungsrichtlinie, und einige Entwickler werden wiederholt überprüft und aufgefordert, ihren Codierungsstil nach der Einreichung von PRs zu ändern, was viel Zeit und Mühe kostet, die vermieden werden könnte.

Es gibt zwei Lint-Tools in OpenResty, die Ihnen helfen können, den Codierungsstil automatisch zu überprüfen: luacheck und lj-releng. Ersteres ist ein gängiges Lint-Tool in der Lua- und OpenResty-Welt, und letzteres ist ein in Perl geschriebenes Lint-Tool von OpenResty selbst.

Ich selbst installiere das luacheck-Plugin im VS Code-Editor, damit ich ein Tool habe, das mir beim Schreiben von Code automatisch Vorschläge macht; und im CI eines Projekts führe ich beide Tools aus, z.B.

luacheck -q lua

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

Schließlich ist ein zusätzliches Tool zum Testen nie eine schlechte Sache.

Diese beiden Tools beziehen sich jedoch mehr auf die Erkennung von globalen Variablen, der Zeilenlänge und anderen grundlegendsten Codierungsstilen, die noch weit vom detaillierten Niveau von Python PEP 8 entfernt sind, und es gibt keine Dokumentation, auf die Sie sich beziehen können.

Daher habe ich heute basierend auf meiner Erfahrung in OpenResty-bezogenen Open-Source-Projekten die OpenResty-Codierungsstil-Dokumentation zusammengefasst. Diese Spezifikation stimmt auch mit dem Codierungsstil einiger API-Gateways wie APISIX und Kong überein.

Einrückung

In OpenResty verwenden wir 4 Leerzeichen als Einrückungsmarkierungen, obwohl Lua keine solche Syntax erfordert. Hier sind zwei Beispiele für falschen und korrekten Code.

--Nein
if a then
ngx.say("hello")
end
--Ja
if a then
    ngx.say("hello")
end

Der Einfachheit halber können wir den Vorgang vereinfachen, indem wir die Tabulatortaste in dem von Ihnen verwendeten Editor in 4 Leerzeichen ändern.

Leerzeichen

Auf beiden Seiten des Operators ist ein Leerzeichen erforderlich, um sie zu trennen. Die folgenden Beispiele zeigen falschen und korrekten Code.

--Nein
local i=1
local s    =    "apisix"
--Ja
local i = 1
local s = "apisix"

Leerzeile

Viele Entwickler bringen Entwicklungskonventionen aus anderen Sprachen nach OpenResty, wie z.B. das Hinzufügen eines Semikolons am Ende einer Zeile:

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

Aber tatsächlich macht das Hinzufügen von Semikolons Lua-Code sehr hässlich, was unnötig ist. Außerdem sollten Sie nicht mehrere Codezeilen in eine einzige Zeile umwandeln, um Zeilen zu sparen und prägnant zu sein. Dies lässt Sie im Unklaren, welcher Codeabschnitt fehlerhaft ist, wenn Sie den Fehler lokalisieren.

--Nein
if a then ngx.say("hello") end
--Ja
if a then
    ngx.say("hello")
end

Darüber hinaus müssen die Funktionen durch zwei Leerzeilen getrennt werden.

--Nein
local function foo()
end
local function bar()
end
--Ja
local function foo()
end


local function bar()
end

Wenn es mehrere if elseif-Zweige gibt, müssen diese ebenfalls durch eine Leerzeile getrennt werden.

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

elseif a== 2 then
    bar()

elseif a == 3 then
    run()

else
    error()
end

Maximale Zeilenlänge

Jede Zeile sollte 80 Zeichen nicht überschreiten; wenn sie dies tut, müssen wir einen Zeilenumbruch durchführen und ausrichten. Und beim Ausrichten von Zeilenumbrüchen müssen wir die Entsprechung zwischen den oberen und unteren Zeilen widerspiegeln. Für das folgende Beispiel sollte das Argument der Funktion in der zweiten Zeile rechts von der öffnenden Klammer in der ersten Zeile stehen.

--Nein
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--Ja
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
                    conf.default_conn_delay)

Wenn es sich um die Ausrichtung von verketteten Zeichenfolgen handelt, müssen wir .. in die nächste Zeile setzen.

--Nein
return limit_conn_new("plugin-limit-conn" ..  "plugin-limit-conn" ..
                    "plugin-limit-conn")
--Ja
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
                    .. "plugin-limit-conn")

Variable

Dieser Punkt wurde auch in früheren Artikeln mehrmals betont: Wir sollten immer lokale Variablen anstelle von globalen Variablen verwenden.

--Nein
i = 1
s = "apisix"
--Ja
local i = 1
local s = "apisix"

Was die Benennung von Variablen betrifft, sollte der snake_case-Stil verwendet werden.

--Nein
local IndexArr = 1
local str_Name = "apisix"
--Ja
local index_arr = 1
local str_name = "apisix"

Für Konstanten hingegen sollte der all-caps-Stil verwendet werden.

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

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

Tabelle

In OpenResty verwenden wir table.new, um die Tabelle vorab zuzuweisen.

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

Außerdem ist zu beachten, dass Sie nil im Array nicht verwenden dürfen, und wenn Sie unbedingt null verwenden müssen, verwenden Sie ngx.null, um dies anzuzeigen.

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

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

Zeichenfolge

Verkettet niemals Zeichenfolgen auf einem Hot-Code-Pfad.

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

Funktion

Die Benennung von Funktionen folgt ebenfalls snake_case.

--Nein
local function testNginx()
end
--Ja
local function test_nginx()
end

Und die Funktion sollte so früh wie möglich zurückkehren.

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

    if name == "a" then
        ret = false
    end
    -- do something else
    return ret
--Ja
local function check(age, name)
    if age < 20 then
        return false
    end

    if name == "a" then
        return false
    end
    -- do something else
    return true

Modul

Alle require-Bibliotheken müssen localisiert werden:

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

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

Für die Konsistenz des Stils müssen auch require und ngx localisiert werden:

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

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

Fehlerbehandlung

Für Funktionen, die mit Fehlerinformationen zurückkehren, müssen die Fehlerinformationen beurteilt und verarbeitet werden:

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

Und im Fall von selbst geschriebenen Funktionen sollen die Fehlerinformationen als zweiter Parameter im String-Format zurückgegeben werden:

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

Zusammenfassung

Dies ist eine erste Version des Codierungsstil-Leitfadens, und wir werden ihn auf GitHub zur Verfügung stellen, um ihn kontinuierlich zu aktualisieren und zu pflegen. Sie sind eingeladen, diese Spezifikation zu teilen, damit mehr OpenResty-Benutzer daran teilnehmen können.