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
            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
                    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
            --[[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
      3
            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
            --幻影设置  
    GHOST_STATE = {       
        INTERVAL_TIME = 80,     --出现间隔时间(毫秒)  
        FADE_TIME = 500,        --fade时间(毫秒)  
    },  

—|—

  • 类内配置常量,local引用, 均用大写,单词间以下划线相连

    • 1
      2
      3
      4
            local LOCAL_CONST = {  
        SPINE_ID = 1,  
        ...  
    }  

—|—
* 1

            local GHOST = ConstUI.GHOST_STATE  

—|—

  • 所有枚举,均采用 大驼峰命名法 ,并必须加Enum前缀,枚举值采用 大驼峰命名法

    • 1
      2
      3
      4
      5
            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
    -- 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
            function funcMethod(param)  
    	param = param or defalutValue  
    	...  
    end  
      
    -- 默认值为true  
    param = param ~= false  
      
    -- 默认值为false  
    param = param or false  

—|—

  • _ 做为想忽略的变量

    • 1
      2
      3
            for _, 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   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
            -- 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 而非 ..

<u

糖果

糖果
LUA教程

如果不小心安装错 SQL Server 为 Evaluation 的版本,要小心当超过 180 天之后,系统就会无法正常使用了 这几天遇到一个蛮特别的案例,原本收到的问题是 “维护计划” 忽然无法使用,即便是里面没有任何的Task,都无法顺利地执行。但从对方所提供的错误消...… Continue reading

PLUM NIZ静电容键盘怎么样?

Published on September 25, 2020

程序员如何选择合适的机械键盘

Published on September 18, 2020