JS Coding Convention
Improvements? Suggestions? email dna@hola.org
Coding conventions used by other companies: AirBNB | google | crockford | wordpress | drupal | weflex
Overview: consistent & minimal
Be consistent
If there is no specific rule in this document - be consistent with existing codebase (use rgrep).
If you're editing code, take a few minutes to look at the code
around you and determine its style.
If it prints error messages starting with a capital letter,
you should too. If it put spaces around complex expressions
but not around simple expressions, your complex expressions
should also have spaces around them.
The point of having style guidelines is to have a common
vocabulary of coding, so people can concentrate on what
you're saying rather than on how you're saying it.
If code you add to a file looks drastically different from the
existing code around it, it throws readers out of their
rhythm when they go to read it.
Avoid this.
Minimalistic and Condense
Wherever there is no specific rule, always prefer minimalism.
Less tokens, higher condensity: get as much code as possible
visible within the screen, so less scrolling is needed.
Sparse, elaborate, defensive code is not appreciated.
An example of absurd over-engineering. Although it's for Java, but the general idea holds for JS.
Tools
We write code by hand, line-by-line. We do have tools to
assist us locating conventions mistakes zlint -c
, but they are just a 'helpers'.
The tools match our conventions only 95%, so its still our personal responsibility to manually make
sure line-by-line the code we write matches the conventions
100%, whether the tool finds the mistakes or not.
Text file layout
Tab size is 8 spaces.
Shift width (indentation) is 4 spaces.
Column width 79 char, so it fits well with 80 col terminal.
Indentation is always one shift-width(4):
open_msg_box("closing file %s on server\n", file_name);
open_msg_box("closing file %s on server\n", file_name);
Formatting and naming
Code blocks and statements
if
for
while
don't require a code block for 0 or 1 statements: open{
on next line. only use a block for multiline blocks.try
catch
function
require a code block in all cases: open{
on the same line.
if/for/while
if/for/while block
if
for
while
open and close braces of a section should be on the same
level.
if (pkt) { pkt.close(); pkt.uninit(); }
if (pkt) { pkt.close(); pkt.uninit(); }
if
for
while
statement that takes more than one line should always have
braces
same thing goes when the then/loop part is more than one line
if (slot.dw_bus==pci_scan.card_slot[ret].dw_bus && slot.dw_slot==pci_scan.card_slot[ret].dw_slot && slot.dw_function==pci_scan.card_slot[ret].dw_function) { break; }
if (x==y) { my_func(param1, param2, param3, param4, param5, param6, param7); }
for (ret=0; ret<pci_scan.dw_cards; ret++) if (slot.dw_bus==pci_scan.card_slot[ret].dw_bus && slot.dw_slot==pci_scan.card_slot[ret].dw_slot && slot.dw_function==pci_scan.card_slot[ret].dw_function) break;
for (ret=0; ret<pci_scan.dw_cards; ret++) { if (slot.dw_bus==pci_scan.card_slot[ret].dw_bus && slot.dw_slot==pci_scan.card_slot[ret].dw_slot && slot.dw_function==pci_scan.card_slot[ret].dw_function) { break; } }
if/for/while without a statement
while(pop_first(list)) ;
while (pop_first(list));
for(i=0; i<10; i++) ;
for (i=0; i<10; i++);
if (a>b+10) ; else if (a>b+5) do_x(); else if (a>b+2) ; else do_y();
if (a>b+10); else if (a>b+5) do_x(); else if (a>b+2); else do_y();
Then
'then' part of if
statement should be in a separate line.
if (close_file) fclose(fp);
if (close_file) fclose(fp);
else if
else if
statements should be on the same level at the starting if
reason: this is similar to switch
statement
if (argv[1]==="--help") print_usage(); else if (argv[1]==="--run") { run_application(); print_results(); } else print_error();
if (argv[1]==="--help") print_usage(); else if (argv[1]==="--run") { run_application(); print_results(); } else print_error();
Functions
Tiny functions: same line.
One-liner: body in second line.
Two lines and above: a proper block. if anon function
: block open in same line.
var tiny = function(){code;}; var tiny = function() { code; };
var tiny = function(){ code; };
var tiny = function() { code; };
var tiny = function(){ code; }; var tiny = function(){ code; };
function tiny(){code;}; function tiny() { code; }; function tiny() { code; }
function tiny(){ code; } function tiny(){ code; } function tiny(){ code; }
function long(args){ code; code; } function long(args) { code; code; }
function long(args){ code; code; }
function long_args(a1, a2, a3, a4){ code; code; }
function long_args(a1, a2, a3, a4) { code; code; }
Inline functions
Function definition that fits the line:
let x = parse_args(...args, function(line){ let escaped = E.escape(line); ... });
Function definition that does not fit the line:
let x = parse_args(a, b, c, d, e, function(line, err) { let escaped = E.escape(line); ... });
let x = parse_args(a, b, c, d, e, function(line, err) { let escaped = E.escape(line); ... });
let x = parse_args(a, b, c, d, e, function(line, err){ let escaped = E.escape(line); ... });
let x = parse_args(a, b, c, d, e, function(line, err) { let escaped = E.escape(line); ... });
Functions spacing
No space between function name and opening parenthesis
No space between opening parenthesis and first parameter
One space after comma:
printf ("hello %s\n", "world"); printf( "hello world\n" ); printf("hello world\n","world");
printf("hello %s\n", "world");
Breaking a long line
When breaking up a long line, it should continue with one shift-width for indentation
if (line_length>1 && (screen.sz.vertical<buffer.sz.vertical || explicit_lines)) { console.log("this is a test section that will show how to handle "+ "long lines, such as this one which is 2 lines long"); }
if (line_length>1 && (screen.sz.vertical<buffer.sz.vertical || explicit_lines)) { console.log('this is a test section that will show how to handle '+ 'long lines, such as this one which is 2 lines long'); }
switch statements
switch
statements should have case
on the same level, no space before :
.
switch (key) { case KEY_UP : key = UP; break; case KEY_DOWN : key = DN; break; default : key = NONE; }
switch (key) { case KEY_UP: key = UP; break; case KEY_DOWN: key = DN; break; default: key = NONE; }
switch (key) { case KEY_UP: key = UP; break; case KEY_DOWN: key = DN; break; default: key = NONE; }
1-liner case
: with single statement plus break
, or a return
statement.
switch (key) { case KEY_UP: line--; break; case KEY_DOWN: for (x=line; x; x--) line++; break; case KEY_ESC: return; default: bubble = true; }
switch (key) { case KEY_UP: line--; break; case KEY_DOWN: for (x=line; x; x--) line++; break; case KEY_ESC: return; default: bubble = true; }
Never break
after return
switch (key) { case KEY_UP: line--; break; case KEY_ESC: return; break; }
switch (key) { case KEY_UP: line--; break; case KEY_ESC: return; }
Never break
at end of default
switch (key) { case KEY_UP: line--; break; default: line++; break; }
switch (key) { case KEY_UP: line--; break; default: line++; }
Reserved words
One space after reserved words before the opening parenthesis,
except for function
and catch
(which is function like):
if(close_file)
if (close_file)
for(i=0; !is_last(i); i++);
for (i=0; !is_last(i); i++);
try{ code; } catch (e) { code; }
try { code; } catch(e){ code; }
var t = function (api) { api('test'); };
var t = function(api){ api('test'); };
try-catch
Multiline try
: close block on catch
line (like do-while
):
try { short_code; } catch(e){ code; }
try { longer_code; } catch(e){ code; }
try { and_even; longer_code; } catch(e){ code; }
Catch variable should be named 'e'
try { res = yield etask.nfn_apply(zmongo.collection, '.save', [obj]); } catch(error){ handle_error(zmongo, 'save', error, obj); }
try { res = yield etask.nfn_apply(zmongo.collection, '.save', [obj]); } catch(e){ handle_error(zmongo, 'save', e, obj); }
Operator spacing
both sides should be equal: either space before and after, or no spaces at all.
Trivial > >= < <= == != expressions
Trivial > >= < <= == !=
expressions should not have spaces around them: a_var
.
if (x> 5) if (x >5) if (x > 5)
if (x>5)
Nearly-trivial expressions can be with or without spaces: a[3]
, f(x)
, x.y.z
, a.b.c(x, y, z)
.
if (f(x, y) > g(y, z)) if (f(x, y)>g(y, z)) if (x.y == 5) if (x.y==5) if (x.y>=5) if (x.y >= 5)
We consider simple short arithmetics also as nearly-trivial
expressions: x+1
, 2*x
.
if (x+1>5) if (x+1 > 5)
if one side is not trivial, then must have spaces.
if (a&&a.b) if (a && a.b)
if (!a&&a.b)
if (!a && a.b)
if one side is long, then prefer to have spaces.
if (a&&Array.isArray(a))
if (a && Array.isArray(a))
Assignments spaces
Spaces around assignments = += -= *= /= &= |=
are not mandatory in for()
loops.
a=b; d+=x;
a = b; d += x;
for (i=0; i<10; i+=4); for (i = 0; i<10; i += 4);
Unary operators
Don't put a space after ++
--
!
, and other unary operators. increment after the value, not
before.
i --; ++j;
i--; j++;
if (! i)
if (!i)
var speed_int = + speed_str;
var speed_int = +speed_str;
Multiple +
Multiple +
operators are trivial expressions, and thus we prefer not to
put spaces around them (such as string concatenation).
msg = 'hello ' + name + '!';
msg = 'hello '+name+'!'; msg = 'hello '+get_my_name()+'!'; msg = 'hello ' + get_my_name()+'!';
Conditionals ? :
? :
should have spaces all around them.
var msg = login_ok?'Welcome':'Please login';
var msg = login_ok ? 'Welcome' : 'Please login';
Minimalism
Do not use unneeded parentheses in expressions:
if ((a!=b) || (c!=d)) ...
if (a!=b || c!=d) ...
Avoid empty lines
Never in any case have two empty lines together.
A single empty line is allowed in global scope and between
functions, but use it sparsely.
Separate line
Should not separate with more than one blank line between sections, functions etc.
function init(){ ...; }; function end(){ ...; };
function init(){ ...; }; function end(){ ...; };
function init(){ ...; }; function end(){ ...; };
Inside functions, should never have an empty line. Use comments to separate.
function setup_conn(orig){ var conn = net.connect(dst); conn.pipe(orig); conn.set_speed('fast'); if (conn.rx>MAX_SPEED) conn.set_speed('medium'); return conn; }
function setup_conn(orig){ var conn = net.connect(dst); conn.pipe(orig); // setup speed conn.set_speed('fast'); if (conn.rx>MAX_SPEED) conn.set_speed('medium'); return conn; }
function setup_conn(orig){ var conn = net.connect(dst); conn.pipe(orig); conn.set_speed('fast'); if (conn.rx>MAX_SPEED) conn.set_speed('medium'); return conn; }
Arrow function short syntax
When possible, use arrow function short syntax
let t = data=>{ return do_something(data); }
let t = data=>do_something(data);
return statements
Minimal return statement
return
statements should not have parentheses and should not have a
space after them:
return (0);
return 0;
return ;
return;
return undefined
For return undefined
, just do return
, undefined
is the default of JS.
If a function returns undefined
at exit, you don't need to call return
.
function x(x){ if (!x) return undefined; ...; if (x_is_valid(x)) return x+5; ...; }
function x(x){ if (!x) return; ...; if (x_is_valid(x)) return x+5; ...; }
return an assignment
You may return an assignment in order to merge assignment and return statments. Do not add unneeded brackets
return (a = b);
return a = b;
Cast to void
You may 'cast' to void
in order to merge an action and return undefined
into a single line.
return void sock.close();
if (ret) return void (cache[id] = ret);
if (clock) clock = void clock.restore();
Compare to 0
When 0 stands as non valid or empty option, avoid comparing to 0
if (total==0) return 'NA';
if (!total) return 'NA';
Long strings
Breaking up long strings: no space around +
, and prefer +
at beginning of next line.
msg = 'this ' + 'is a long string.'; msg = 'this ' + 'is a long string.';
msg = 'this '+ 'is a long string.'; msg = 'this ' +'is a long string.';
msg = 'this ' +'is a long string.';
Space, if needed should be at the end of each line and not at the beginning of the next line
msg = 'this' +' is a long string';
msg = 'this ' +'is a long string';
File-level closures
File-level closures: do not indent the whole file. leave a space after the opening, and before the closing:
(function($, chrome, console){ code; code; })(jQuery, chrome, console);
(function($, chrome, console){ code; code; })(jQuery, chrome, console);
JS file template
IE/Chrome/FF template
// LICENSE_CODE ZON 'use strict'; /*jslint browser:true*/
NodeJS application template
#!/usr/bin/env node // LICENSE_CODE ZON 'use strict'; /*jslint node:true*/ require('util/config.js');
NodeJS module template
// LICENSE_CODE ZON 'use strict'; /*jslint node:true*/ require('util/config.js');
require()
Local packages should include .js
in the name. Global packages should be the package name only
(NODE_PATH
environment variable should be defined).
const express = require('/usr/lib/node_modules/express');
const express = require('express');
const get_rules = require('./get_rules');
const get_rules = require('./get_rules.js');
require
should be called at the top of the file, not locally, so that
unit-tests will surface all the problems.
function readconf(){ return require('fs').readFileSync('conf'); }
// top of file... const fs = require('fs'); // later down in the file... function f(){ return fs.readFileSync('conf'); }
Constants and variable names of required modules should normally be the same, according to the module name.
const rules = require('./get_rules.js'); // in foo.js const actions = require('./get_rules.js'); // in bar.js
const get_rules = require('./get_rules.js'); // in foo.js and bar.js
Use _
instead of -
/
const node_getopt = require('node-getopt'); const svc_reconf = require('../svc/reconf.js');
Always prepend z
to variable names for the following modules from pkg/util
, and, when necessary, for other modules that clash with global
packages.
const zconf = require('util/config.js'); const zconf = require('util/config_int.js'); const zescape = require('util/escape.js'); const zhttp = require('util/zhttp.js'); const zhttp = require('util/http.js'); const zurl = require('util/url.js'); const zutil = require('util/util.js'); const zos = require('util/os.js');
You may drop the node-
prefix, or -js
suffix.
const getopt = require('node-getopt'); const uglify = require('uglify-js');
Special modules with short names: jquery and lodash.
var $ = require('jquery'); var _ = require('lodash');
Comments
Prefer C++ comment over C comments.
/* close all files */ fclose(fp);
// close all files fclose(fp);
/* multi * line * comment */
// multi // line // comment
Comments aligned
Comments should be aligned as the code they comment, or one space after the end of the line.
/* * close all files */ fclose(fp);
/* close all files */ fclose(fp);
fclose(fp); /* close all files */
Long comments
Comments which occupy more than one line should adhere to the following guideline:
// // close all the files that were opened before the function was called // and send them to the output file // fclose(fp);
// close all the files that were opened before the function was called // and send them to the output file fclose(fp);
Multiline comments
Multiline comments should always be on their own, not continue on existing statements. If longer, put the comment at the line above.
var isa_pnp_scan = { search_id: 'a-test', // if search_id.vendor[0]==0 - scan all vendor // IDs // if searchId.dwSerial==0 - scan all serial // numbers cards: 2, // number of cards found card: [card1, card2], // cards found };
var isa_pnp_scan = { search_id: 'a-test', // if search_id.vendor[0]==0 - scan // all vendor IDs // if searchId.dwSerial==0 - scan all // serial numbers cards: 2, // number of cards found card: [card1, card2]; // cards found };
var isa_pnp_scan = { // if search_id.vendor[0]==0 - scan all vendor IDs // if searchId.dwSerial==0 - scan all serial numbers search_id: 's-test', cards: 2, // number of cards found card: [card1, card2], // cards found };
XXX - todo mark
Comments of XXX
should be used for temporary code, that would be changed in
the future: hack, quick workaround, non-elegant code, bug.
They should be XXX [login]: COMMENT
, multiple names should be separated with /
.
XXX derry: ANDROID move to compat XXX derry: arik: move to compat XXX derry arik: move to compat
XXX derry: move to compat XXX derry ANDROID: move to compat XXX derry/arik: move to compat
XXX
comments are tasks, so they should always have someone
assigned. Preferably yourself!
XXX: move to compat
XXX some-other-developer: move to compat
XXX derry: move to compat
Using XXX comments can be used, when needed, to override any possible rules!
if (0) it('pause_disconnect', ()=>etask(function*(){
if (0) // XXX sergey: lots of sporadic failues it('pause_disconnect', ()=>etask(function*(){
// XXX derry: copied urgent gist as-is to hack around a bug from angular // http://github.com/.... // will fix by 8-Nov-2016
Loops
for vs while
If a while
loop has an init
and/or next
expression, then use a for
loop:
i = 0; while (i<10) { ...; i++; }
for (i = 0; i<10; i++) ...;
If a for
loop has only a condition
, then use while
:
for (; have_more();) ...;
while (have_more()) ...;
If no init
/condition
/next
, then any option is ok:
for (;;) do_endless_work();
while (1) do_endless_work();
for loops
In for
loops, when there is a function that gets the next element, it
should be done once (inside the step condition):
assign inside a statement:
for (i = 0, result = get_char(); result=='\r'; result = get_char(), i++) handle_result(result);
for (i = 0; (result = get_char())=='\r'; i++) handle_result(result);
do-while
do-while
: long do
block should be closed on while
line:
do add_item(); while (have_items);
do add_item(); while (have_items);
do { add_item(); another_action(); } while (have_items);
Spacing
Put one space before and after an assignment.
var a=0; x= x+1; bits|=BIT5;
var a = 0; x = x+1; bits |= BIT5;
If it is in a for
loop, you can skip the spaces. if you skip one of the spaces,
skip both:
for (i= 0; i; i--) printf("%d", i);
for (i=0; i; i--) printf("%d", i);
for (i = 0; i; i--) printf("%d", i);
Don't put a space before a statement separator, put one after it:
for (i=0 ;i ;i--, j*=2) ;
for (i=0; i; i--, j*=2);
for (i = 0; i; i--, j *= 2);
Check fail in same line of call
Prefer to check for failures in same line of calling the function.
i = str.indexOf("\r\n"); if (i<0) return;
if ((i = str.indexOf("\r\n"))<0) return;
m = str.match("EOF"); if (!m) return;
if (!(m = str.match("EOF"))) return;
Object notation
var node = { name: 'server', port: 42, status: 'updated', setup_time: 10*1000, };
var node = { name: 'server', port: 42, status: 'updated', setup_time: 10*1000, };
Multiline objects: should have a comma after last property.
var node = { name: 'server', status: 'updated', setup_time: 10*1000 };
var node = { name: 'server', status: 'updated', setup_time: 10*1000, };
Short objects: should be in a single line if they are very short.
var node = {name: 'server', port: 42,};
var node = { name: 'server', port: 42, };
var node = {name: 'server', port: 42};
Object contains short object: should be in a single line.
var node = {info: {name: 'server', port: 42}, };
var node = { info: {name: 'server', port: 42}, };
var node = {info: {name: 'server', port: 42}};
Objects spacing:
- before
,
and:
signs, there should not be a space, while after those signs, space is required. - after
{
and before}
signs, there should not be a space.
var node = {name: 'server', port: 42};
var node = {name: 'server' , port: 42};
var node = {name : 'server', port : 42};
var node = { name: 'server', port: 42 };
Casting
Convert to number: +val
Convert to signed 32bit int: val|0
Convert to unsigned 32bit int: val>>>0
Convert to Boolean: !!val
Convert Boolean to 0/1: +bool_val
or +!!any_val
String literals: prefer 'single quotes'
over "double quotes"
"I am the walrus."
'I am the walrus.'
"I'm the walrus."
exports
Use variable E as alias to exports.
exports.foo = function(){ ... };
const E = exports; E.foo = function(){ ... };
Export functions
Only export functions which are used outside of the module, keep everything else local.
E.internal_helper = function(){ ... }; ... E.internal_helper();
function internal_helper(){ ... } ... internal_helper();
Unit-test exports
Use variable E.t
for exports only used in tests, give the function name to use
inside module.
// in foo.js E.internal_function = function(){ ... }; ... E.internal_function(); // in test.js foo.internal_function();
// in foo.js const E = exports; ... function internal_function(){ ... } ... internal_function(); ... E.t = {internal_function: internal_function, ...}; // in test.js foo.t.internal_function();
Continuation .method()
Continuation .method()
or +'str'
on next line can be same indentation as parent line. Same goes
for single var
definition, and return
.
$('<h1>', $('<div>') .append('<span>'));
$('<h1>', $('<div>') .append('<span>'));
$('<div>') .append('<span>');
$('<div>') .append('<span>');
elm = $('<span>') .append('<span>');
var elm = $('<span>') .append('<span>');
var e1 = $('<div>'), e2 = $('<span>') .append('<span>');
var e1 = $('<div>'); var e2 = $('<span>') .append('<span>');
return $('<div>') .append('<span>');
return $('<h2>', $('<div>') .append('<span>'));
return $('<h2>', $('<div>') .append('<span>'));
var s = '<div>' +'<span>';
s = x ? '<child>' : '<nothing>' +'<span>';
s = '<div>' +'<span>';
return '<div>' +'<span>';
. of .method()
.
of .method()
MUST be the first character on a method call continuation
line.
set_user(db.open('users'). get$(user));
set_user(db.open('users') .get$(user));
$('<h1>', $('<div>'). append('<span>'));
$('<h1>', $('<div>') .append('<span>'));
Variable declarations
var
declarations longer than one line must have their own var
.
var a = 'test', b = f('another', 'test'), c = 'yet another';
var a = 'test'; var b = f('another', 'test'); var c = 'yet another';
var a = 'test'; var b = f('another', 'test'), c = 'yet another';
var a = 'test', b = f('another', 'test'), c = 'yet another';
var a = 'test', b = f('another', 'test'); var c = 'yet another';
var a = 'test'; var b = f('another', 'test'); var c = 'yet another';
Comparing variables
When comparing to undefined
use ===
and !==
On any other case, use ==
and !=
if (a==='OK')
if (typeof a==='undefined')
if (a=='OK')
if (a===undefined)
Prefer .bind() over this
When assigning functions that depend on this
, use bind()
.
var log = console.log; log('a'); // TypeError: Illegal invocation
var log = console.log.bind(console); log('a');
Shorten using locality
Use locality to shorten names, relying on the contexts of the local code scope.
for (let opration_idx=0; operation_idx<operations.length; operation_idx++) ...;
for (let i=0; i<operations.length; i++) ...;
for (let allowed_customer in allowed_customers) ...;
for (let c in allowed_customers) ...;
UNIX notation naming
Code will be in UNIX notation. UNIX names should not include the data type, rather the meaning of the information in the data.
var bIsCompleted; var iCount;
var is_completed, data, vendor_id, count, buffer, new_name, name; function read_data_block(){} const MS_PER_SEC = 1000;
Positive naming
Default to using positive naming, rather than negative (no/not/disable...). This helps avoid double negation (not not).
if (!not_customer(customer)) disable_customer(customer, false);
if (is_customer(customer)) enable_customer(customer, true);
var no_rule = lookup_rule(rule)<0; if (!no_rule) rm_rule(); else add_rule();
var have_rule = lookup_rule(rule)>=0; if (have_rule) rm_rule(); else add_rule();
Simplicity and usability
Command line options, or option variable (opt), use common usage as default even if negative
zlxc --browser
zlxc --no-browser
opt = {exit_on_err: 1};
opt = {no_exit: 1};
Default value
Using implicit undefined
as default value
let need_reconf = false; if (is_host(':servers')) need_reconf = true;
let need_reconf = undefined if (is_host(':servers')) need_reconf = true;
let need_reconf; if (is_host(':servers')) need_reconf = true;
Multi-signature functions
For functions that have multiple signatures or where there is an optional (not last) argument, you may optionally add the different possible signatures as a comment, in the nodejs signature documentation style.
// _apply(opt, func[, _this], args) // _apply(opt, object, method, args) E._apply = function(opt, func, _this, args){ ... };
Saving the value of this
When saving the value of this
for use in lexically nested functions, use _this
as the variable name.
var self = this;
var _this = this;
function on_end(opt){ var _this = this; return function on_end_cb(msg){ if (_this.socket) return 'socket'; }; }
Use __this
and ___this
... for deep code.
function on_end(opt){ var _this = this; return function(msg){ if (_this.socket) return 'socket'; var __this = this; setTimeout(function(){ __this.socket = undefined; }, 1000); }; }
Functions as class definition
When a function is a class definition, e.g. needs new
in order to use it, it should start with capital letter
function etask(opt, states){ ... }
function Etask(opt, states){ ... }
ES6
Arrow functions
No spaces around =>
. Prefer to drop ()
e.on('play', () => { player.start(); started = 1; }
e.on('play', ()=>{ player.start(); started = 1; }
socket.on('connect', ()=> state = 'CONNECTED' );
socket.on('connect', ()=>state = 'CONNECTED');
docs.forEach(doc => add(doc));
docs.forEach(doc=>add(doc));
docs.forEach((doc, index)=>{ if (index) add(doc, index); });
Drop ()
around single argument.
docs.forEach((doc)=>add(doc));
docs.forEach(doc=>add(doc));
Prefer to drop {}
around single short statement.
docs.forEach(doc=>{ add(doc); });
docs.forEach(doc=>add(doc));
Preferred Methods
never use .indexOf()
for arrays/strings when .includes()
fits.
if (apps.indexOf(zserver_match[1])<0) apps.push(zserver_match[1]);
if (!apps.includes(zserver_match[1])) apps.push(zserver_match[1]);
never user .indexOf()
for strings when .startsWith()
fits.
return !patch[0].indexOf(changed_file) || !patch[1].indexOf(changed_file);
return patch[0].startsWith(changed_file) || patch[1].startsWith(changed_file);
Generators
No spaces around *
.
etask(function* (){ ... });
etask(function*(){ ... });
etask(function * get_request(){ ... });
etask(function*get_request(){ ... });
Class definition
Class name start with capital leter
class SimpleView {}
class simple_view {}
class Simple_view {}
Add space between class name and {
class A{}
class A {}
Etask methods
Indentation reducing is allowed, but class methods should be indented
class A { prop(){ return etask(function*(){ code; }); } }
class A { prop(){ return etask(function*(){ code; }); } }
class A { prop(){ let _this = this; return etask(function*(){ code; }); } }
class A { prop(){ let _this = this; return etask(function*(){ code; }); } }
Programming technique
Generic code
Code should be written generically only if during the time you
are writing it, it is called at least twice, if not more,
and save code in the caller.
Generic code need a deeper unit-tests then regular code.
E.first_weekday_of_month = function(wd, d){ ... };
E.strftime = function(fmt, d, opt){ ... };
Early return
Avoid if()
on 50% or more of a function.
let inited; E.init = ()=>{ if (!inited) { inited = true; register_app(); set_timers(); } };
let inited; E.init = ()=>{ if (inited) return; inited = true; register_app(); set_timers(); };
No defensive code
No function argument validation
function send_msg(client, msg, opt){ if (client===undefined || msg===undefined) throw new Error('send_msg: wrong params'); opt = opt||{}; msg = prepare_msg(msg); ... }
function send_msg(client, msg, opt){ opt = opt||{}; msg = prepare_msg(msg); ... }
Assigning in a truth value (if or while)
Assigning truth value in if
while
for
helps shorten and simplify code.
for (i = 0; get_result(i); i++) handle_result(get_result(i));
for (i = 0;; i++) { result = get_result(i); if (!result) break; handle_result(result); }
for (i = 0; result = get_result(i); i++) handle_result(result);
if (compute_num()) return compute_num();
if (x = compute_num()) return x;
while (1) { i = input(); if (!i) break; handle_input(i); }
while (i = input()) handle_input(i);
Temporary disabling a test
When temporary disabling test code that fail:
Do not indent the code of the disabled tests.
if (0) // XXX yoni: fails on BAT jtest_eq(...); if (0) // XXX derry: need fix for Ubuntu { jtest1(); jtest2(); }
if (0) // XXX yoni: fails on BAT jtest_eq(...); if (0){ // XXX derry: need fix for Ubuntu jtest1(); jtest2(); }
If it is only one test (one statement), then don't use { }
even if the statement is written in 2 lines:
if (0) // XXX: yoni: fails on BAT { jtest_run(xxx, yyy, zzz); }
if (0) // XXX: yoni: fails on BAT jtest_run(xxx, yyy, zzz);
Open '{' on the same if() line:
// XXX: yoni: fails on BAT if (0) { jtest_run(xxx, yyy, zzz); jtest_run(xxx, yyy, zzz); }
if (0){ // XXX: yoni: fails on BAT jtest_run(xxx, yyy, zzz); jtest_run(xxx, yyy, zzz); }
Performance
99% of the code is not performance critical. So always try to
write shorter, simpler, more natural and modern code. If ES6
gives nicer simpler constructs - we use them.
But, in the rare 1% of the code that performs tight loops, we
deviate from 'nice simple code', and write a little longer
code, to avoid JS VM JIT in-efficiencies.
We normally check V8, and re-check check these issues
periodically as newer versions of JS VM's come out.
for..of: 3x-20x slower
for (let p of patterns) add_pattern(p);
for (let i=0; i<patterns.length; i++) add_pattern(patterns[i]);
Wrong performance assumptions
We list here commonly mistaken performance assumptions. They might have been correct in the past, but JS VMs get better and better - so these performance improvement assumptions are not longer correct.
Map is faster than Object
For keys that are plain positive numbers, Object may be faster due to Array optimizations in the VM. But for keys that are strings - Map is faster.
let cache = {}; cache[key] = value;
let cache = new Map(); cache.set(key, value);
Deep Fix
Hola specific API
Disable feature
TBA: code examples
etask
Overview
etask
is Hola's library for writing asynchronous code in a concise
synchronous like manner.
Why etask?
Promises/async functions don't support structured cancelation,
and callbacks are difficult to coordinate/compose.etask
supports cancelation by default and can manage callback and
promise driven subtasks easily.
For example, if we wanted to find a specific user from the DB,
the simplest synchronous code would look like this:
function user_find_sync(){ let conn = mongodb.connect(); let iter = mongodb.find(conn.users, {}); let u; while ((u = mongodb.get_next(iter))) { if (correct_user(u)) break; } mongodb.close(conn); return u && u.username; }
But this is synchronous, blocking code. JavaScript is async
and single threaded, so blocking calls are a huge
performance problem.
So lets see how to port the sync code to async code. Lets
start with the ideal solution, using Hola's etask
s and ES6 generators.
etask ES6 (perfect!):
let user_find_es6 = ()=>etask(function*(){ let conn = yield mongodb.connect(); let iter = yield mongodb.find(conn.users, {}); let u; while (u = yield mongodb.find_next(iter)) { if (yield correct_user(u)) break; } yield mongodb.close(conn); return u && u.username; }
Compare this with other possible approaches:
callbacks (callback-hell...):
let conn, iter; function user_find_cbs(cb){ mongodb.connect(mongo_connected_cb, cb); } function mongo_connected_cb(cb){ mongodb.find(conn.users, {}, users_find_cb, cb); } function users_find_cb(iter, cb){ mongodb.get_next(iter, filter_users_cb); } function filter_users_cb(u, cb){ if (!u) return mongo_disconnect(cb); correct_user(correct_user_cb, u, cb); } function correct_user_cb(u, is_correct, cb){ if (is_correct) return mongo_disconnect(cb, u.username); mongo_connected_cb(cb); } function mongo_disconnect(cb, username){ mongodb.close(conn, disconnected_cb, cb, username); } function disconnected_cb(cb, username){ cb(username); }
promise (includes ugly recursion to emulate a loop, nested then
, and obscure execution flow):
function user_find(){ return mongodb.connect() .then(function(conn){ return mongodb.find(conn.users, {}); }) .then(function filter(){ return mongodb.find_next(iter).then(function(u){ if (!u) { return mongodb.close(conn) .then(function(){ return cb(); }); } return correct_user(u).then(function(is_correct){ if (is_correct) { return mongodb.close(conn).then(function(){ user_find_end(user.username); }); } return filter(iter); }); }); }); }
async function
(no support for cancelation if the parent function exits
early):
async function user_find(){ let conn = await mongodb.connect(); let iter = await mongodb.find(conn.users, {}); let u; while (u = await mongodb.find_next(iter)) { if (await correct_user(u)) break; } await mongodb.close(conn); return u && u.username; }
etask ES5 (when generators are not available):
function user_find(){ return etask([function(){ return mongodb.connect(); }, function(conn){ return mongodb.find(conn.users, {}); }, function(iter){ return etask.while([function(){ return mongodb.find_next(iter); }, function(u){ if (!u) return this.break(); return correct_user(u); }, function(is_correct){ if (is_correct) this.break(u.username); }]); }, function(u){ username = u; return mongodb.close(conn); }, function(){ return username; }]); }
Cheat sheet
synchronous | etask ES5 | etask ES6 |
for | this.for() | for |
continue | this.continue() | continue |
return | this.return() | return |
Usage examples
Simple calls to etask or promise returning functions:
let process_items = ()=>etask(function*(){ let items = yield get_items(); for (let item of items) { if (!(yield item_valid(item)) return false; } return true; });
Call a callback driven function:
let make_request = url=>etask(function*(){ return yield etask.nfn_apply(request, [url]); });
Wait on an event emitter:
let save_request = (req, file)=>etask(function*(){ req.pipe(file) .on('end', ()=>this.continue()) .on('error', e=>this.throw(e)); return yield this.wait(); });
let save_request = (req, file)=>etask(function*(){ req.pipe(file) .on('end', this.continue_fn()) .on('error', this.throw_fn()); return yield this.wait(); });
Scheduled resource cleanup (like Go's defer
statement):
let do_something = ()=>etask(function*(){ let temp_dir = yield make_temp_dir(); // temp dir will be cleaned up whether the function succeeds or throws this.finally(()=>unlink(temp_dir)); yield do_step1(); yield do_step2(); return yield do_step3(); });
Coding
When possible, use ES6 arrow function with no brackets and no return
let t = function(fn, domain, expected){ let i = 7; return etask(function*(){ ... }; });
let t = (fn, domain, expected)=>etask(function*(){ let i = 7; ... });
return etask()
in the middle of a function should be indented to the function
level. Should be used rarely, only when fast path needed
let get_headers = req=>{ let cache; if (cache = cache_getreq) return cache; return etask(function*get_headers(){ ... }); }
let get_headers = req=>{ let cache; if (cache = cache_get(req)) return cache; return etask(function*get_headers(){ ... }); }
etask class indentation
class Read_client { search(q){ let _this = this; return etask(function*(){ ... }); } }
class Read_client { search(q){ let _this = this; return etask(function*(){ ... }); } }
class Read_client { search(q){ let _this = this; return etask(function*(){ ... }); } }
class Read_client { search(q){ let _this = this; return etask(function*(){ ... }); } }
No hidden (automatic) yield
in return.
let insert_cid_to_mongo = cid=>etask(function*(){ let client = yield mongodb.findOne(...cid...); return mongodb.update(...client...); });
let insert_cid_to_mongo = cid=>etask(function*(){ let client = yield mongodb.findOne(...cid...); return yield mongodb.update(...client...); });
etask name for lib API
E.find_all = (zmongo, selector, opt)=>etask(function*(){ ... });
E.find_all = (zmongo, selector, opt)=>etask(function*mongo_find_all(){ ... });
No etask name for internal functions
let generate_daily = user=>etask(function*generate_daily(){ ... });
let generate_daily = user=>etask(function*(){ ... });
Avoid enclosing a large portion of a function in a try block.
Prefer this.on('uncaught', ...) and this.finally when
applicable. (why?)
let get_zone_bw = config=>(req, res)=>etask(function*(){ let zone = yield check_zone(config, req, res); try { let data = yield get_graphite_bw(req, zone.customer, [zone.name]); res.json(data); } catch(e){ zerr(zerr.e2s(e)); return void res.status(500).send('err'); } });
let get_zone_bw = config=>(req, res)=>etask(function*(){ let zone = yield check_zone(config, req, res); this.on('uncaught', err_handler(res)); let data = yield get_graphite_bw(req, zone.customer, [zone.name]); res.json(data); });
React code
Read React docs and follow guidlines and recommendations unless they conflict with Hola React conventions.
Use Components to stay DRY
Use the same guidlines for code repetition as regular functions: Try not repeat yourself.
Move shared JSX to a util component to repeat markup
<div className="feature"> <h3>title1</h3> <p>text</p> </div> <div className="feature"> <h3>title2</h3> <p>text2</p> </div>
let Feature = props=> <div className="feature"> <h3>{props.title}</h3> <p>{props.children}</p> </div>; <Feature title="title1">text1</Feature> <Feature title="title2">text2</Feature>
JSX
JSX coding convention follows HTML coding. When switching from JS code to JSX code use 4 chars indentation on the first. Rest follow HTML convention of 2 chars indentation.
return <View> </View>;
return <View></View>;
return <View> </View>;
return <View> <Button/> </View>;
return ( <View> <Button/> </View>);
return ( <View> <Button/> </View> );
return ( <View> {show_panel && <Panel/>} </View>) );
return ( <View> {show_panel && <Panel/>} </View> );
CSS
Unittest
TBD