BeWithYou

胡搞的技术博客

  1. 首页
  2. web前端/Javascript
  3. Lua编程风格(测试markdown编辑)

Lua编程风格(测试markdown编辑)


今天读了一篇关于lua编程风格的文章,放到了团队的wiki上。尝尝鲜用markdown写的,感觉很爽。跟富文本编辑器相比,简直是无脑排版。
之后改造了一下博客的代码,现在本站支持markdown编辑日志啦。这边篇文章就是用markdown写的。放上来看看效果如何。

Lua Style Guide

[翻译自原文地址]

引言

在应用此Lua编程风格导引之前,先参考下Python style guide里的注意事项,这些对于Lua也同样有效。我们的代码被读的次数远比被写的次数频繁,这份编程风格导引,意在保持代码编写统一性德前提下,提升代码的可读性。保持编码风格统一是非常重要滴,我们越来越多的发现,在项目之间、项目内部、甚至是每个单独的模块和函数中都是如此。但是最重要的是——你需要知道在什么时候可以不用保持统一。因为有些时候,统一的编程风格并不适用于所有场景。记住,当遵循某种编程风格会造成代码可读性下降的时候,建议你暂时抛弃他吧。

Lua有它自己的语法和固有用法,所以我们尝试制定属于Lua自身的编程风格。尽管如此,在其他编程语言尤其是脚本语言中,许多工程师已经积攒了数十年的教训,我们也没有必要再去踩这些坑。所以我们可以从下面这些语言的编程风格中获得一些灵感:

[Python Style Guide]
[Perl Style Guide]
[Various Primarily C/C++ Style Guides]
编程风格也是一门艺术。规则总是有一些任意性,但是他们总是有着显而易见的根本原因。提供这些建议还不够,更有用的是从人为的角度深入理解这些风格被广泛推荐的原因。

[How To Write Unmaintainable Code by Roedy Green]
[Perl Best Practices by Damian Conway]
[Code Complete by Steve McConnell]
现在,对于Lua来说,如何定义一个好的编程风格,我们将从一些权威的代码中寻找思路。因为它们大多来自官方文档,Lua原作者以及其他较为有名的出处:
[Programming in Lua] by Roberto Ierusalimschy
[Lua 5.1 Reference Manual] by R. Ierusalimschy, L. H. de Figueiredo, W. Celes Other LuaBooks such as Beginning Lua Programming
Examples Lua programs in the "test" folder of the Lua distribution.
Well known modules such as in Kepler[1] or on LuaForge.

格式

缩进

缩进 - 通常使用2个空格。Programming in Lua, the Lua Reference Manual, Beginning Lua Programming 以及 the Lua users wiki里都是这么用的。(为什么要这样呢,我不知道,也许是因为Lua语句可以一层一层叠的很深吧,甚至在LISP或者其他函数式编程风格中也是如此。或者仅仅是代码因为这样看起来更小巧易懂)。

for i,v in ipairs(t) do
  if type(v) == "string" then
    print(v)
  end
end

命名

变量名长度 - 这是一条通用规则(不仅适用于Lua),用大小字母命名变量比用小写更具有描述性。举个例子,在一个大型程序中,小写i作为一个全局变量名是一个很差的选择。但是它用作一个小循环中的计数器确实是非常合适的。

值和对象的变量名 - 保存值和对象的变量,我们一贯用小写和缩写来命名(比如color)。《Beginning Lua Programming》这本书里对于用户变量用了驼峰命名法。这很好的区分了用户定义的变量和内置变量。

  • 布尔变量命名 - 用一个"is"的前缀来命名布尔变量和用作判断的函数,这将会非常有用。比如is_directory比directory更合适用来命名布尔变量,因为后者看起来更像是存了一个目录对象。

函数命名 - 函数通常和值与对象的命名方式相同,函数也是一类对象。由多个单词构成的函数名可以聚在一起,像getmetatable,在标准Lua函数里就是这么做的。尽管如此,你也可以选择用下划线命名法,比如print_table。有些有其他语言编程背景的人还会用驼峰法,就像obj:GetValue().

