DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. JavaScript
  4. Guarding Functions in JavaScript

Guarding Functions in JavaScript

Having the ability to detect and report errors early on in the lifecycle of an error is key to debugging, especially in JavaScript. Read on for some code and how to make this happen easily.

Dmitry Sheiko user avatar by
Dmitry Sheiko
·
Oct. 10, 16 · Opinion
Like (4)
Save
Tweet
Share
6.96K Views

Join the DZone community and get the full member experience.

Join For Free

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 lose 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/endpoint validation like other languages. For an instance PHP 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's exactly what byContract does. Here what 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 afterward.

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( "john.snow@got.com", "email" ); // ok
byContract( "bla-bla", "email" ); // Exception!

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

byContract.is.email = validator.isEmail;

Validation logic belongs 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.

JavaScript

Published at DZone with permission of Dmitry Sheiko, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Use AWS Controllers for Kubernetes To Deploy a Serverless Data Processing Solution With SQS, Lambda, and DynamoDB
  • Building the Next-Generation Data Lakehouse: 10X Performance
  • Best Navicat Alternative for Windows
  • File Uploads for the Web (2): Upload Files With JavaScript

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: