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
5self.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
7function (c, a, b)
if c then
return a
else
return b
end
end1
2
3for k, v in pairs(table) do
...
end
做为 函数参数部分 或 函数调用 的小括号 前后无空格
原因:
lua解析语法时是采用空格等分割来解析的,某些情况下,不小心加空格会导致非预期的结果
容易忘记相关空格,导致风格不统一,因此不如都不加
样例:
1
2
3
4
5
6
7
8function totable(val)
local tab = {}
if "table" == type(val) then
return val
end
tab[1] = val
return tab
end1
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--[[describe:
这是函数描述
param1:参数类型
参数描述
param2:参数类型
参数描述
[param3]:参数类型([]代表可选参数)
参数描述
return:返回值类型,返回值类型
返回值类型 - 描述
返回值类型 - 描述
]]
function funcName(param1, param2, param3)
...
return return1, return2
end
文件注释
1
2
3
4
5
6--[[
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
3function 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
4self.iExample
self.bExampleExample
self.sExampleExampleExample
self.tExampleArray
一般由 名词 或者 多名词 组成,不要随意简写
根据类的特性和使用场景,加上相关的后缀或者前缀
2.3 变量命名
采用 小驼峰命名法
使用 名词 或者是 形容词+名词 命名
尽量避免出现 仅靠部分字母大小写区分 的相似的变量
1
local posx, posX
2.4 常量命名
配置常量 ,均用大写,单词间以下划线相连
1
2
3
4
5--幻影设置
GHOST_STATE = {
INTERVAL_TIME = 80, --出现间隔时间(毫秒)
FADE_TIME = 500, --fade时间(毫秒)
},
类内配置常量,local引用,均用大写,单词间以下划线相连
1
2
3
4local LOCAL_CONST = {
SPINE_ID = 1,
...
}1
local GHOST = ConstUI.GHOST_STATE
所有枚举,均采用 大驼峰命名法,并必须加Enum前缀,枚举值采用 大驼峰命名法
1
2
3
4
5EnumExample = {
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
-- 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
10function funcMethod(param)
param = param or defalutValue
...
end
-- 默认值为true
param = param ~= false
-- 默认值为false
param = param or false
_ 做为想忽略的变量
1
2
3for _, v in pairs(tab) do
...
end针对上面情况,即使for循环,尽量不要用 i, k, v,而采用更为详细一些的变量名称
3.2 table
表的拷贝 此方法在表内条目大于2000时会失效
1
toTable = {unpack(fromTable)}
判断空表
1
2
3
4#t == 0 不能判断表为空
应该使用: next(t) == nil
PS:next(t) 返回的是第一个找到的键的值,对于存储布尔类型值的表,可能返回false,因此必须与nil比较。
更快的插入值
1
2
3
4
5-- 慢,不推荐
table.insert(t, value)
-- 更快!推荐
t[#t + 1] = value
3.3 函数
明确函数的功能,精确(而不是近似)地实现函数设计
接口函数参数的合法性检查,明确由调用者负责,而非函数设计者
防止将函数的参数作为工作变量,有可能误改参数内容
为简单的功能编写函数
1
2
3
4
5value = (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-- 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-- 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 而非 ..