Lua内部变量命名 - Lua 5.1 Reference Manual - Lexical Conventions里面说, “按照约定,Lua中一个变量名以下划线开头后面跟着大写字母(比如_VERSION)是要被保留作为内部全局变量使用的”。这些变量通常都是常量,但是也有例外,比如_G。

常量命名 - 常量,尤其是表示简单值的常量,通常用ALL_CAPS方式命名,单词之间可以用下划线分开(可选的)。

模块/包的命名 - 模块名通常是小写的缩写的名词,单词中间没有任何分隔符。至少在Kepler项目中是如此:luasql.postgres (not Lua-SQL.Postgres). Examples: "lxp", "luasql", "luasql.postgres", "luasql.mysql", "luasql.oci8", "luasql.sqlite", "luasql.odbc", "socket", "xmlrpc", "xmlrpc.http", "soap", "lualdap", "logging", "md5", "zip", "stable", "copas", "lxp", "lxp.lom", "stable", "lfs", "htk". 但是,我们也可以看到模块下面的评论也暴露出对于modules被用作class的担忧。

当你想忽略某个变量时,可以用只有下划线"_"命名的变量,它通常被用作占位:

for _,v in ipairs(t) do print(v) end

备注:类似于Haskell,Erlang,Ocaml,Prolog中关于"_"的用法,"_"通常有个特殊含义,用于模式匹配中充当匿名(可忽略)变量。而在Lua中,"_"只是一个约定,没有特殊的内在含义。通常情况下,可以标记未使用的变量的编辑器会主动忽略"_"的变量(比如LuaInspect)。

i,k,v和t经常被这么用:

for k,v in pairs(t) ... end
for i,v in ipairs(t) ... end
mt.__newindex = function(t, k, v) ... end

M经常被用作“当前模块表”(比如PIL2, 15.3)。

self表示一个方法作用的对象(就像this在C++和Java里的用法一样)。事实上,self这种用法是一种语法糖:

function Car:move(distance)
  self.position = self.position + distance
end

类的命名(或者说meatballs表示的类)可以大小写混用(比如BankAccount),当然也不是必须这样。如果是名字中有缩写(就像XML)的话,可以只大写首字母(XmlDocument)。

匈牙利命名法(Hungarian notations). 把语义信息写进变量名里对于代码阅读十分有益,特别是当语义信息无法通过其他手段被推断出来的时候。但是过分强调这点也许是多余的,它也可能会降低代码的可读性。在一些静态语言(比如C)中,编译器是知道数据类型的,所以将变量类型写进变量名中有可能是多余的。但是,变量类型也不是语义信息中唯一重要的部分。

local function paint(canvas, ntimes)
  for i=1,ntimes do
    local hello_str_asc_lc_en_const = "hello world"
    canvas:draw(hello_str_asc_lc_en_const:toupper())
  end
end

上面这个例子中,关于变量命名的约定对读者来说意味着如下几点。canvas是一个canvas对象,可能继承了Canvas,这是从其他角度无法推断出来的语义信息。ntime是一个整数,代表画的次数。i是约定俗成的整数下标,就算换成其他字母也要保持代码精简。而_str_asc_lc_en_const是一个多余用法,可能会造成迷惑,它应当被移除掉:显然这个变量应该是一个常量,用小写的ASCII英文字符来表示。

库和特性

除非必须的情况,避免用debug库,特别是正在运行的是一份可信赖的代码的时候。(用debug库在某些时候相当于一个hack: StringInterpolation.)

避免使用过时的特性。在Lua5.1中,这些过时的特性包括table.getn,table.setn,table.foreach[i]以及gcinfo。可以参考[Lua 5.1 Reference Manual - 7 - Incompatibilities with the Previous Version]

作用域

能用local就用local,别用全局变量。

local x = 0
local function count()
  x = x + 1
  print(x)
end

