RidgeRun Developer Manual - Coding Styles - Javascript
WORK IN PROGRESS. Please Contact RidgeRun OR email to support@ridgerun.com if you have any questions. |
RidgeRun Developer Manual |
---|
Coding Styles |
Development Tools |
Editors |
Debugging Tools |
|
Profiling Tools |
Methodologies |
Design Patterns |
RidgeRun Developer Manual/Testing |
RidgeRun Developer Manual/Build Systems |
Contact Us |
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