当然,写代码就跟写文章一样,每个人或多或少都有自己的风格。不同的语言也就像不同的文体一样,也有自己的独特的风格。Lua是一门脚本语言,写起来轻松惬意,但不代表它没有属于自己的风格指南。
好的代码风格基于 可读性 和 一致性 。 代码更多的时间是给人看的 ,如果思考好了结构和逻辑,写代码的过程其实很快。风格的一致性也很重要,这样可以减少复杂度和理解成本。养成一种良好的代码风格会形成一种良好写代码习惯,这种习惯会使编码事半功倍。
下文将从 命名 , 作用域 , 模块 , 注释 和 惯用法(精巧用法) 等方面来说明Lua的代码风格,文章的最后会附上一些 参考资料 的链接以供读者拓展阅读。
最好的代码是 自说明代码 ,这种代码不需要多余的注释,其本身便具备了描述作者意图的信息。一种好的命名风格是自说明代码的基础。
firstName
、 lastName
。 FirstName
、 LastName
、 CamelCase
,也被称为Pascal命名法。 login_btn
。 MIN_GAP_TIME
。 采用驼峰法或者下划线法都不太重要,重要的是你采用了自己喜欢的一种命名法,然后 一直保持下去 。
通常作用域范围更大的变量名要比作用域范围更小的变量名具有更多的描述信息。例如: i
经常用于循环中充当计数变量,而将其作为全局变量使用容易导致诸多问题。
对于变量(包括函数),小驼峰式命名法或小下划线命名法是一个好选择。比如: curSpeed
表示当前速度, canDrop
表示是否能掉落等等。
对于布尔值型的变量,通常前缀加上 is
可以方便理解,比如 isRemoved
比 Removed
更加能表示这是一个布尔值变量。
Lua中有一种特殊的变量名: _
,常用来表示可以被忽略的、不会使用到的变量,常使用在循环中。
-- `_`表示表的键可以被忽略,只在循环内使用表中的值`v` for _ , v in ipairs ( t ) do print ( v ) end |
在表的循环中和函数参数列表中, i
常表示 ipairs
下的数组下标, k
常表示 pairs
下的键, v
常表示对应的值, t
则表示表。
for k , v in pairs ( t ) do . . . end for i , v in ipairs ( t ) do . . . end mt . __newindex = function ( t , k , v ) . . . end |
Lua里没有严格的常数定义标识符,所以对于常数的命名格外重要。
常数一般采用大下划线命名法。这样每个字母都大写,十分醒目,且各个单词都用下划线分割,便于阅读。
比如: MAX_SPEED
表示最大速度, IS_SHOW_DEBUG_ERROR_MSG
表示是否显示报错消息等等。
为了不与变量名和常数名混淆,类名通常使用大驼峰式命名法,即首字母大写。比如: TouchManager
表示触摸管理器类。
包名和模块名通常很短,并且全部小写,单词间并没有下划线区分。比如:文件读取库名为 lfs
,表示 Lua File System
;XML解析库名为 lxp
,表示 Lua XML Parser
等等。
通常为了不与类名混淆,对于文件名,经常使用小驼峰式命名法或小下划线命名法。
Lua的作用域以关键字 end
进行标识。
对于变量,有一条原则:在一切能使用 local
修饰的情况下,使用 local
进行修饰。
因为不用 local
修饰的变量会自动变成全局变量。全局变量十分危险,很容易被篡改而不知道在哪里被篡改了,这很容易导致顽固的bug出现。并且全局变量的处理速度也比局部变量的速度要慢很多。
所以,尽可能的用 local
来修饰变量。
有时候,用 do .. end
可以用来明确限定局部变量的作用域。
local v do local x = u2 * v3 - u3 * v2 local y = u3 * v1 - u1 * v3 local z = u1 * v2 - u2 * v1 v = { x , y , z } end -- x,y,z的作用域结束,被系统清理 local count do local x = 0 count = function ( ) x = x + 1 ; return x end end -- x的作用域结束,被系统清理 |
Lua中有一个叫 module
的公有函数,此函数的作用是将一组变量和函数打包在一个模块名下,便于其他文件 require
。但是这个函数受到了诸多的 指责 ,原因是其会创建一个公共变量,并且这个公共变量中的所有细节都会暴露出来。这其实十分不符合面向对象的规范。
以下有一种办法可以避免这个问题,即不采用 module
函数进行打包。
-- hello/mytest.lua local M = { } -- 私有变量 local function test ( ) print ( 123 ) end function M . test1 ( ) test ( ) end function M . test2 ( ) M . test1 ( ) ; M . test1 ( ) end return M -- 关键 |
以下是导入此模块的方法。
local MT = require "hello.mytest" MT . test2 ( ) |
Lua内没有类这个变量类型,但是通过Lua的 metatable
可以轻松实现类的继承,多态等等特性。关于Lua中类的实现原理,请参考我之前写的这篇博客:Lua中实现类的原理。
通常在 --
前加上一个空格。
return nil -- not found (建议) return nil --not found (不建议) |
注释通常用在函数接口,或者复杂,精巧的逻辑上。
对于接口的注释,可以按照javadoc类似的来写。
-- Deletes a session. -- @param id Session identification. ------------------------------------- function delete ( id ) assert ( check_id ( id ) ) remove ( filename ( id ) ) end |
原因:
local
的变量会在作用域结束时释放其内存 local
的变量会比全局变量的存取更快 -- 不推荐 if obj ~ = nil and willBreak == false then -- ... end -- 推荐 if obj and not willBreak then -- ... end |
原因:Lua在逻辑判断时将所有 非false
和 nil
的逻辑判断视为真,反之视为假,不需要再与布尔值和 nil
进行比对。
但是,在需要对 false
和 nil
进行区分时,需要写明 ==
: obj == nil
和 obj == false
。
范式: param = param or defaultValue
function setName ( name ) name = name or 'noName' -- ... end |
原因: or
会在第一次为 true
的时候断路,返回其判断的最后一个值。所以当 name
为空时, name or 'noName'
返回为 'noName'
,这会将 name
的值自动设置为 noName
。
u = { unpack ( t ) } |
用 #t == 0
并不能判断表是否为空,因为 #
预算符会忽略所有不连续的数字下标和非数字下标。
正确做法是:
if next ( t ) == nil then -- 表为空 -- ... end |
因为表的键可能为 false
,所以必须与 nil
比较,而不直接使用 ~next(t)
来判断表是否空。
-- 更慢,不推荐 table . insert ( t , value ) -- 更快,推荐 t [ # t + 1 ] = value |
原因: []和#
避免了高层的函数调用开销。
这篇文章是基于 Lua Style Guide 而来。
语言的风格大致是通用的,在Python里,有一种叫pythonic的代码风格,详见: 让你的python代码更加pythonic 。
对于任何程序员,我都力荐 《代码大全》 这本书。在里面,你可以找到十分完备的从设计,架构到具体编码,注释,到团队协作等等相关的引导。
还有几本书: 《程序员修炼之道》 , 《高效程序员的45个习惯》 , 《重构》 。它们可以作为《代码大全》的补集存在。
关于《高效程序员的45个习惯》这本书,我进行了总结和提炼,阅读之前不妨看看这篇 读书笔记 。