Als Entwickler verbringen wir viel Zeit mit dem Debuggen, insbesondere damit, die Ursache von Problemen zu ermitteln. Entwicklungstools helfen uns bei der Verfolgung von Aufrufstapeln, aber der Ablaufverfolgungsprozess ist immer noch recht zeitaufwändig, insbesondere wenn es um kaskadierende asynchrone Aufrufe geht. Dieses Problem wurde vor langer Zeit entdeckt.
Angenommen, wir haben eine Funktion, die nach Elementen sucht, die eine bestimmte Zeichenfolge aus verschiedenen Dokumentstrukturen enthalten. Wir verwenden den folgenden scheinbar legitimen Aufruf:
grep( "substring", tree );
Aber wir erzielen nicht die erwarteten Ergebnisse. Erfahrungsgemäß werden wir einige Zeit damit verbringen, die vorgegebene Baumstruktur des Dokuments zu überprüfen, was einige Zeit in Anspruch nehmen kann. Dann werden wir wahrscheinlich weitere Prüfungen durchführen, aber am Ende werden wir anhand des Funktionscodes feststellen, dass die Reihenfolge der übergebenen Parameter umgekehrt ist. Unter diesem Gesichtspunkt tritt der obige Fehler nicht auf, solange wir auf die Parameter der Funktion achten.
function grep( tree, substring ){ if ( !( tree instanceof Tree ) ) { throw TypeError( "Invalid tree parameter" ); } if ( typeof substring !== "string" ) { throw TypeError( "Invalid substring parameter" ); } //... }
Diese Verifizierungsmethode ist Teil des Design by Contract-Ansatzes. Es listet die Vor- und Nachbedingungen auf, die in den Softwarekomponenten überprüft werden müssen. Im obigen Beispiel müssen wir testen, ob die Funktionseingabeparameter dem angegebenen Format entsprechen (vergleichen Sie den ersten Parameter mit dem Baumdokumenttyp und den zweiten Parameter mit dem Zeichenfolgentyp) und wir empfehlen zu prüfen, ob der Funktionsausgabetyp eine Zeichenfolge ist .
Allerdings verfügt Javascript bisher nicht über die eingebaute Funktion, den Beginn und das Ende von Funktionen wie andere Sprachen zu überprüfen. Beispielsweise verfügt die PHP-Sprache über Typhinweise:
<?php function grep( Tree $tree, string $substring ): string {}
TypeScript verfügt über eine strikte Typisierung:
function grep( tree: Tree, substring: string ): string {}
Darüber hinaus unterstützt es erweiterte Typen (Vereinigungstypen, optionale Typen, Schnittmengentypen, Generika). usw.):
function normalize( numberLike: number | string, modifier?: boolean ): string {}
Gemäß den in der ES-Spezifikation vorgeschlagenen Funktionen wird es in Zukunft eine Funktion namens Guards geben, die die Verwendung der folgenden Syntax empfiehlt:
function grep( tree:: Tree, substring:: String ):: String {}
Bisher mussten wir in Javascript externe Bibliotheken oder konvertierbare Compiler verwenden, um dieses Problem zu lösen. Es stehen jedoch weniger Ressourcen zur Verfügung. Die älteste Bibliothek ist Cerny.js. Es ähnelt einem DbC (Datenbankcomputer), leistungsstark und flexibel:
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"); }); })();
Aber für mich sieht es komplex aus. Ich bevorzuge eine prägnante und saubere Methode zur Überprüfung der Vor-/Nachbedingungen. Die von Contractual bereitgestellte Syntax entspricht meinen Anforderungen:
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));
Abgesehen davon, dass es sich nicht um Javascript handelt, sieht es sehr gut aus. Wenn Sie es verwenden müssen, müssen Sie Contractual oder Babel Contracts verwenden, um den Quellcode in Javascript zu kompilieren. Ich bin nicht gegen sprachübergreifende Compiler, aber wenn ich mich entscheiden müsste, würde ich lieber TypeScript verwenden.
Aber zurück zu Javascript: Ich frage mich, ob Ihnen aufgefallen ist, dass wir zusätzlich zu verwandten Bibliotheken und Frameworks JSDoc verwendet haben, um den Formatvergleich von Funktionseingabe und -rückgabe beim Annotieren von Funktionen und Klassen zu beschreiben. Es wäre schön, wenn Dokumentationskommentare zur Überprüfung des Formats verwendet werden könnten. Wie Sie wissen, hängt es vom Compiler ab. Wir können jedoch Bibliotheken verwenden, die auf Jascript-Dokumentausdrücken basieren. Glücklicherweise ist byContract eine solche Bibliothek. Die Syntax von byContract sieht so aus:
/** * @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
Wie Sie sehen, können wir den angegebenen Typ aus dem Dokumentationskommentar in byContract kopieren/einfügen und vergleichen, so einfach ist das. Nachfolgend gehen wir näher auf Folgendes ein. Auf byContract kann als UMD-Modul (AMD oder CommonJS) oder als globale Variable zugegriffen werden. Wir können den Wert/Javascript-Dokumentausdruck als Parameterpaar an byContract
byContract( value, "JSDOC-EXPRESSION" );
oder die der Dokumentausdrucksliste entsprechende Werteliste als Parameterpaar übergeben:
byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );
byContract Der eingehende Wert wird erkannt, und wenn er nicht mit dem entsprechenden JSDoc-Ausdrucksformat übereinstimmt, wird eine byContract.Exception-Ausnahme mit Informationen wie „Der eingehende Wert verletzt den Typ NaN“ ausgelöst.
在最简单的案例中,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)!