RidgeRun Developer Manual - Coding Styles - Javascript

From RidgeRun Developer Wiki





Previous: Coding Styles/Python Index Next: Development Tools





Javascript programming guide

Javascript is a language widely used for web development and back-end development. Currently, NodeJS exploits the flexibility of Javascript to implement server-side scripting and applications. The NodeJS implementation works as JIT (Just-in-time) compilation, meaning that the first execution will be slower than the following ones.

Javascript for front-end and for back-end have some differences, not that notorious from the syntax point of view, but for the module importing. Usually, for Front-end, EMAScipt6 (ES6) is widely accepted by the community and it's quite easy to find code style standards, such as Google one. However, some rules are not applicable for NodeJS because some mechanisms are preferred above others.

General syntax

Variable declaration

You can declare variables by using: const, var, let. The scope is different amongst those ways of declaration.

Keyword const let var
Global scope No No Yes
Function scope Yes Yes Yes
Block scope Yes Yes No
Reassignation capability
No Yes Yes

It's always a best practice to make the variables immutable if they are not going to be reassigned. Some cases:

/* Not immutable: you only access the data or manipulate the object as a such */
const values = [1,2,3,4,5];
const mappedValues= values.map(function(value) {
  return value * 2;
});
mappedValues[2] = 0; /* This is not an assignation but object manipulation */ 

/* Mutable: you need to reassign the value*/
let sum = 0;
values.forEach(function(element) {
  sum += element; /* Here, there is a reassignation*/
});

It's a best practice to prefer the variable declaration in const or let.

Now, it's important to know that, similar to Python, the variables work as some sort of sticky notes. The objects are shallow-copied.

Another note is that a variable initialisation without the const, var, let tags leads to global variable declaration.

Function declaration

The functions are declared in two possible ways:

1. Using the function tag: this is the old style but still valid.

function multiply(a, b) {
  if (!a || !b) {
    return null;
  }
  return a * b;
}

2. As arrow functions: new style and preferred for callbacks or nested functions

const myFunction= () => {
  console.log("My Function")
};

myFunction();

/* Another example */
let sum = 0;
const values = [1,2,3,4];
values.forEach((value) => {
  sum += value;
});

You can assign the function to variables with the other notation as well, since they behave like objects.

Array methods

Class declaration

You can use Classes in Javascript. There are some interesting features which are not well-documented and it's worthy to mention it over here to save you some time.

A basic class can be declared like this:

class MyClass {
  constructor(val1, val2) {
    this.property1 = val1;
    this.property2 = val2;
    this.property3 = null;
  }

  method1(val) {
    return this.property1 * val;
  }
};

Lines 3-5 declares the properties of the class, whereas 8 is a method declaration. You can use this to get a reference of the object itself.

You can privatise the access to some properties or protecting them by using a special property declaration. By default, all the properties declared in 3-5 are public.

class MyClass {
  #property1;
  #property2;
  #property3;
  constructor(val1, val2) {
    this.#property1 = val1;
    this.#property2 = val2;
    this.#property3 = null;
  }

  /* Accesible private member */
  get property2() {
    return this.#property2;
  }
  set property2(val) {
    if (!val) return;
    this.#property2 = val;
  }

  /* Read-only member */
  get property3() {
    return this.#property3;
  }
  set property3(val) {}
};

In Javascript, we can expose some properties through getters/setters like in Python. In the code, #property1 is fully private and it's inaccessible from outside. #property2 is fully accessible from outside, while #property3 is read-only.

While executing:

const myClass= MyClass(1, 2);

const a1 = myClass.property1;  // error
const a2 = myClass.#property1; // error

const b1 = myClass.property2;  // 2
myClass.property2 = 3;
const b2 = myClass.property2;  // 3
const b3 = myClass.#property2; // error

const c1 = myClass.property3;  // null
myClass.property3 = 3;
const c2 = myClass.property3;  // null
const c3 = myClass.#property3; // error

You can use inheritance as well:

class MyChildClass extends MyClass {
  constructor(val1, val2) {
    super(val1, val2);
  }

  // Override a method
  method1(val) {
    return super.method1 * val;
  }
};

When using constructors, you should call the super first. Besides, you can override methods like in line 7 and call the super methods like in 8.

Using templates

Do you remember how useful are the templates in C++? Well, here are needed in some cases. Some variables like events are not easily implementable. This means that, using them within a class can lead to issues. If you want, for example, to link an event listener within a class, what you can do is the following:

// Suppose that watcher is a module object
function MyTemplatedClass (event) {
  class MyClass {
    #property1;
    constructor() {
      if (watcher) watcher('create', this.onCreate);
    }

    /* This function is usually invoked as a callback, and `this` does not work in this context */
    onCreate(val) {
      event.emit('create', val);
    }
  };
  return MyClass;
}

// Suppose that my_event has been already declared and defined
const myClass= new MyTemplatedClass(my_event);

Line 6 detaches the function from the class, making this inaccessible. So, that's why we need a template, to define what will happen when the method onCreate is called by the callback triggered by watcher.

