最近梳理了下公司游戏开发的编码规范,考虑到平时开发主要以Lua语言为主,所以本文也以Lua代码作为案例,除了Lua语言自身的特性,其他规范思路其他语言亦可适用。
本文主要从命名规范、格式规范以及性能相关三个方面进行规范的说明,后续开发过程中若有新的规范制定,持续更新本文内容。
命名规范
大规则:命名需做到“闻其名、知其意”,在不影响其表意的基础上精简命名长度。
local变量命名
以“小驼峰命名法”作为命名规范,如:myKingdom、userInfo等。
若对视图层变量进行命名,可以“视图类型+表意”方式命名,可参考:
1 | -- 确认按钮 |
亦可以“表意+视图用处”方式命名,可参考:
1 | -- 名字下方的背景图 |
为了和方法内局部变量做区分,全文件范围的local变量在上述规则基础上,以下划线开始,如:
1 | local M = class("ChatForm", Form) |
table中的key,以小写字母+下划线命名,比如:
1 | M.TabId = { |
有时为了简化书写、简化阅读,从成员变量中取视图变量,赋值给local变量的时候,命名可以简写为“视图用途”或“视图类型”,如:
1 | function M:updateTopArea() |
成员变量命名
lua中成员变量一般指存储在self里的变量,其命名规则为:在local变量命名规范基础上,以下划线开始,如:
1 | local topArea = lc.createNode(...) |
存在类里(比如:M类)的变量,其命名同样以下划线开始,如:
1 | function M:initDefines() |
方法命名
方法的命名大小写规范,一般以小驼峰命名,如:
1 | function M:updateTopArea() |
但是考虑到C#转Lua导致一些Unity内置方法名必须是大驼峰命名,为了保持一致,可以考虑项目中的所有方法以大驼峰方式命名,如:
1 | local M = class("InfoPanel") |
方法命名格式,一般是“动词+名词”,可参考如下:
1 | -->>>>> 创建某个组件或初始化某块区域,以create、init开头 |
格式规范
如果把一个代码文件想象成一个房间,房间里的东西应该是近似强迫症一样地整齐摆放好,而不是像杂货堆,这是本小节规范格式需要达到的效果——“整齐、清爽、不杂糅”。对于不艰涩的逻辑,全篇代码甚至可以一行注释都不加就能读懂,类似这样:
1 | local M = class("OptionForm", Form) |
空格规范
逗号后面留空格,比如:
1 | -- 正例 |
运算符、等号两边留空格,比如:
1 | -- 正例 |
小括号内侧不留空格,比如:
1 | -- 正例 |
条件、循环语句
if
语句块内侧,开始和结束不要留空行,比如:
1 | -- 正例 |
if
语句块前留空行,比如:
1 | -- 正例 |
如果if
前面存在单独一行代码,且这行代码与判断逻辑本身存在密切关联,if
前面可以不留空行,比如:
1 | local data = self._data |
elseif
语句块前面留空行,比如:
1 | -- 正例 |
else
语句块是否留空行取决于前面的if
或elseif
。
如果前面是elseif
,则else
前面必留空行,比如:
1 | -- 正例 |
如果前面是if
,且if
语句块是多行,则else
前面必留空行,比如:
1 | -- 正例 |
如果前面if
语句块是单行,且else
语句块也是单行,则else
前面可以不留空行,比如:
1 | local data = self._data |
如果前面if
语句块是单行,但else
语句块是多行,则else
前面需要留空行,比如:
1 | -- 正例 |
上面else
留空行的规则可以简单理解为:只允许if
和else
里都只有一行代码时,可以不留空行,其他情况均需要留空行。
while
、do ... while
、for
循环语义块前面留空行规则与if
相同。
方法定义
每个定义的方法之间需要留空行,比如:
1 | -- 正例 |
方法定义内部,开始和结束不留空行,比如:
1 | -- 正例 |
视图的创建与刷新
不同组件的创建、刷新逻辑之间以空行分隔,比如:
1 | -- 正例 |
上述代码,反例2虽然看起来也很整齐,但是一旦涉及相关代码删除,或是添加新的组件创建代码,就需要上下移动光标到指定位置修改3个地方代码,且我们没法保证所有的节点创建只有create
、lc.addChildToPos
和self._xxx = xxx
三个部分,所有节点没法做到统一,为了方便维护,还是建议不同的组件以空行分开。
其他
上述规则没有提及的部分,暂时没有硬性规定,但是在书写的过程中,还是需要我们凭借语感来判定是否需要用空行分开,何为语感:
就是当我写了一坨很厚的代码,review起来感觉有点费力时,用空行将其分开后,再看这坨代码,感觉舒服多了,这就是语感。
合并行规范
local变量声明
有时我们需要在一开始声明并赋值多个local变量,但是这些变量存在着一定关联,可以将多个变量合并为一行:
1 | -- 多行声明local变量 |
条件语句
有时条件语句里面没有包含过多的逻辑代码,比如只有:break
、return
等,我们允许这些关键词和if
条件判断放到同一行,比如:
1 | function M:checkUpdateTopArea() |
但是当if
条件后跟着elseif
、else
时,不建议将break
等关键字写在同一行,这样会让代码结构看起来很难看,比如:
1 | -- 正例 |
缩进规范
大规则:所有的Tab
由制表符改为4个空格。
VS Code里可设置,如下图:
之所以如此改,是考虑一份代码文件里,缩进有时用制表符,有时会用空格。而不同平台下,解析制表符得到的缩进和4个空格的缩进宽度是不一致的,所以干脆将Tab
按键由制表符统一为4个空格。
基于此规则,下面提到的“缩进”表示“缩进4个空格”。
长条件语句
我们经常会遇到if
语句太长,超出一屏的情况,这时会考虑将if语句写成多行,而换行后的条件判断,建议以2个缩进书写,比如:
1 | -- 单行if语句 |
两个缩进的好处是:明显与if
语句里的逻辑代码区分开,很容易知晓这个缩进是if
条件判断。
方法作为参数传入
比如按钮的创建封装,会经常把回调方法传进去,而调用这个封装方法时,会传入回调方法,可以有这么几种写法:
1 | -- 按钮的创建封装方法定义 |
其他代码逻辑走传统缩进规则即可。
提前return代替嵌套
有时因为逻辑需要,会涉及到很多的if条件嵌套,针对这种情况,建议走not
条件,提前return,比如:
1 | -- 嵌套写法 |
这样的写法是不是更加优雅?
性能相关
性能优化是一款游戏上线后必做的事,但是性能瓶颈有很大程度是代码书写不规范导致的,因此有必要在这里提一提影响性能的代码书写。
事件分发与接收
避免在循环中分发事件,比如:
1 | -- 正例 |
如果可以预见有些事件会在同一帧分发多次,建议在收事件的地方用dirty延迟刷新代替立即刷新,比如:
1 | -- 正例 |
事件接收的监听回调需要做好严格筛选、层层把关,防止出现不必要的刷新:
1 | -- 正例 |
逻辑筛选一般没有视图刷新来得复杂。
能做局部刷新,尽量不要全局刷新:
1 | function M:updateView() |
频繁调用的方法需要格外注意
在定时器这类每帧都调用的回调方法中,避免做遍历逻辑,尤其是无效的遍历,如果实在无法规避遍历的坑,建议将数组遍历改为Map索引:
1 | -- 正例 |
避免在频繁调用的方法里做复杂的计算,比如:
1 | -- 之前做的一款2D游戏,客人消费后往地上扔银币 |
引入缓存机制,避免做重复且结果不变的计算:
1 | function M:onSchedule() |
关于local变量和全局变量
在Lua里,访问局部变量比访问全局变量的速度快,而Lua里全局变量使用较多的则是self变量以及全局类名。在平时的调用中,在一段代码块里,若出现使用self获取相同的变量超过2次(包含2次),建议将self变量先赋值给local变量,再调用,比如:
1 | -- 正例 |
如果存在全局的类,调用多次,也建议先赋值给local变量,比如:
1 | -- WorldGrid是一个类,记录在全局变量里 |
尤其不要在循环中使用多重索引,比如:
1 | -- 正例 |
本文内容至此,若有不正确之处,欢迎评论区留言指正!