개발자로서 우리는 디버깅, 특히 문제의 원인을 찾는 데 많은 시간을 보냅니다. 개발 도구는 호출 스택을 추적하는 데 도움을 주지만 추적 프로세스는 특히 계단식 비동기 호출이 발생할 때 여전히 시간이 많이 걸립니다. 이 문제는 오래 전에 발견되었습니다.
다양한 문서 구조에서 지정된 문자열을 포함하는 요소를 검색하는 함수가 있다고 가정해 보겠습니다. 우리는
grep( "substring", tree );
처럼 겉보기에 합법적인 호출을 사용하지만 예상한 결과를 얻지 못합니다. 과거의 경험에 따르면 주어진 트리 문서 구조를 확인하는 데 시간이 좀 걸릴 것이므로 시간이 오래 걸릴 수 있습니다. 그런 다음 아마도 다른 검사를 수행하겠지만 결국 함수 코드에서 전달된 매개변수의 순서가 반대라는 것을 알게 될 것입니다. 이러한 관점에서 함수의 매개변수에 주의를 기울이면 위의 오류는 발생하지 않습니다.
function grep( tree, substring ){ if ( !( tree instanceof Tree ) ) { throw TypeError( "Invalid tree parameter" ); } if ( typeof substring !== "string" ) { throw TypeError( "Invalid substring parameter" ); } //... }
이 검증 방법은 계약에 의한 설계 접근 방식의 일부입니다. 소프트웨어 구성 요소에서 확인해야 하는 사전 및 사후 조건이 나열되어 있습니다. 위의 예에서는 함수 입력 매개변수가 지정된 형식을 준수하는지 테스트해야 하며(첫 번째 매개변수를 트리 문서 유형과 비교하고 두 번째 매개변수를 문자열 유형과 비교) 함수 출력 유형이 문자열인지 확인하는 것이 좋습니다. .
그러나 현재까지의 Javascript에는 다른 언어와 마찬가지로 함수의 진입과 종료를 확인하는 기능이 내장되어 있지 않습니다. 예를 들어, PHP 언어에는 유형 힌트가 있습니다:
<?php function grep( Tree $tree, string $substring ): string {}
TypeScript에는 엄격한 유형 지정이 있습니다:
function grep( tree: Tree, substring: string ): string {}
또한 고급 유형(통합 유형, 선택적 유형, 교차 유형, 제네릭 등)을 지원합니다. .):
function normalize( numberLike: number | string, modifier?: boolean ): string {}
ES 사양에서 제안된 기능에 따르면 향후 Guards라는 기능이 포함될 예정이며, 다음 구문 사용을 권장합니다.
function grep( tree:: Tree, substring:: String ):: String {}
지금까지 Javascript에서는, 이 문제를 해결하려면 외부 라이브러리나 컨버터블 컴파일러를 사용해야 합니다. 그러나 사용할 수 있는 리소스는 더 적습니다. 가장 오래된 라이브러리는 Cerny.js 입니다. 강력하고 유연한 DbC(데이터베이스 컴퓨터)와 유사합니다.
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"); }); })();
하지만 제게는 복잡해 보입니다. 나는 전제조건/사후조건을 확인하기 위해 간결하고 깔끔한 방법을 사용하는 것을 선호합니다. Contractual에서 제공하는 구문은 내 요구 사항을 충족합니다.
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));
Javascript가 아닌 점을 제외하면 매우 좋아 보입니다. 사용해야 한다면 Contractual 또는 Babel Contracts를 사용하여 소스 코드를 Javascript로 컴파일해야 합니다. 나는 교차 언어 컴파일러에 반대하지는 않지만 선택해야 한다면 TypeScript를 사용하고 싶습니다.
/** * @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
보시다시피 문서 주석에서 지정된 유형을 byContract에 복사/붙여넣고 비교할 수 있습니다. 매우 간단합니다. 아래에서는 다음 내용을 더 자세히 살펴보겠습니다. byContract는 UMD 모듈(AMD 또는 CommonJS) 또는 전역 변수로 액세스할 수 있습니다. 값/자바스크립트 문서 표현식을 매개변수 쌍으로 byContract
byContract( value, "JSDOC-EXPRESSION" );
에 전달하거나 문서 표현식 목록에 해당하는 값 목록을 매개변수 쌍으로 전달할 수 있습니다.
byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );
byContract는 수신 값이 해당 JSDoc 표현식 형식과 일치하지 않는 경우 '전달된 값이 NaN 유형을 위반합니다'와 같은 정보가 포함된 byContract.Exception이 발생합니다.
在最简单的案例中,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中文网(www.php.cn)!