全局元素的作用域更大,生命周期更长。所以他们会增加[连接]和复杂性。[1]不要污染程序的环境。在Lua中,访问local的东西也比访问全局的要快[PIL 4.2]因为访问全局的元素需要在一个table中实时查找,而local的元素是存在寄存器中的[ ScopeTutorial ].
DetectingUndefinedVariables给出了一个检查粗心使用全局元素的方法。在Lua代码中,全局元素有些时候是由于你的错误拼写或者其他隐藏的错误造成的。
很多情况下,使用do-blocks来限制local变量的作用域是十分有效的[PIL 4.2]:

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

local count
do
  local x = 0
  count = function() x = x + 1; return x end
end

全局变量同样可以通过Lua的module系统[PIL2 15] 或者 [setfenv]来缩写作用域。

模块

Lua5.1的modules系统经常被推荐使用。然而,它也受到了不少批评。可以参看LuaModuleFunctionCritiqued。总的来说,你可能会考虑这样来写一个模块:

-- hello/mytest.lua
module(..., package.seeall)
local function test() print(123) end
function test1() test() end
function test2() test1(); test1() end

然后这么使用它:

require "hello.mytest"
hello.mytest.test2()

这样不好之处在于,它在所有模块中创建了一个全局变量hello(副作用),全局的环境也通过hello的table暴露出来了,比如hello.mytest.print == _G.print(这对于一个沙箱来说是有害的,而且看起来怪怪的)。

这些问题可以这样避免,我们大可不必使用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()

一个包含了有构造函数的类(面向对象概念)的模块,可以有好几种打包方法。这里有个建议:[*2]

-- file: finance/BankAccount.lua

local M = {}; M.__index = M

local function construct()
  local self = setmetatable({balance = 0}, M)
  return self
end
setmetatable(M, {__call = construct})

function M:add(value) self.balance = self.balance + 1 end

return M

这样定义的模块只包含一个类(至少只包含一个public类),也就是它自己。
可以这么使用:

local BankAccount = require "finance.BankAccount"
local account = BankAccount()

甚至这样也行:

local new = require
local account = new "finance.BankAccount" ()

上面这个例子有点Java意思,"finance"这个包是全部小写的,而BankAccount这个类名是混合大小写的(驼峰的),对象是小写的。注意这点好处,类很容易和小写的实例区分开来。如果你要这么用BankAccount:add(1),很可能是错的。因为冒号:是对象调用方法使用的,而BankAccount明显是一个类。

上面这种方法也不是唯一的。你将会发现一些其他的风格:

account = finance.newBankAccount()
account = finance.create_bank_account()
account = finance.bankaccount.create()
account = finance.BankAccount.new()

本文认为,如果没有好的理由,采用这么多种风格并不是一件好事。

注释

请在 -- 后面加上空格。

return nil  -- not found    (推荐这么用)
return nil  --not found     (不推荐)

(上面的例子来自 luarefman, PiL, luagems minus chap 21, BLP, and Kepler/LuaRocks.)

其实,关于注释并没有一个约定俗成的标准。

文档注释可以参看(DecoratorsAndDocstrings). 这里也推荐一下POD(按需打印)的方式(参看LuaSearch以及LuaDoc).

Kepler 有些时候用doxygen/Javadoc-like的风格:

-- taken from cgilua/src/cgilua/session.lua
-------------------------------------
-- Deletes a session.
-- @param id Session identification.
-------------------------------------
function delete (id)
  assert (check_id (id))
  remove (filename (id))
end

end终止符

因为"end"会作为很多结构的终止符,所以最好在结构终止的地方加上注释(特别是一个很大的block中),用来说明它属于哪个结构:[*3]

for i,v in ipairs(t) do
  if type(v) == "string" then
    ...lots of code here...
  end -- if string
end -- for each t

如果你的编辑器没有block匹配功能,但是有圆括号匹配功能,那么可以这么用(当然你也可以换个更好的编辑器):

for i,v in ipairs(t) do --(
  if type(v) == "string" then --(
    ...lots of code here...
  end --) if string
end --) for each t

Software Licensing 这段懒了没有翻

The choice of software license for your Lua code depends on your goals and what type of code it is (e.g. module or application). Licensing choice is particularly significant for modules, which are often distributed with other modules and applications.

