本条例列举了用Lua语言编写Scribunto模块应该遵守的一些规定。遵守这些规范有助于Lua代码更加可读、性能更佳并易于维护。
代码逻辑与算法
不要定义全局变量
在Lua中,定义变量(包括声明函数)时应该尽可能使用局部变量,避免定义全局变量。例如,下面这个例子就是不恰当的,
myconst = 32
function myutil(s)
-- 一段代码
end
应当改成:
local myconst = 32
local function myutil(s)
-- 一段代码
end
对于MediaWiki库中指定了使用全局变量的,则可以按照MediaWiki库函数中的操作。此外,暂时规定模块:No globals也是一个例外,该模块会通过修改_G
的元表来防止定义全局变量,即通过修改全局变量本身来避免意外修改全局变量。
尽可能使用已有函数
很多模块需要实现的一些功能,没必要专门通过写函数来实现,而是尽可能使用已有的函数。例如,MediaWiki提供的mw
、Module:TableTools等元模块、libraryUtil
库都提供了相当实用的函数。这些情况下应该尽可能使用已经提供好的函数,而不需要自己去定义一个新的。例如:
local function trim(s)
return s:match "^%s+(.-)%s+$"
end
就完全可以写成:
local trim = mw.text.trim
对于很多模块都单独声明了,且没有已有函数提供该功能的,可以将其合并到新的或已有的元模块的函数中。
避免在Lua中展开模板或解析wiki文本
在Lua模板中,能用模块解决的,就不要调用模板。例如,在代码中,如需调用{{navbox}},应该调用require 'Module:Navbox'._navbox
,而不是frame:expandTemplate{name = 'Navbox', args = 参数}
这样会快得多。
能直接使用frame:callParserFunction
或frame:extensionTag
的(以及某些情况确实需要调用frame:expandTemplate
的),就不要使用frame:preprocess
。
能使用mw
库中的函数解决的,就避免去调用解析器函数。例如,已经有tostring(mw.title.getCurrentTitle)
,就不要去使用frame:callParserFunction('FULLPAGENAME')
,更不要使用frame:preprocess '{{FULLPAGENAME}}'
。
严禁频繁连接字符串
有时候需要对输出的字符串逐个增加,例如:
local result = ''
for _, s in ipairs(my_list) do
result = result .. '* ' .. s
end
return result
这种行为称为频繁连接字符串。这样每连接一次,都会创建新的字符串对象,然后将旧的字符串作为垃圾回收,影响性能。
如果需要多次追加字符串,应当使用表(数组),最后使用table.concat
连接。此外,频繁往表(数组)的末尾处追加时,建议使用t[#t + 1] = v
而非table.insert(t, v)
,这是因为Lua最原始的运算操作往往比调用函数更快。
local result = {}
for _, s in ipairs(my_list) do
result[#result + 1] = '* ' .. s
end
return table.concat(result)
在本例中,还可以使用更加友好、更加灵活的mw.html
库,例如:
local result = mw.html.create 'ul'
for _, s in ipairs(my_list) do
result:tag 'li':wikitext(s)
end
return tostring(result)
区分普通模块与数据
有时候需要在模块内存储表形式的大量数据。这种情况下,应该将数据放在专门的模块页面中,然后在运行时使用mw.loadData
而非require
获取这些数据。与require
相比,mw.loadData
的优点在于,无论通过多少模板、模块运行多少次,mw.loadData
在每个页面均只会运行和加载一次目标模块页面的数据,而不是每个模块都加载一次。这对含有大量数据的模块而言,性能非常高。
注意,使用mw.loadData
加载的表是只读的,并且是通过元表来访问字段的。因此mw.loadData
返回的对象不能写入任何字段(否则会出错),也不能使用table库中的函数或者#
操作符。但是,常规的字段访问以及通过pairs
、ipairs
进行迭代仍然是正常的。
代码格式
空行
空行可以将代码清晰地分成多个部分。通常,多个函数之间应当空行。
空格
空格可以使得代码更加美观。以下情况下,建议空格:
- 运算符之间,例如
c = a + b
。 - 用逗号分隔的多个变量之间,例如
tonumber('32', 7)
、return a, b
、for k, v in pairs(t) do
。- 注意:函数名称和参数之间不空格,但紧接字符串字面量、省略括号的情况除外。如
f(32)
不建议写成f (32)
、print 'Hello'
不建议写成print'Hello'
。
- 注意:函数名称和参数之间不空格,但紧接字符串字面量、省略括号的情况除外。如
- 单行注释的两个横线之后建议加一个空格。如代码后同一行内加入注释,两个横线之前建议加至少两个空格。
缩进
缩进可以使得代码层次清晰。虽然不像Python那样必要,但是没有缩进的代码以及缩进混乱的代码必然十分难读。
每一级缩进一般使用“制表符”(在代码编辑器中按键盘上的Tab键,手机版可能无法直接输入制表符,可以复制代码中已有的制表符)。尽管很多IDE都推荐使用空格代替制表符,但是在有兽档案馆不强求这么做。
此外,对于MediaWiki库的mw.html对象,可以在链式调用中根据层次逻辑调整缩进。
一个Lua页面内不允许混用不同类型(制表符、空格)缩进。要么全用空格,要么全用制表符。
注释
注释用于在代码中记录著作权协议、函数用法或提醒编辑者等内容,使得读者更加能够理解注释内容。注释可以用于:
- 阐明函数的返回值类型和用途,以及参数的类型和用途
- 将不再需要的代码注释掉。
- 说明代码中的逻辑。
此外,注释不应当“说废话”。例如x = x + 1 -- 将x增加1
是没有意义的。