未加星标

Guarding functions in JavaScript

字体大小 | |
[系统(linux) 所属分类 系统(linux) | 发布者 店小二04 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

As developers we spend a lot of our time on debugging and particularly on spotting the source of a problem. DevTools guides us though the call stack, but the tracing process can be still pretty time consuming, especially on a cascade of asynchronous calls. The remedy here is early problem reporting.

Let's say we have a function to search trough a multidimensional structure for the elements containing a given string. We make a call that looks like legit:

grep( "substring", tree );

Yet we don't get the expected result. We would spend some time on examining the given tree structure and it can be quite a big one. Then we would probably do other checks, but eventually we would find out from the code of the function that it expects the arguments in the opposite order. Thus if we had just guarded the function parameters, we would not lost all of this time:

function grep( tree, substring ){ if ( !( tree instanceof Tree ) ) { throw TypeError( "Invalid tree parameter" ); } if ( typeof substring !== "string" ) { throw TypeError( "Invalid substring parameter" ); } //... }

This kind of validation is a part of Design by Contract approach . It states for validation of preconditions and postconditions within a software component. In our case we have to test our function input against a specified contract ( tree is an instance of Tree and substring is a string) and advisable we check the output to be a string.

Yeah, javascript doesn't have currently built-in facilities for entry/end point validation like other languages. For an instancephp has type hinting:

<?php function grep( Tree $tree, string $substring ): string {}

TypeScript has strict types:

function grep( tree: Tree, substring: string ): string {}

in addition it supports also advanced types (union, optional, intersection, generics and others):

function normalize( numberLike: number | string, modifier?: boolean ): string {}

Among the features proposed for ES.Next there is one called Guards that suggests the following syntax:

function grep( tree:: Tree, substring:: String ):: String {}

Nowadays in JavaScript we have to cope with external libraries or transpilers. However just a few can be found. One of the oldest libraries is Cerny.js . It is very much of DbC, powerful and flexible:

var NewMath = {}; (function() { var check = CERNY.check; var pre = CERNY.pre; var method = CERNY.method; // The new division function divide(a,b) { return a / b; } method(NewMath, "divide", divide); // The precondition for a division pre(divide, function(a,b) { check(b !== 0, "b may not be 0"); }); })();

But as for me, it's too complex to read. I would prefer something concise and clean just to test pre-/postcoditions. The syntax provided by Contractual is very much of what I mean:

function divide ( a, b ) { pre: typeof a === "number"; typeof b === "number"; b !== 0, "May not divide by zero"; main: return a / b; post: __result < a; } alert(divide(10, 0));

Everything looks fine except it's no JavaScript. One has to compile the sources to JavaScript with Contractual or Babel Contracts . I have nothing against transpilers, but if to use one, I would rather go with TypeScript.

But coming back to JavaScript, have you every realized that regardless of libraries and frameworks we keep already declaring entry/exit point contracts when commenting functions and classes with JSDoc . It would be just perfect if doc comments were used for validation. As you understand, it cannot be done without compilation. However we can use a library that relies on JSDoc expressions. Fortunately that exactly what byContract does. Here how the syntax look like:

/** * @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( "div" ); // .. // 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 see we can copy/paste types from the doc comment to byContract and that makes a contract, that simple. Let's examine it more closely. byContract can be accessed as a UMD module (both AMD/CommonJS-compliant) or as a global variable. We can pass to it either value/JSDoc expression pair

byContract( value, "JSDOC-EXPRESSION" );

or list of values against a list of expressions:

byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );

byContract tests the values and if the associated contract (as JSDoc expression) violated it throws byContract.Exception (which is an instance of TypeError) with a message like `Value violates the contract NaN`.

In the simplest case the contract is set to validate against a primitive type like `array`, `string`, `undefined`, `boolean`, `function`, `nan`, `null`, `number`, `object`, `regexp`:

byContract( true, "boolean" );

When we need to allow value to be one of a list of specified types we can use type union

byContract( 100, "string|number|boolean" );

A function can have mandatory as well as optional parameters. By default a parameter provided with a primitive type in the contract is considered mandatory. But with '=' modifier we can set it as optional. So byContract that treats e.g. `number=` like `number|undefined`

function foo( bar, baz ) { byContract( arguments, [ "number=", "string=" ] ); }

Following JSDoc nullable/non-nullable types also supported

byContract( 42, "?number" ); // a number or null. byContract( 42, "!number" ); // a number, but never null.

Of course we can use interfaces for a contract. Thus we can refer any available in the scope objects, including JavaScript built-in interfaces:

var instance = new Date(); byContract( instance, "Date" ); byContract( view, "Backbone.NativeView" ); byContract( e, "Event" );

For arrays and objects we can optionally validate the content. So we can state that e.g. all of array values must be numbers or all the keys and values of an object are strings:

byContract( [ 1, 1 ], "Array.<number>" ); byContract( { foo: "foo", bar: "bar" }, "Object.<string, string>" );

It may serve well for linear structures, but useless otherwise. So alternatively we can create a type definition describing the content of an object (see byContract.typedef ) and refer it as a type afterwards

byContract.typedef( "Hero", { hasSuperhumanStrength: "boolean", hasWaterbreathing: "boolean" }); var superman = { hasSuperhumanStrength: true, hasWaterbreathing: false }; byContract( superman, "Hero" );

This example defines a type `Hero` that represents an object/namespace required to have properties `hasSuperhumanStrength` and `hasWaterbreathing` both of boolean type.

All the described methods validate values by types, but what about invariants? We can wrap the constraint in a custom type. Let's say for testing string is an email address we can add a validator like that:

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( "[email protected]", "email" ); // ok byContract( "bla-bla", "email" ); // Exception!

Actually you probably don't need event to write a validaton function, but use an external library (e.g. validator ) instead:

byContract.is.email = validator.isEmail;

Validation logic belong to the development environment. With byContract we can disable the validation globally with a trigger:

if ( env !== "dev" ) { byContract.isEnabled = false; }

byContract is a small validation library (~1KB gzip) that allows you to benefit from Design by Contract programming in your JavaScript code.

本文系统(linux)相关术语:linux系统 鸟哥的linux私房菜 linux命令大全 linux操作系统

主题: JavaScriptJavaHTMLPHPAMD
分页:12
转载请注明
本文标题:Guarding functions in JavaScript
本站链接:http://www.codesec.net/view/480852.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(linux) | 评论(0) | 阅读(36)