javascript 参数检验(一):实现一个方便的参数检验工具

综述

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 返回的是一个特殊的包装对象 Checkeride

  • 当参数 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);

注:

  1. not 是一个特殊属性,会返回一个特殊对象 NotChecker,这个对象使用 try 执行原对象的检查方法,catch 到异常则认为检查经过。而且 NotChecker 的检查方法返回的是原对象而不是本身,因此 not.match 以后链接 length 时,已经再也不 not 的做用范围。

  2. 因为德摩根定律的存在,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);

注:

  1. 上面的代码中,has 是一个特殊方法,它检验参数中是否包含指定的属性(own property),若是包含,就返回一个包装该属性的 Checker,不然抛检查失败的异常。

  2. owner 是一个特殊属性,它返回包装上一层对象的 Checker 对象。因此咱们能够在调用 has 检查属性以后,调用 owner“跳回去”继续检查上层对象。

代码

为了检验上面的想法,我实现了一个 js 库 param-check,代码位于:
https://github.com/yusangeng/param-check

由于只是一个语言切换是产生的 idea,因此目前这个库还不完善,实际能有多大意义还很差说,对性能和编程范式的影响还须要评估。

相关文章
相关标签/搜索