There are advantages to licensing Lua modules, or at least those intended for the general Lua community, under the same terms as Lua[2] itself, which as of version 5.0 is the MIT license[3]. Not only is the MIT license a very simple to understand and unrestrictive license (in fact, no more restrictive than Lua itself), but consistency in licensing between modules and with Lua allows simplified distribution of bundles of modules and Lua together, such as for distributions and embedded versions of Lua. (This basic approach has worked very well in the past for the Perl language, which has thousands of modules most entirely under an MIT-like license called the Artistic License, under which Perl is also distributed. Perl modules often indicate their licensing simply with the statement "Licensed under the same terms as Perl itself.") Those advantages of using an MIT license are reduced, though not eliminated, if your Lua module acts as a binding to some C library released under some very different license such (L)GPL or a closed source one since the latter code impose stronger restrictions on distribution anyway. Avoid, whenever possible, writing your own license or adding additional clauses but rather consider strongly the words of warning about consistency at the top of this document since inconsistency is a detriment to reusability, and reusability is a main advantage of modules.

Lua编程技巧

在一个条件判断中测试一个变量是否为nil(前提是它不能为false),只需要简单的写变量名即可,没必要用它显式的和nil比较。Lua认为nil和false都是false,其他所有值都是true。

local line = io.read()
if line then  -- instead of line ~= nil
  ...
end
...
if not line then  -- instead of line == nil
  ...
end

但是,如果变量也可能为false,那么你就必须显式的比较了:line == nil v.s. line == false.

and 和 or 可以被用于判断条件:

local function test(x)
  x = x or "idunno"
    -- rather than if x == false or x == nil then x = "idunno" end
  print(x == "yes" and "YES!" or x)
    -- rather than if x == "yes" then print("YES!") else print(x) end
end

克隆一个小的table t(注意:table的大小限制是依据操作系统的,在某些系统中甚至只能有2000这么大)可以这么做:

u = {unpack(t)}

判断一个table是否是空的(包括非整数的下标,#t是会忽略的),可以这么做:

if next(t) == nil then ...

在一个数组后追加元素,用t[#t+1] = 1比table.insert(t, 1)更简洁有效。

设计模式

Lua是一个小巧的语言,少量的简单语句块却可以有很多强有力的方法来构建。因为这种自由性,在设计模式中,语言的自省性也是必需的。Lua里面实现某种可以被复用的功能,通常都有惯用的做法或者设计模式。(e.g. ObjectOrientationTutorial or ReadOnlyTables)。这些问题可以参看LuaTutorialSampleCode

编码标准

这里有一个关于编码标准的列表,来自于许多Lua项目:

个人贡献

This section contains less refined or more subjective content not yet incorporated into the main text above.

[*2] (Advocates of the style described above include DavidManura, and others like RiciLake have mentioned similar things. (add your name here))

[*3] Noted by GavinWraith

[*4] JulioFernandez


PAGE COMMENTS

I think "Lua style" is too subjective to reach consensus on, therefore this page is not workable. Suggest page rename to DavidsLuaStyleGuide?. --JohnBelmonte

The purpose of this document is to help users improve the style of their coding. Lua has some conventions that are commonly acknowledged in some form or even stated in the standard Lua references (e.g. avoiding globals, limiting scope, avoiding debug library, using and/or to shorten conditions, etc.) Indeed, Lua style is more diverse than in other languages, and there are many cases with an absence of consensus. However, consensus is not required: it is useful merely to describe the various approaches commonly used, and indicate where they are used, so that users can more effectively choose conventions in their own projects. A "Personal Preferences" section is provided for more subjective styles or personal opinions. --DavidManura

An attempt at a style guide should probably limit focus to the Lua language itself (i.e. exclude C API usage). --JohnBelmonte The style of how you design a binding, and the style of how you write binding in C and C++, are legitimate discussions for a style guide for a language designed for embedding. -- RiciLake

RecentChanges · preferences edit · history Last edited September 16, 2012 8:36 pm GMT (diff)

回到顶部