NodeJS specific rules

The most relevant rule for NodeJS is how the modules are imported. ES6 introduced the concept of import similar to Python, which is also used in Typescript. However, NodeJS already had an engine for modules based on CommonJS, which is optimised for NodeJS. That's why one approach is preferred on top of the other.

Importing a module

For importing a module, you can use require

const express = require('express');
const my_module = require('./my_module');

require will look for the module in the node_modules folders available in the system under some conditions: vicinity or default locations, similar to PATH or LD_LIBRARY_PATH, but for NPM.

Another important aspect is to identify the type of object that the module returns, to establish the naming condition accordingly.

For local files, you can use the example of line 2.

Exporting a module

For exporting a module, you can export any object or value.

const MY_CONST1 = 5;
const MY_CONST2 = 5;

module.exports = {
  const1: MY_CONST1,
  const2: MY_CONST2,
};
// When you import the module, you will get the dictionary. Respect the naming condition for properties of classes in this case.

Building system

Use NPM to handle the installation of the dependencies, the application startup and deployment. You can get more information in:

https://developer.mozilla.org/es/docs/Learn/Server-side/Express_Nodejs/development_environment

Creating a new project

For creating a new project with NPM, you can use:

npm init

An assistant will prompt and ask you some questions regarding the project. It will create a file called package.json

Then, create a file called index.js with a dummy application and add a rule to start the project, editing the file package.json. For example:

{
  "name": "test",
  "version": "0.1.0",
  "description": "Test project",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "RidgeRun",
  "license": "ISC"
}

In line 7, the rule is added. It can be called by using:

npm run start

You can use nodemon to daemonise the development process. It starts the application and restarts it when you edit the source code. You can add nodemon to your project by:

1. Installing nodemon:

npm install nodemon

2. Adding the rule:

{
  "name": "test",
  "version": "0.1.0",
  "description": "Test project",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "debug": "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "RidgeRun",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^2.0.6"
  }
}

3. You can launch the daemonised version with:

npm run debug

Note: when you install a dependency, a file called package-lock.json will be added. It's recommended to add it to the repo for locking the packages and avoiding breaking the code for updates and so on.

Testing suites

For developing unit tests and regression tests, please, consider Mocha for the task. If you need to test your API, please also consider chai.

1. Add a test script:

For implementing a pretty basic test with mocha, you can take the following file as an example:

const assert = require('assert');

function asynchronous_example(callback) {
    callback(0);
}

function synchronous_example() {
    return 0;
}

/* Suite - Module */
describe('Execution Modes', function() {
    /* Function unit test - setting done twice leads an error*/
    describe('#Asynchronous', function() {
        it('Should return 0 by the callback', function(done) {
            asynchronous_example((res) => {
                setImmediate(done);
            });
        });
    });
    /* Function unit test */
    describe('#Synchronous', function() {
        it('Should return 0 by function return', function() {
            assert.strictEqual(synchronous_example(), 0);
        });
    });
});

There are cases for asynchronous examples and synchronous functions.

All the test scripts must be stored into the folder ${PROJECT_DIR}/test/

2. Install mocha

npm install mocha

3. Add rule for testing:

{
  "name": "test",
  "version": "0.1.0",
  "description": "Test project",
  "main": "index.js",
  "scripts": {
    "test": "mocha --exit"
  },
  "author": "RidgeRun",
  "license": "ISC",
  "dependencies": {
    "mocha": "^8.2.1"
  }
}

4. Execute the tests:

npm run test

This command will invoke mocha, which looks for all the tests in the folder ${PROJECT_DIR}/text and will load all of them. Take into consideration that all the scripts will be loaded and share the same stack of variables.

Project documentation

For documenting Javascript code, you can use jsdoc. For a pretty website theme, please consider better-docs.

Code style

The widely accepted standard is Javascript Standard, adopted by NPM, GitHub, and others. You can integrate the verification with:

NPM: https://standardjs.com/index.html

And check the rules with: https://standardjs.com/rules.html

Naming convention

1. Classes or object constructors should be in UpperCamelCase 2. For variables and functions, use lowerCamelCase

Implementing the linter

1. Install standard js.

npm install standard --save-dev

This will install it as a development dependency locally. It's recommended to keep it locally to avoid crashes with other versions and so on.

2. Add the rule:

{
  "name": "test",
  "version": "0.1.0",
  "description": "Test project",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "debug": "nodemon index.js",
    "fix-indent": "standard --fix",
    "check-indent": "standard",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "RidgeRun",
  "license": "ISC",
  "dependencies": {
    "mocha": "^8.2.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.6",
    "standard": "^16.0.3"
  }
}

Lines 9 and 10 shows the new rules added. The fix-indent fixes the indentation applying the linter and check-indent just check the indentations, reporting the issues.

3. Apply the linter

# To observe the changes
npm run check-indent
# To fix
npm run fix-indent


Previous: Coding Styles/Python Index Next: Development Tools