As developers, we spend a lot of time debugging, especially in discovering the source of problems. Development tools guide us in tracing call stacks, but the tracing process is still quite time-consuming, especially when encountering cascading asynchronous calls. This problem was discovered a long time ago.
Suppose we have a function that searches for elements containing a specified string from different document structures. We use the following call, which looks legitimate:
grep( "substring", tree );
But we don’t get the expected results. According to past experience, we will spend some time checking the given tree document structure, which may take a long time. Then we will probably do other checks, but in the end, we will find from the function code that the order of the parameters passed in is reversed. From this point of view, as long as we pay attention to the parameters of the function, the above error will not occur.
function grep( tree, substring ){ if ( !( tree instanceof Tree ) ) { throw TypeError( "Invalid tree parameter" ); } if ( typeof substring !== "string" ) { throw TypeError( "Invalid substring parameter" ); } //... }
This verification method is part of the Design by Contract approach. It lists the pre- and post-conditions that need to be verified in the software components. In the above example, we have to test that the function input parameters conform to the specified format (compare the first parameter to the tree document type and the second parameter to the string type) and we recommend checking whether the function output type is a string.
However, Javascript so far does not have the built-in function for verification at the entry and end of functions like other languages. For an example, PHP language has type hints:
<?php function grep( Tree $tree, string $substring ): string {}
TypeScript has strict typing:
function grep( tree: Tree, substring: string ): string {}
Additionally, it supports advanced types (union types, optional types, intersection types, generics, etc. etc.):
function normalize( numberLike: number | string, modifier?: boolean ): string {}
According to the features proposed in the ES specification, there will be a feature called Guards in the future, which recommends using the following syntax:
function grep( tree:: Tree, substring:: String ):: String {}
So far in Javascript, we must Use external libraries or convertible compilers to solve this problem. However, fewer resources are available. The oldest library is Cerny.js . It's similar to DbC (Database Computer), powerful and flexible:
var NewMath = {}; (function() { var check = CERNY.check; var pre = CERNY.pre; var method = CERNY.method; // The new pision function pide(a,b) { return a / b; } method(NewMath, "pide", pide); // The precondition for a pision pre(pide, function(a,b) { check(b !== 0, "b may not be 0"); }); })();
But to me it reads complex. I prefer to use a concise and clean way to check preconditions/postconditions. The syntax provided by Contractual meets my requirements:
function pide ( a, b ) { pre: typeof a === "number"; typeof b === "number"; b !== 0, "May not pide by zero"; main: return a / b; post: __result < a; } alert(pide(10, 0));
Except it is not Javascript, it looks very good. If you need to use it, you must use Contractual or Babel Contracts to compile the source code into Javascript. I'm not against cross-language compilers, but if I had to choose, I'd rather use TypeScript.
But back to Javascript, I wonder if you have noticed that in addition to related libraries and frameworks, we have been using JSDoc to describe the format comparison of function entry and return when annotating functions and classes. It would be nice if documentation comments could be used to verify the format. As you understand, it depends on the compiler. However, we can use libraries that rely on Jascript document expressions. Fortunately, byContract is such a library. The syntax of byContract looks like this:
/** * @param {number|string} sum * @param {Object.<string, string>} dictionary * @param {function} transformer * @returns {HTMLElement} */ function makeTotalElement( sum, dictionary, transformer ) { // Test if the contract is respected at entry point byContract( arguments, [ "number|string", "Object.<string, string>", "function" ] ); // .. var res = document.createElement( "p" ); // .. // Test if the contract is respected at exit point return byContract( res, "HTMLElement" ); } // Test it var el1 = makeTotalElement( 100, { foo: "foo" }, function(){}); // ok var el2 = makeTotalElement( 100, { foo: 100 }, function(){}); // exception
As you can see, we can copy/paste the specified type from the documentation comment into byContract and compare, it’s that simple. Below we examine the following more closely. byContract can be accessed as a UMD module (AMD or CommonJS) or a global variable. We can pass the value/Javascript document expression as a pair of parameters to byContract
byContract( value, "JSDOC-EXPRESSION" );
or the value list corresponding to the document expression list as a pair of parameters:
byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );
byContract will detect the incoming If the value is inconsistent with the corresponding JSDoc expression format, a byContract.Exception exception with information like `The passed-in value violates the type NaN` will be thrown.
在最简单的案例中,byContract用来验证如 `array`, `string`, `undefined`, `boolean`, `function`, `nan`, `null`, `number`, `object`, `regexp`之类的 原型类型:
byContract( true, "boolean" );
当我们需要允许输入值在一个指定类型列表中的时候,可以使用 type union 。
byContract( 100, "string|number|boolean" );
一个函数可以有必填的参数,也可以有可选参数。默认情况下,参数在和原型类型做对比的时候是必填的。但是用'='修饰符我们就可以设置成可选类型。所以 byContract 处理如 `number=` 这样的表达式时候,会转为 `number|undefined`
function foo( bar, baz ) { byContract( arguments, [ "number=", "string=" ] ); }
下面是Js文档中 nullable/non-nullable types (可空/不可空类型):
byContract( 42, "?number" ); // a number or null. byContract( 42, "!number" ); // a number, but never null.
当然,我们可以用接口来做比较。这样我们就可以引用作用域范围内任何可用的对象,包括Javascript内置接口:
var instance = new Date(); byContract( instance, "Date" ); byContract( view, "Backbone.NativeView" ); byContract( e, "Event" );
对于数组和对象,我们可以有选择性地验证其内容。比如可以验证所有数组的值必须是数字或者所有的对象的键和值是字符串类型:
byContract( [ 1, 1 ], "Array.<number>" ); byContract( { foo: "foo", bar: "bar" }, "Object.<string, string>" );
以上的验证对线性数据结构有用,其他情况下就不起作用了。所以同样的,我们可以创建一个 type definition (类型定义)来描述对象的内容(参考byContract类型定义)然后在后面作为一个类型引用它即可。
byContract.typedef( "Hero", { hasSuperhumanStrength: "boolean", hasWaterbreathing: "boolean" }); var superman = { hasSuperhumanStrength: true, hasWaterbreathing: false }; byContract( superman, "Hero" );
这个示例定义了一个'Hero'类型来表示一个对象/命名空间,必须有boolean类型的 `hasSuperhumanStrength`和`hasWaterbreathing` 属性。
所有的方法都通过类型验证传入的值,但是不变的量(常量)呢?我们可以用一个自定义类型来包装类型约束。比如说检测字符串是不是一个邮件地址类型,我们可以增加这样的验证:
byContract.is.email = function( val ){ var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)| (".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test( val ); } byContract( "john.snow@got.com", "email" ); // ok byContract( "bla-bla", "email" ); // Exception!
事实上,你很可能不要用事件来写验证函数,而是用外部库(类似 validator )代替:
byContract.is.email = validator.isEmail;
验证逻辑取决于开发环境。使用 byContract, 我们可以用全局触发器来禁用验证逻辑 :
if ( env !== "dev" ) { byContract.isEnabled = false; }
byContract 是一个很小的验证插件(压缩文件大约1KB大小) ,你可以在你的Javascript代码中使用它从而得到对比编程设计模式的好处。
以上就是守护 Javascript 中的函数参数的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!