摘要:本文尝试编写一种参数检查工具,期待能缓解类似问题。为了实现链式调用,返回的是一个特殊的包装对象。如果要打印出检查失败的参数名,需要写成。由于德摩根定律的存在,后的参数表实际上在表达与的关系,比如表示的是参数既不为也不为。
综述
javascript 属于弱类型语言,参数的类型错误只能在运行期发现。当你需要 expose “非常健壮”的接口给外部,或者在调试较大项目的时候,你可能会怀念强类型语言的类型约束,或者 assert 一类东西。
正因为 js 没有类型约束,也没有 assert 这样的“契约型”断言工具,所以同一个人写出的 js 代码,健壮性常常是不稳定的,有时约束多,有时约束少,有时候返回 null,有时候抛异常,并且约束代码也常常不统一放在函数入口处。
本文尝试编写一种参数检查工具,期待能缓解类似问题。
参数检查假设,我们需要给所有接口统一添加稳定的约束,以及约束破坏后统一的反馈行为(比如崩溃),除了语言原生支持(听说 Eiffel 有这个能力,有兴趣的可以 google 下),最直接的方法就是设计一个类似 assert 的参数检查函数 check,在每个函数入口处调用 check 检查参数,如果检查失败则执行既定的失败反馈。
如果所有的函数都这样编写,就可以保证所有函数严格执行约束,约束破坏后立刻停止运行,并打印相应的信息。
接口我们很容易大致设想一个 check 接口的模样——
check.setCheckFailedCallback(function (e) {}); function test(a) { check(a).检查1(条件1).检查2(条件2)…… }
有几个细节需要讨论一下:
上面的代码使用了链式调用,链式调用的必要性是很显然的——我们需要一种组合检查步骤的方式。为了实现链式调用,check 返回的是一个特殊的包装对象 Checker。
当参数 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][wd]+$/ check(a, "a").is("string").not.match(/^[w][wd]+$/); // a 是字符串并且不符合正则表达式 /^[w][wd]+$/, 并且长度等于 10 check(a, "a").is("string").not.match(/^[w][wd]+$/).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,所以目前这个库还不完善,实际能有多大意义还不好说,对性能和编程范式的影响还需要评估。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/87798.html
摘要:上一个版本的问题接这篇文章,聊聊参数检查工具的完善。最终实现了这样的效果检查是否在区间与的交集内检查是否在区间与的并集内检查是否是数组并且长度大于检查是否不是之间的偶数即 上一个版本的问题 接这篇文章,聊聊参数检查工具 param-check 的完善。 按照之前的接口设计,链式调用表示与,参数表表示或,自然产生了一个问题——如果我要表达(A与B)或(C与D)这样的逻辑组合应该怎么办? ...
摘要:介绍这是一篇短文,旨在展示多种在中安全地访问深层嵌套值的方式。所以每次我们想要访问深度嵌套的数据时,都必须明确地进行手动检查。我们还触及了,可以更新深度嵌套数据而不会改变对象。 介绍 这是一篇短文,旨在展示多种在javascript中安全地访问深层嵌套值的方式。下面的例子通过不同的方式来解决这一问题。 开始之前,让我们看下实际遇到这种状况时.. 假设有一个props对象(如下),当我们...
摘要:检验是用来检验序列是否平稳的方式一般来说是时间序列中的一种检验方法中可使用现成的工具来实现检验最参数和返回结果的理解还不够深刻后头再把参数和返回结果都加上参数项序列,一维数组差分次数只有常量,有常量项和趋势项,有常量项线性和二次趋势 adf检验是用来检验序列是否平稳的方式一般来说是时间序列中的一种检验方法python中可使用现成的工具statsmodels来实现adf检验 import...
阅读 4001·2021-11-22 13:53
阅读 1628·2021-09-23 11:52
阅读 2419·2021-09-06 15:02
阅读 877·2019-08-30 15:54
阅读 843·2019-08-30 14:15
阅读 2364·2019-08-29 18:39
阅读 599·2019-08-29 16:07
阅读 399·2019-08-29 13:13