Coding standards

Best practices for building product modules

Overview

At Root, we highly value standardisation in product module development. Software is seldom maintained by the original author. Coding standards and conventions enable future developers and engineers to jump into builds and know what to expect. It reduces the effort of maintenance and improves the efficiency of feature addition or bug fixes.

We want product module software to be well packaged, scalable, clean, and easily readable for developers and engineers — irrespective of who the original authors were. This guide will try to accomplish this by serving as the centralised repository of the standards for product module builds.

Visual Studio Code setup

Root’s code editor of choice is Visual Studio Code, however there are lots of other great tools like Sublime Text and Atom. For the purpose of this guide, the focus will be to set up Visual Studio Code, however all of this can be replicated on other editors.

Extensions

There are a lot of awesome extensions that can be installed to VS Code to supercharge your workflow. The following extensions are recommended to successfully develop on the Root platform and workbench.

ExtensionWhy use it?What is it?
PrettierCode formattingPrettier is an opinionated code formatter. It enforces a consistent style by parsing your code and reprinting it with its own rules that take the maximum line length into account, wrapping code when necessary. To automatically format your code base when saving, enable the Editor: Format On Save setting on VS Code.
npmNode package managerThis extension supports running npm scripts defined in the package.json file and validating the installed modules against the dependencies defined in the package.json.
GitLensEasily collaborate on GitSupercharge the Git capabilities built into Visual Studio Code — Visualize code authorship at a glance via Git blame annotations and code lens, seamlessly navigate and explore Git repositories, gain valuable insights via powerful comparison commands, and so much more.
YAML & Red Hat CommonsAPI documentationProvides comprehensive YAML Language support to Visual Studio Code, via the yaml-language-server, with built-in Kubernetes syntax support.

Code base structure

We strive to make all our product modules follow a set file and folder structure to make it easy for developers to dive into. Here are some basic guidelines to follow with setting up the file structure and how we do naming conventions.

File structure

Once a product module is pulled to your local machine the code base will be in the correct structure, as described in the file and folder structure guide. In this section we would like to specifically focus on the code folder. By default it will only contain the main.js file. It is highly encouraged to use the product module code files to organise your code into logical sections. The below is a good starter template.

|- product_module_key
|  |- code/
|    |- main.js
|    |- env.js
|    |- 00-helper-functions.js
|    |- 01-ratings.js
|    |- 02-quote-hook.js
|    |- 03-application-hook.js
|    |- 04-policy-hook.js
|    |- 05-alteration-hooks.js
|    |- 06-scheduled-functions.js
|    |- 07-reactivation-flow.js
|    |- 08-lifecycle-hooks.js
|    |- unit-tests/
|      |- 00-policy-issue-flow-data.js
|      |- 01-policy-issue-flow-tests.js
|      |- 02-alteration-hooks-data.js
|      |- 03-alteration-hooks-tests.js

The corresponding configuration in the .root-config.json can be seen below.

{  
"codeFileOrder": [
    "main.js",
    "env.js",
    "00-helper-functions.js",
    "01-ratings.js",
    "02-quote-hook.js",
    "03-application-hook.js",
    "04-policy-hook.js",
    "05-alteration-hooks.js",
    "06-scheduled-functions.js",
    "07-reactivation-flow.js",
    "08-lifecycle-hooks.js",
  ],
}

This structure is great to follow and mimics the order in which the Root platform executes hooks. The customisation mostly lies in more granularity. For instance, sometimes it is beneficial to split files further into separate lifecycle hooks, e.g. 09-after-payment-success.js.

Naming conventions

As can be seen above, there are only three rules on naming conventions in the code file:

  1. All file names start with a two digit number to organise them, more or less in the same order in which they will be executed on the Root platform.
  2. Words in the file name should be split with hyphens (-) and be very descriptive of what code lives there.
  3. Only use lower case for file names.

Libraries

As part of keeping things consistent across the platform, we use a common “toolbox” of libraries. These libraries are used often and would be good to familiarise yourself with.

LibraryWhat's it used for?Why this library?
Moment.jsWorking with dates/timesJavascript dates are a mess. Moment.js provides a much more reliable, easy-to-work-with API around Javascript dates. For more information please visit the dates section in the product module code guide.
moment-timezoneWorking with timezonesThe moment-timezone module allows you to parse and display dates in any timezone. For more information please visit the dates section in the product module code guide.
Mocha and ChaiWriting/running testsWell established libraries from writing and running tests.
JoiData validationThe most powerful schema description language
and data validator for JavaScript. For a list of the custom Root extensions please visit the validation section in the product module code guide.
Node FetchAPI requestsA light-weight, easy-to-use module that enables the fetching of resources making it an ideal choice for server-side HTTP requests.
HandlebarsTemplatingIt uses a template and an input object to generate HTML or other text formats. Handlebars templates look like regular text with embedded Handlebars expressions.
JavaScript ES6Typing the codebaseJavaScript is a very powerful programming language, and ES6 made it even better.

