javascript 属于弱类型语言,参数的类型错误只能在运行期发现。当你须要 expose “很是健壮”的接口给外部,或者在调试较大项目的时候,你可能会怀念强类型语言的类型约束,或者 assert
一类东西。javascript
正由于 js 没有类型约束,也没有 assert
这样的“契约型”断言工具,因此同一我的写出的 js 代码,健壮性经常是不稳定的,有时约束多,有时约束少,有时候返回 null,有时候抛异常,而且约束代码也经常不统一放在函数入口处。java
本文尝试编写一种参数检查工具,期待能缓解相似问题。git
假设,咱们须要给全部接口统一添加稳定的约束,以及约束破坏后统一的反馈行为(好比崩溃),除了语言原生支持(据说 Eiffel 有这个能力,有兴趣的能够 google 下),最直接的方法就是设计一个相似 assert
的参数检查函数 check
,在每一个函数入口处调用 check
检查参数,若是检查失败则执行既定的失败反馈。github
若是全部的函数都这样编写,就能够保证全部函数严格执行约束,约束破坏后马上中止运行,并打印相应的信息。正则表达式
咱们很容易大体设想一个 check 接口的模样——编程
check.setCheckFailedCallback(function (e) {}); function test(a) { check(a).检查1(条件1).检查2(条件2)…… }
有几个细节须要讨论一下:segmentfault
上面的代码使用了链式调用,链式调用的必要性是很显然的——咱们须要一种组合检查步骤的方式。为了实现链式调用,check
返回的是一个特殊的包装对象 Checker
。ide
当参数 a
经过全部检查后,代码向下执行。若是有一个检查没有经过,此时须要执行一个反馈。因为外层代码可能存在 try
块,因此这里抛异常是不可靠的,或者说咱们要想一个办法抛出一个“不可 catch”的异常。这里采用的最简单的办法,上层设置回调函数 checkFailedCallback
,检查失败后自行处理结果,同时抛出一个异常。函数
check(a)
这种写法,其实是作不到的。js 里没有宏,因此没有办法接受一个变量同时拿到变量的名称。若是要打印出检查失败的参数名,须要写成 check(a, 'a')
。这种写法有点累赘,可能有更好的方案,我还在思考。工具
刚才说到链式调用能够用来组合检查步骤,可是只有一种组合方式显然是不行的。由于检查步骤之间的关系可能有三种:与、或、非。咱们要想办法使用同一的规则把三种关系表达清楚。
具体就不解释了,分享一下个人规则:
链式调用实现“与”:
// a 是 number 型,而且大于 1 小于 3 check(a, 'a').is('number').gt(1).lt(3);
参数表实现“或”:
// a 是 number 型,而且位于 [0, 1) || (1, 2] 区间上 check(a, 'a').is('number').within('[0, 1)', '(1, 2]');
注:因为参数表实现“或”,因此这里“或”的优先级永远比“与”高,若是须要“与”比“或”高,则须要一点技巧,具体见我这篇文章。
not 属性实现“非”:
// a 是字符串而且不符合正则表达式 /^[\w][\w\d]+$/ check(a, 'a').is('string').not.match(/^[\w][\w\d]+$/); // a 是字符串而且不符合正则表达式 /^[\w][\w\d]+$/, 而且长度等于 10 check(a, 'a').is('string').not.match(/^[\w][\w\d]+$/).length().eq(10);
注:
not 是一个特殊属性,会返回一个特殊对象 NotChecker
,这个对象使用 try
执行原对象的检查方法,catch
到异常则认为检查经过。而且 NotChecker
的检查方法返回的是原对象而不是本身,因此 not.match
以后链接 length
时,已经再也不 not
的做用范围。
因为德摩根定律的存在,not 后的参数表实际上在表达"与"的关系,好比:
check(a, 'a').not.is('string', 'number').
表示的是参数 a 既不为 string 也不为 number。
另外,为了方便使用,还须要实现一些另外的接口,好比:
// a 包含属性 foo,大于 1 小于 3; 同时包含属性 bar, 大于 2 小于 4 check(a, 'a').has('foo').gt(1).lt(3).owner.has('bar').gt(2).lt(4);
注:
上面的代码中,has
是一个特殊方法,它检验参数中是否包含指定的属性(own property),若是包含,就返回一个包装该属性的 Checker,不然抛检查失败的异常。
owner
是一个特殊属性,它返回包装上一层对象的 Checker 对象。因此咱们能够在调用 has
检查属性以后,调用 owner
“跳回去”继续检查上层对象。
为了检验上面的想法,我实现了一个 js 库 param-check,代码位于:
https://github.com/yusangeng/param-check
由于只是一个语言切换是产生的 idea,因此目前这个库还不完善,实际能有多大意义还很差说,对性能和编程范式的影响还须要评估。