我们使用tolua++手工绑定c/c++接口到lua中,在绑定的接口实现里,就需要取出传入的参数。tolua++中提供了一系列tolua_toxxx函数,例如:
这些函数都有一个def参数。乍一看,这些函数使用起来很简单。传入lua_State,传入参数在栈中的位置,然后再传一个失败后返回的默认值。
我重点要说的是这里这个失败,按正常程序员的理解,针对lua而言,什么情况下算失败呢?lua语言里函数参数支持不传,此时实参为nil,将nil转换为一个c类型必然失败;参数类型不正确算不算失败?你传一个user data,c里按数字来取,这也算失败。
这么简单的API还需要多纠结什么呢?然后我们浩浩荡荡地写了上百个接口,什么tolua_tostring/tolua_tonumber的使用少说也有500了吧?
然后有一天,服务器宕机了,空指针:
跟踪后发现,脚本里传入的是nil,这里的name取出来是NULL,而不是”“(的地址)。然后吐槽了一下这个API,辛苦地修改了所有类似代码,增加对空指针的判断。我没有多想。
故事继续,有一天服务器虽然没宕机,但功能不正常了:
这个意思是,这个函数的参数1默认是2*PI,什么是默认?lua里某函数参数不传,或传nil就是使用默认。因为不传的话,这个实参本身就是nil。但,tolua_tonumber的行为不是这样的,它的实现真是偷懒:
意思是,只有当你不传的时候,它才返回默认值,否则就交给lua的API来管,而lua这些API是不支持应用层的默认参数的,对于lua_tonumber错误时就返回0,lua_tostring错误时就返回NULL。
这种其行为和其带来的common sense不一致的API设计,实在让人蛋疼。什么是common sense呢?就像一个UI库里的按钮,我们都知道有click事件,hover事件,UI库的文档甚至都不需要解释什么是click什么是hover,因为大家看到这个东西,就有了共识,无需废话,这就是common sense。就像tolua的这些API,非常普通,大家一看都期待在意外情况下你能返回def值。但它竟然不是。实在不行,你可以模仿lua的check系列函数的实现嘛:
即,根本不用去检查栈问题,直接在lua_tonumber之后再做包装检查。何况,lua需要你去检查栈吗?当你访问了栈外的元素时,lua会自动返回一个全局常量luaO_nilobject:
另,程序悲剧也来源于臆想。