JavaScript conventions

Code formatting

The .prettierrc config file should be added to the code base and contain the following:

{
 "trailingComma": "all",
 "tabWidth": 2,
 "semi": true,
 "singleQuote": true,
 "jsxSingleQuote": true,
 "printWidth": 80
}

To ensure prettier works, the following settings need to be enabled in VS code:

  • "editor.formatOnSave": true
  • "prettier.enable": true

You should be able to see the output below if prettier is working as expected

2182

Expected Prettier output in VS Code

Indentation & line length

The unit of indentation is two (2) spaces. Use of tabs should be avoided because there still is not a standard for the placement of tabstops. This is also part of the prettier config file.

We wrap our code after 80 lines. This removes the risk of a statement not fitting on a single line, and enhances the readability of code.

Comments

Be generous with comments. It is useful to leave information that will be read at a later time by people (possibly your future self) who will need to understand what you have done and why. The comments should be well-written and clear, just like the code they are annotating.

It is important that comments be kept up-to-date. Erroneous comments can make programs even harder to read and understand. Make comments meaningful. Focus on what is not immediately visible. Don't waste the reader's time with comments like below.

const i = 0; // Set i to zero.

Use line comments, not block comments. The comments should start at the left margin. The exception is for functions. All functions parameters and usage should be documented. See below an example of good commenting on the getQuote hook.

/**
 * Generates and returns a quote package using the input data (rating factors)
 * @param {object} data The input data required to generate a quote
 * @return {object} Quote package
 */
const getQuote = (data) => {
  return new QuotePackage({
    // Below are standard fields for all products
    package_name: '<PACKAGE NAME>', 
    sum_assured: sumAssured, 
    base_premium: premium, 
    suggested_premium: premium, 
    billing_frequency: 'monthly', 
    module: {
      // Save any data, calculations, or results here for future re-use.
      ...data,
    },
    input_data: { ...data },
  });
};

Functions

There are multiple ways that functions can be declared in Javascript. To help with consistency, we prefer to declare function expressions in order to avoid confusion with hoisting. Therefore, functions should be declared before they are initialised.

Taking this a step further, arrow functions is a great way to declare functions expressions and comes with a few advantages that help writing code in product modules. They are more appealing to the eye and easier to write. Another notable benefit of arrow functions is that they do not define a scope, instead being within the parent scope. This prevents many of the issues that can occur when using the this keyword.

Functions should, as far as possible, stick to doing one thing. It is easy to get carried away with adding extra stuff to function while you are at it and the best way to find out whether a function is doing too much is by looking at its name. The name should tell what the function does and anything unrelated should go.

Lastly, the sequence of functions should make sense to the reader. If an inner function is necessary, declare it in the parent function and not outside. This makes it much easier to debug code.

function parent(c, d) {
  var e = c * d;
  
  function inner(a, b) {	// declare inner functions inside parent
      return (e * a) + b;
  }
  
  return inner(0, 1);
}

Variable naming

To keep it simple, we declare our variables and functions using camelCase, always starting with a letter. It is a good practise to always declare variables before use, even though this isn't strictly required by JavaScript.

When declaring objects we use snake_case for property names, as is common practise. Below follows some examples of variables naming.

Use of global variables should be minimised. Implied global variables should never be used. Be as prescriptive as possible when naming a variable for future readers of the code.

// Variables in product modules
const validMaritalStatus = ['married', 'single', 'divorced', 'widowed'];
const { policy_number: policyNumber } = policy;
const baseRiskRate = 0.015; 

// Objects in product modules
const body = {
	policy_number: generatePolicyNumber(),
	sum_assured: 10000000,
	policy_status: 'active',
};

General conventions

  • Always put spaces around operators ( = + - * / ), and after commas.
let x = y + z;
const myArray = ["One", "Two", "Three"];
  • It is almost always better to use the === and !== operators. The == and != operators do type coercion. In particular, do not use == to compare against falsy values, and better yet, avoid truthy/falsy where possible.
  • In JavaScript blocks do not have scope. Only functions have scope. Do not use blocks except as required by the compound statements.
  • Use {} instead of new Object(). Use [] instead of new Array().
  • The use of var is discouraged. Rather use const and let for declarations.
  • Keep ternary operators simple. Worst case scenario you have two nested ternaries. Anything longer should be an if or switch statement for readability and easier debugging reasons. That being said, we prefer ternary over logical || and && checks. They are much more readable, as can be seen below.
// let's assume premium is R50.00 or 5000 cents
const { monthly_premium: monthlyPremium } = policy;

// avoid this
const badMessage = (monthlyPremium > 8000 && `Premium: ${monthlyPremium}`) || 'Premium less than R80.00';
                    
// prefer this
const betterMessage = monthlyPremium > 8000 ? `Premium: ${monthlyPremium}` : 'Premium less than R80.00';