Lua代码规范
在公司整理了一份Lua语言的代码规范,分享一下。
写代码如同写文章,每个人或多或少都有自己的风格。我们在进行产品开发的过程中,更多的是团队上的协作与交流而非单打独斗。所以,为了 提高开发效率,降低维护成本,促进团队合作 ,代码的审查,整理出这篇文章。
借助于《Python风格指南》中的警告词,在此处引用一下:代码的阅读频率比编写的要高得多,本风格指南旨在通过一致性提高代码的可读性。一致性在不断增加的度量中,与其他项目、项目内以及单个模块或功能内是重要的。但是最重要的是:知道什么时候不一致,并运用你最好的判断——有时风格指南并不适用。当应用规则会降低代码的可读性时,最好打破规则。
最后,一句话送给大家:
Programming style is an art.
1. 版式
1.1 基础
-
所有文件均采用: UTF-8 无 BOM 格式
-
程序块要采用 缩进风格 编写,缩进的空格数为 4个 ,对齐使用 空格键 , 禁用TAB键
- 避免使用不同编辑器阅读程序时,因TAB键设置的空格数不同而造成程序布局不整齐。
-
函数内代码不超过 50行 , 单行代码不超过 80列
-
长表达式要在 低优先级操作符 处拆分成新行, 操作符放在新行之首
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
-
local monsterModel = {id = monsterData.id, index = 0, entityData = monsterData, type = monsterData.role_type, state = EnumMonsterStatus.Alive, level = 0, coordinate = {toward = EnumEntityToward.None, x = 0, z = 0}, dropModels = { }, hpMedicineDropRate = 0, epMedicineDropRate = 0}
-- good style
local monsterModel = {
id = monsterData.id,
index = 0,
entityData = monsterData,
type = monsterData.role_type,
state = EnumMonsterStatus.Alive,
level = 0,
coordinate = {toward = EnumEntityToward.None, x = 0, z = 0},
dropModels = {},
hpMedicineDropRate = 0,
epMedicineDropRate = 0,
}
if self.pEntity:isAttackStatus() or self.pEntity:isWakeStatus() or self.pEntity:isTobeHitStatus() or self.pEntity:isMoveStatus() or self.pEntity:isJostledStatus() then
return false
end
-- good style
if self.pEntity:isAttackStatus()
or self.pEntity:isWakeStatus()
or self.pEntity:isTobeHitStatus()
or self.pEntity:isMoveStatus()
or self.pEntity:isJostledStatus() then
return false
end
—|—
* 若函数或过程中的参数较长,则要进行适当的划分
1
2
3
4
5
self.pQualitity = CREATESPINE(
RES.QUALITYEFFECT_SPINE[_type].ID,
self.LayerQuality,
RES.QUALITYEFFECT_SPINE[_type].NAME,
true, nil, true)
—|—
1.2 代码
空行
- 代码概念与逻辑之间,逻辑段落小节之间,用 单个空行 分割
- 函数之间用 单个空行 分割
- 在注释之前增加 单个或多个空行
空格
-
双目运算符, 前后 都要有一个空格
- 1
(110 + 50 * i, 110 + 50 / (i + 1), time + i - 12)
—|—
-
逗号 前无空格,后跟空格
- 1
2
3
4
5
6
7
- 1
function (c, a, b)
if c then
return a
else
return b
end
end
—|—
* 1
2
3
for k, v in pairs(table) do
...
end
—|—
-
做为 函数参数部分 或 函数调用 的小括号 前后无空格
-
原因:
-
lua解析语法时是采用空格等分割来解析的,某些情况下,不小心加空格会导致非预期的结果
-
容易忘记相关空格,导致风格不统一,因此不如都不加
-
-
样例:
- 1
2
3
4
5
6
7
8
- 1
-
function totable(val)
local tab = {}
if "table" == type(val) then
return val
end
tab[1] = val
return tab
end
—|—
* 1
local clientTable = totable(serverTable)
—|—
-
大括号 前后无空格 ,后跟运算符看情况
- 1
package.loaded[path] = nil
—|—
其他
- 每行代码结束时, 禁止使用分号
- 单字符使用 单引号 , 字符串使用 双引号
- 使用 小括号 来强行规定运算顺序
注释
本规范的代码目标是自说明代码。
相对于代码的维护,注释的维护往往是非常差的,所以切忌乱用注释,尽量只在下列地方添加注释。
- 代码比较晦涩,用了不同于常人的方法(显然这种方法肯定要比常人方法更高效)
- 对外的接口部分,描述清楚作用及参数返回
- 不同于正常逻辑的部分,一般是策划强制要求,要予以说明
- 所有含有物理含义的变量、常量,如果其命名不是充分自说明的,在声明时必须加以注释,说明其物理含义
- 数据结构声明,不能充分自说明的,必须加以注释,说明
注释的规范
-
单行注释,注意 空格
- 1
-- 这是单行注释
—|—
- 多行注释,注意缩进。
最后 ]] 前加一个缩进,是为了让某些编辑器(Sublime、VSCode)折叠时简洁一些
* 1
2
3
4
--[[
这是多行注释
这是多行注释
]]
—|—
-
函数注释格式,位于函数上一行,采用多行注释,三个字段:描述、参数(可缺省)、返回(可缺省)
- 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 1
--[[describe:
这是函数描述
param1:参数类型
参数描述
param2:参数类型
参数描述
[param3]:参数类型([]代表可选参数)
参数描述
return:返回值类型,返回值类型
返回值类型 - 描述
返回值类型 - 描述
]]
function funcName(param1, param2, param3)
...
return return1, return2
end
—|—
-
文件注释
- 1
2
3
4
5
6
- 1
--[[
author: author1
partner: partner1, partner2
XMind: XMind名称
describe: 这是一个测试的类,主要负责的功能是啦啦啦啦啦啦啦啦啦啦啦啦
]]
—|—
2. 命名规范
2.1 谨记
- 最好的代码应该是 自说明代码 ,这种代码不需要多余的注释,本身便具备了作者意图的信息。
- 采用英文单词或单词组合,单词首先要准确,其次要简单。 注意单词拼写,切忌使用拼音!
- 不要为了避免命名过长而随意截取单词,丢失可读性
- 所有命名不要与Cocos2d-x引擎或其他第三方工具已有的命名风格冲突
- 作用域越大的名称应该越详细越能表述自己
- i 做为小局部变量相对来说很妥当,但是做为全局变量会很糟糕。
- 尽量不要用 _ 开头的名称,这些一般是lua内部所使用
- 除非必要,不要用数字或者奇怪的字符来命名
2.2 类的命名规范
-
所有 类的定义 均采用 大驼峰命名法(Pascal命名法)
- 1
Example = class("Example", nil)
—|—
-
所有 类的函数 均采用 小驼峰命名法
- 1
2
3
- 1
function Example:exampleFunction()
...
end
—|—
-
所有 类的成员 均采用 self.(类型)(名称),类型均为小写,名称开头大写(整体是 小驼峰命名法 )
- 类型对照表
类型 | 缩写 | 描述 |
---|---|---|
int | i | 整数 |
float | f | 浮点数(不分float or double) |
boolean | b | 布尔 |
string | s | 字符串 |
enum | e | 枚举 |
ptr | p | userdata |
function | func | 函数 |
table | t | table, 后缀为Array代表数组,后缀为Map代表字典 |
-
如果是传入的回调函数,则采用 self.pXXXHandler 来命名
-
例子:
1 2 3 4
self.iExample
self.bExampleExample
self.sExampleExampleExample
self.tExampleArray
—|—
-
一般由 名词 或者 多名词 组成,不要随意简写
-
根据类的特性和使用场景,加上相关的后缀或者前缀
2.3 变量命名
-
采用 小驼峰命名法
-
使用 名词 或者是 形容词+名词 命名
-
尽量避免出现 仅靠部分字母大小写区分 的相似的变量
- 1
local posx, posX
—|—
2.4 常量命名
-
配置常量 , 均用大写,单词间以下划线相连
- 1
2
3
4
5
- 1
--幻影设置
GHOST_STATE = {
INTERVAL_TIME = 80, --出现间隔时间(毫秒)
FADE_TIME = 500, --fade时间(毫秒)
},
—|—
-
类内配置常量,local引用, 均用大写,单词间以下划线相连
- 1
2
3
4
- 1
local LOCAL_CONST = {
SPINE_ID = 1,
...
}
—|—
* 1
local GHOST = ConstUI.GHOST_STATE
—|—
-
所有枚举,均采用 大驼峰命名法 ,并必须加Enum前缀,枚举值采用 大驼峰命名法
- 1
2
3
4
5
- 1
EnumExample = {
Example = 1,
ExampleExample = 2,
ExampleExampleExample = 3,
}
—|—
* 全局枚举值,应该在 global_enum中分模块定义;禁止在Ui或Cache中定义全局枚举值。
- 全局函数,需要根据功能,分table调用,比如 table.insert, table.remove 这种。除了必要main.lua内函数,禁止没有范围,直接命名使用全局函数
3. 使用规则及技巧
3.1 变量
-
尽量使用local变量而非global变量
-
同一行变量赋值不要超过 3 个,且无值时用nil显式赋值
-
常量避免使用 magic number
- 1
2
3
4
5
6
7
- 1
-- 9.8就是 magic number
local speed = time * 9.8
-- good style
_G.ACCELERATION = 9.8
local speed = time * ACCELERATION
—|—
-
禁止使用三元操作 and or
- and 后值若为boolean类型,会不符预期,给后期查错造成困扰。
-
默认参数
- 1
2
3
4
5
6
7
8
9
10
- 1
function funcMethod(param)
param = param or defalutValue
...
end
-- 默认值为true
param = param ~= false
-- 默认值为false
param = param or false
—|—
-
_ 做为想忽略的变量
- 1
2
3
- 1
for _, v in pairs(tab) do
...
end
—|—
* 针对上面情况,即使for循环,尽量不要用 i, k, v,而采用更为详细一些的变量名称
3.2 table
-
表的拷贝 此方法在表内条目大于2000时会失效
- 1
toTable = {unpack(fromTable)}
—|—
-
判断空表
- 1
2
3
4
- 1
#t == 0 不能判断表为空
应该使用: next(t) == nil
PS:next(t) 返回的是第一个找到的键的值,对于存储布尔类型值的表,可能返回false,因此必须与nil比较。
—|—
-
更快的插入值
- 1
2
3
4
5
- 1
-- 慢,不推荐
table.insert(t, value)
-- 更快!推荐
t[#t + 1] = value
—|—
3.3 函数
-
明确函数的功能,精确(而不是近似)地实现函数设计
-
接口函数参数的合法性检查,明确由 调用者 负责,而非函数设计者
-
防止将函数的参数作为工作变量,有可能误改参数内容
-
为简单的功能编写函数
1 2 3 4 5
value = (a > b) and a or b
function max(a, b)
return (a > b) and a or b
end
—|—
-
不要设计多用途而面面俱到的函数
- 多功能集于一身的函数,很可能使函数的理解、测试、维护变得困难
-
功能不明确较小的函数,特别使仅有一个上级函数调用它时,应考虑把它合并到上级函数中,而不必单独存在。
-
设计高扇入、合理扇出(小于7,一般3-5)的函数
- 说明:扇入指有多少上级函数调用它;扇出是指一个函数直接调用其他函数的数目。
- 扇入过大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。
- 扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,表明函数的调用层次可能过多,不利于程序的阅读和函数结构的分析,并且程序运行时会对系统资源(如堆栈空间等)造成压力。
- 良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
3.4 其他
对于大数据量的特殊优化
-
多次 重复使用的全局接口,可以用局部变量保存下再使用
- 1
2
3
4
5
6
7
8
9
10
11
12
- 1
-- example1
for i = 1, 10000000 do
local x = math.sin(i)
end
-- example2
local sin = math.sin
for i = 1, 10000000 do
local x = sin(i)
end
example2 效率会高于 example1
—|—
-
创建 非常多 的小size表时,应预先填充好表的大小
- 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 1
-- example1
for i = 1, 10000000 do
local tab = {}
tab[1] = 1
tab[2] = 2
tab[3] = 3
end
-- example2
for i = 1, 10000000 do
local tab = {1, 1, 1}
tab[1] = 1
tab[2] = 2
tab[3] = 3
end
example2 效率高于 example1
—|—
- 很多个 字符串连接,使用 table.concat 而非 ..
<u