34 Special Release Open Resty Coding Guidelines

34 Special Release- OpenResty Coding Guidelines #

Hello, I’m Wen Ming.

Many programming languages have their own coding guidelines to inform developers of common conventions and help maintain a consistent code style, as well as avoid common pitfalls. These guidelines are especially helpful for beginners, allowing them to quickly and accurately get started. For example, Python’s PEP 8 is a great example of such guidelines, and almost all Python developers have read this coding style guide written by the authors of Python.

It is very important for developers to have a unified mindset and write code according to guidelines. Currently, OpenResty does not have its own coding guidelines, so some developers, after submitting a pull request, are often reviewed and asked to modify their code style, resulting in a significant waste of time and effort.

In fact, in OpenResty, there are two tools available that can automatically check code style: luacheck and lj-releng. The former is a commonly used detection tool in the Lua and OpenResty community, while the latter is a code detection tool developed by OpenResty itself using Perl.

For me personally, I would install the luacheck plugin in the VS Code editor, so that I can receive automatic prompts while writing code. And in the project’s CI, both of these tools are run, for example:

luacheck -q lua

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

After all, having one more tool for detecting code style is never a bad thing.

However, these two tools mainly check for global variables, line lengths, and other basic code style aspects. They are still far from the detailed level of Python’s PEP 8, and there is also no documentation for you to refer to.

So today, based on my experience in OpenResty-related open source projects, I have summarized the coding style document for OpenResty. This guideline is also consistent with the code style of some common API gateways such as Kong and APISIX.

Indentation #

In OpenResty, we use 4 spaces as the indentation marker, although Lua does not have such syntax requirements. Here are two code examples with incorrect and correct indentation:

-- No
if a then
ngx.say("hello")
end
-- Yes
if a then
    ngx.say("hello")
end

To simplify operations, you can change the tab to 4 spaces in the editor you are using.

Spaces #

In between operators, a space is needed as a separator. The following are examples of incorrect and correct code:

-- No
local i=1
local s    =    "apisix"

-- Yes
local i = 1
local s = "apisix"

Blank Lines #

Many developers bring their habits from other languages into OpenResty, such as adding a semicolon at the end of a line:

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

However, adding a semicolon makes the Lua code very ugly and unnecessary. Also, do not try to save lines of code and pursue “conciseness” by converting multiple lines of code into one line. This will make it difficult for you to locate the problem when there is an error:

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


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

In addition, two blank lines should be used to separate functions:

-- No
local function foo()
end
 local function bar()
end


-- Yes
local function foo()
end


 local function bar()
end

If there are multiple if elseif branches, they should also be separated by a blank line:

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


-- Yes
if a == 1 then
    foo()

elseif a == 2 then
    bar()

elseif a == 3 then
    run()

else
    error()
end

Maximum Line Length #

Each line should not exceed 80 characters. If it exceeds the limit, you need to break the line and align the content. When aligning the content, you should maintain the corresponding relationship between the lines, as shown in the example below. The second line of the function’s parameters should be aligned with the right side of the left parenthesis in the first line.

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

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

For string concatenation, you need to place the .. operator on the next line:

-- No
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn" ..
                      "plugin-limit-conn")

-- Yes
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
                      .. "plugin-limit-conn")

Variables #

I have emphasized this point many times before. We should always use local variables and avoid using global variables:

-- No
i = 1
s = "apisix"


-- Yes
local i = 1
local s = "apisix"

As for variable naming, we should use the snake_case style:

-- No
local IndexArr = 1
local str_Name = "apisix"


-- Yes
local index_arr = 1
local str_name = "apisix"

For constants, they should be written in all capital letters:

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


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

Arrays #

In OpenResty, we use table.new to pre-allocate arrays:

-- No
local t = {}
for i = 1, 100 do
   t[i] = i
 end


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

Also, make sure not to use nil in arrays:

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

If you really need to use an empty value, use ngx.null:

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

Strings #

Do not concatenate strings in hot code paths:

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

Functions #

Function names should also follow snake_case:

--No
local function testNginx()
end


--Yes
local function test_nginx()
end

And functions should return as early as possible:

--No
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 


--Yes
local function check(age, name)
    if age < 20 then
        return false
    end

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

Modules #

All required libraries should be localized:

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


-- Yes
local timer_at = ngx.timer.at

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

For style consistency, the require and ngx should also be localized:

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

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


-- Yes
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

Error Handling #

For functions that return error messages, we must check and handle the error messages:

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

However, for functions that we write ourselves, the error message should be the second return value and returned as a string:

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

Conclusion #

This programming specification can be considered as an initial version. I will continue to update and maintain it on GitHub. If the specification does not cover the rules you would like to know, please feel free to leave a comment and ask me. You are also welcome to share this specification, so that more OpenResty users can get involved.