You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

458 lines
8.1 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 34 | 特别放送OpenResty编码指南
你好,我是温铭。
很多开发语言都有自己的编码规范,来告诉开发者这个领域内一些约定俗成的东西,让大家写的代码风格保持一致,并且避免一些常见的陷阱。这对于新手来说是非常友好的,可以让初学者快速准确地上手。比如 Python 的 PEP 80就是其中的典范几乎所有的 Python 开发者都阅读过这份 Python 作者执笔的编码规范。
**让开发者统一思想,按照规范来写代码,是一件非常重要的事情**。OpenResty 还没有自己的编码规范,有些开发者在提交 PR 后,会在代码风格上被反复 review 和要求修改,消耗了大量本可避免的时间和精力。
其实,在 OpenResty 中也有两个可以帮你自动化检测代码风格的工具luacheck 和 lj-releng。前者是 Lua 和 OpenResty 世界通用的检测工具,后者则是 OpenResty 自己用 perl 写的代码检测工具。
对我自己来说,我会在 VS Code 编辑器中安装 luacheck 的插件,这样在我写代码的时候就有工具来自动提示;而在项目的 CI 中,则是会把这两个工具都运行一遍,比如:
```
luacheck -q lua
./utils/lj-releng lua/*.lua lua/apisix/*.lua
```
毕竟,多一个工具的检测总不是坏事。
但是,这两个工具更多的是检测全局变量、每行长度等这些最基础的代码风格,离 Python PEP 80 的详细程度还有遥远的距离,并且也没有文档给你参考。
所以今天我就根据自己在OpenResty 相关开源项目中的经验,总结了一下 OpenResty 的编码风格文档,这个规范也和一些常见的 API 网关比如 Kong、APISIX 的代码风格是一致的。
## 缩进
在 OpenResty 中,我们使用 4 个空格作为缩进的标记,虽然 Lua 并没有这样的语法要求。下面是错误和正确的两段代码示例:
```
--No
if a then
ngx.say("hello")
end
```
```
--yes
if a then
ngx.say("hello")
end
```
为了方便,你可以在使用的编辑器中,把 tab 改为 4 个空格,来简化操作。
## 空格
在操作符的两边,都需要用一个空格来做分隔。下面是错误和正确的两段代码示例:
```
--No
local i=1
local s = "apisix"
```
```
--Yes
local i = 1
local s = "apisix"
```
## 空行
不少开发者会把其他语言的开发习惯带到 OpenResty 中来,比如在行尾增加一个分号:
```
--No
if a then
ngx.say("hello");
end;
```
但事实上,增加分号会让 Lua 代码显得非常丑陋,也是没有必要的。同时,你也不要为了节省代码的行数,追求所谓的“简洁”,而把多行代码变为一行。这样做会让你在定位错误的时候,不知道到底是哪一段代码出了问题:
```
--No
if a then ngx.say("hello") end
```
```
--yes
if a then
ngx.say("hello")
end
```
另外,函数之间需要用两个空行来做分隔:
```
--No
local function foo()
end
local function bar()
end
```
```
--Yes
local function foo()
end
local function bar()
end
```
如果有多个 if elseif 的分支,它们之间也需要一个空行来做分隔:
```
--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
```
## 每行最大长度
每行不能超过 80 个字符,如果超过的话,需要你换行并对齐。并且,在换行对齐的时候,我们要体现出上下两行的对应关系。就下面的示例而言,第二行函数的参数,要在第一行左括号的右边。
```
--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)
```
如果是字符串拼接问题的对齐,则需要把 `..` 放到下一行中:
```
--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")
```
## 变量
这一点我前面也多次强调过,我们应该永远使用局部变量,不要使用全局变量:
```
--No
i = 1
s = "apisix"
```
```
--Yes
local i = 1
local s = "apisix"
```
至于变量的命名,应该使用 `snake_case` 风格:
```
--No
local IndexArr = 1
local str_Name = "apisix"
```
```
--Yes
local index_arr = 1
local str_name = "apisix"
```
而对于常量,则是要使用全部大写的形式:
```
--No
local max_int = 65535
local server_name = "apisix"
```
```
--Yes
local MAX_INT = 65535
local SERVER_NAME = "apisix"
```
## 数组
在OpenResty中我们使用`table.new` 来预先分配数组:
```
--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
```
另外注意,一定不要在数组中使用 nil
```
--No
local t = {1, 2, nil, 3}
```
如果一定要使用空值,请用 ngx.null 来表示:
```
--Yes
local t = {1, 2, ngx.null, 3}
```
## 字符串
千万不要在热代码路径上拼接字符串:
```
--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, "")
```
## 函数
函数的命名也同样遵循 `snake_case`
```
--No
local function testNginx()
end
```
```
--Yes
local function test_nginx()
end
```
并且,函数应该尽可能早地返回:
```
--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
```
## 模块
所有 require 的库都要 local 化:
```
--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
```
为了风格的统一require 和 ngx 也需要 local 化:
```
--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
```
## 错误处理
对于有错误信息返回的函数,我们必须对错误信息进行判断和处理:
```
--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!")
```
而如果是自己编写的函数,错误信息要作为第二个参数,用字符串的格式返回:
```
--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
```
## 写在最后
这个编程规范算是一个最初版本,我会公开到 [GitHub](https://github.com/apache/incubator-apisix/blob/v1.3/CODE_STYLE.md) 中来持续更新和维护。如果文中没有包含到你想知道的规范非常欢迎你留言提问我来给你解答。也欢迎你把这篇规范分享出去让更多的OpenResty使用者参与进来。