最近都是在看lua代码, 并在其基础上进行修改和增加功能. 在代码中看到了不少个人感觉很不好的现象, 就忍不住吐槽一下.
lua做为一门动态语言, 其弱类型及灵活性, 的确大大加快了开发和修改的效率. 但是这种自由有时不加以限制的使用, 有时候可能会造成很严重的后果.
先以我有限的理解说一下lua语言对于访问控制的有限支持.
定义变量或函数只有加上local才代表局部变量或函数, 否则只要这个模块被加载就可以被其他模块访问.
require代表要去加载某个模块, 如果两次调用require去调用同一模块并不会造成同一模块的多次加载.
lua5.1之后加入的module(…,seeall)函数可以自动导出lua函数中的非local函数及变量
在我所看到的代码中到处都是大文件(大的都有1W多), 魔数, 全局变量, 循环依赖, 如果某一模块已经加载并不会再去调用rquire函数等各种问题.
全局变量的存在使得看似将程序分了多个模块, 但是由于全局变量可以被所有模块相互访问和修改, 全无依赖层次可言. 最后不得不将这些模块化为一个整体模块来使用.
魔数让看代码的人搞不情作者意图, 还多了许多可能修改错误的机会.
并没有显式调用require会导致实在看不情模块之间的依赖关系, 对于把握整个程序的结果来看非常不利.
单个文件1W行的代码就像是一大滩稀泥放在那, 让你敢看不敢碰.
虽然lua给予的代码访问限制很少, 甚至于还提供了module(…,seeall)这样方便的函数. 但是如果我们不加限制的去使用只会使程序成为一滩无法维护的烂泥.
《C Programming Language》中有这样一句话 “…限制不仅提倡了经济性, 更在某种程序上提倡了设计了优雅”.
在参考了网上大神的lua源码和《lua程序设计》以后, 我觉得可以采用部分限制来提供代码的可读和可维护性.
require函数的作用就是去加载一个没有被加载过的模块, 并将此模块的返回值记录下来, 作为每次调用require函数的返回值来使用. 那么便可以在require函数上来做文章.
首先除非有必要, 应该舍弃module(…,seeall)函数的调用, 采用返回表的方式来返回某个模块的所有导出接口. 代码如下:
--bar.lua
local bar = {}
local v1 = 3
local v2 = 4
function bar.test()
print("----bar.lua---, v1, v2", v1, v2)
end
return bar
--foo.lua
local bar = require("bar")
bar.test()
虽然相比module(…,seeall)函数来讲, 代码中可以要多打几个字. 但是相对于这种方式提供的好处来讲却是巨大的.
因为采用require函数的返回值来调用相应的模块函数, 那么就限制了只要模块有依赖就必须去显式调用require.
就算不小心写漏了local, 只要不会有意在bar表中去赋值, 那么外部模块也并不能去访问bar模块中的v1和v2变量
由于lua中字符串管理是采用类似《C接口与实现》中的atom的管理方式, 因此字符串比较与整数比较的效率几乎是一样的.
那么其实可以通过直接用字符串传入参数或采用下面这种方式来避免魔数.
--foo.bar
local foo = {}
foo.state = {ONE = "the first step of state machine", TWO = "the step is do something"}
foo.test(step)
if(step == foo.state.ONE) then
dosomething()
elesif (step == foo.state.TWO) then
dosomething()
end
end
毫无疑问我更偏爱上面的方式, 即省了注释, 还能有与使用magic number一样的效率, 在调用此函数时, 还可以减少击键次数.
当然这些限制, 只能某种程序上解决一定的问题, 提高了代码的可读性. 但是像循环依赖, 刻意的全局变量等设计问题, 是无法靠变这些限制来解决的.