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):
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
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
/for
/while
without a statement
Then
'then' part of if
statement should be in a separate line.
else if
else if
statements should be on the same level at the starting
if
reason: this is similar to switch
statement
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.
Inline functions
Function definition that fits the line:
Function definition that does not fit the line:
Functions spacing
No space between function name and opening parenthesis
No space between opening parenthesis and first parameter
One space after comma:
Breaking a long line
When breaking up a long line, it should continue with one shift-width for indentation
switch
statements
switch
statements should have case
on the same level, no space before :
.
1-liner case
: with single statement plus break
,
or a return
statement.
Never break
after return
Never break
at end of default
Reserved words
One space after reserved words before the opening parenthesis, except
for function
and catch
(which is function like):
try
-catch
Multiline try
: close block on catch
line
(like do-while
):
Catch variable should be named 'e'
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
.
Nearly-trivial expressions can be with or without
spaces: a[3]
, f(x)
, x.y.z
,
a.b.c(x, y, z)
.
We consider simple short arithmetics also as nearly-trivial expressions:
x+1
, 2*x
.
if one side is not trivial, then must have spaces.
if one side is long, then prefer to have spaces.
Assignments spaces
Spaces around assignments = += -= *= /= &= |=
are not mandatory in for()
loops.
Unary operators
Don't put a space after ++
--
!
,
and other unary operators.
increment after the value, not before.
Multiple +
Multiple +
operators are trivial expressions, and thus
we prefer not to put spaces around them (such as string concatenation).
Conditionals ? :
? :
should have spaces all around them.
Minimalism
Do not use unneeded parentheses in expressions:
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.
Inside functions, should never have an empty line. Use comments to separate.
Arrow function short syntax
When possible, use arrow function short syntax
return
statements
Minimal return
statement
return
statements should not have parentheses and should not
have a space after them:
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
.
return
an assignment
You may return an assignment in order to merge assignment and return statments. Do not add unneeded brackets
Cast to void
You may 'cast' to void
in order to merge an action and
return undefined
into a single line.
Compare to 0
When 0 stands as non valid or empty option, avoid comparing to 0
Long strings
Breaking up long strings: no space around +
, and prefer
+
at beginning of next line.
Space, if needed should be at the end of each line and not at the beginning of the next line
File-level closures
File-level closures: do not indent the whole file. leave a space after the opening, and before the closing:
JS file template
IE/Chrome/FF template
NodeJS application template
NodeJS module template
require()
Local packages should include .js
in the name.
Global packages
should be the package name only (NODE_PATH
environment
variable should be defined).
require
should be called at the top of the file, not locally,
so that unit-tests will surface all the problems.
Constants and variable names of required modules should normally be the same, according to the module name.
Use _
instead of -
/
Always prepend z
to variable names for the following modules
from pkg/util
, and, when necessary, for other modules that
clash with global packages.
You may drop the node-
prefix, or -js
suffix.
Special modules with short names: jquery and underscore.
Comments
Prefer C++ comment over C comments.
Comments aligned
Comments should be aligned as the code they comment, or one space after the end of the line.
Long comments
Comments which occupy more than one line should adhere to the following guideline:
Multiline comments
Multiline comments should always be on their own, not continue on existing statements. If longer, put the comment at the line above.
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
comments are tasks, so they should always have someone
assigned. Preferably yourself!
Using XXX comments can be used, when needed, to override any possible rules!
Loops
for
vs while
If a while
loop has an init
and/or
next
expression, then use a for
loop:
If a for
loop has only a condition
, then use
while
:
If no init
/condition
/next
,
then any option is ok:
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:
do
-while
do-while
: long do
block should be closed on
while
line:
Spacing
Put one space before and after an assignment.
If it is in a for
loop, you can skip the spaces.
if you skip one of the spaces, skip both:
Don't put a space before a statement separator, put one after it:
Check fail in same line of call
Prefer to check for failures in same line of calling the function.
Object notation
Multiline objects: should have a comma after last property.
Short objects: should be in a single line if they are very short.
Object contains short object: should be in a single line.
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.
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"
exports
Use variable E as alias to exports
.
Export functions
Only export functions which are used outside of the module, keep everything else local.
Unit-test exports
Use variable E.t
for exports
only used in tests, give the function name to use inside module.
Continuation .method()
Continuation .method()
or +'str'
on next line can be same indentation as
parent line. Same goes for single var
definition, and
return
.
', $('')
.append(''));
$('', $('')
.append(''));
$('')
.append('');
$('')
.append('');
elm = $('')
.append('');
var elm = $('')
.append('');
var e1 = $(''), e2 = $('')
.append('');
var e1 = $('');
var e2 = $('')
.append('');
return $('')
.append('');
return $('', $('')
.append(''));
return $('', $('')
.append(''));
var s = ''
+'';
s = x ? '' : ''
+'';
s = ''
+'';
return ''
+'';
.
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));
$('', $('').
append(''));
$('', $('')
.append(''));
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
for (let i=0; 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
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
When you (or someone else) find a coding mistake in your code:
BAD: just fixing that specific mistake you found
OK: fixing also all such mistakes you made in your own
code
GOOD: fixing all such mistakes, in the whole codebase, using
smart rgrep's to find them.
GREAT: if this is a common mistake people tend to repeat, then
once a month repeat the search to find such NEW mistakes.
EXCELLENT: add a rule to zlint to auto detect and if possible
auto fix the mistake.
Spark specific API
Disable feature
dynamic using reconf: preferred
env: usually when needed in init, for example port numbers
if (0): unittests and non production code
TBA: code examples
etask
Overview
etask
is Spark'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 Spark'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?)
Code is shorter and the indentation reduce readability.
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 Spark
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
title1
text
title2
text2
let Feature = props=>
{props.title}
{props.children}
;
text1
text2
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
;
return ;
return
;
return
;
return (
);
return (
);
return (
{show_panel &&
}
)
);
return (
{show_panel &&
}
);
CSS
Unittest
TBD
', $('')
.append(''));
$('')
.append('');
$('')
.append('');
elm = $('')
.append('');
var elm = $('')
.append('');
var e1 = $(''), e2 = $('')
.append('');
var e1 = $('');
var e2 = $('')
.append('');
return $('')
.append('');
return $('', $('')
.append(''));
return $('', $('')
.append(''));
var s = ''
+'';
s = x ? '' : ''
+'';
s = ''
+'';
return ''
+'';
.
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));
$('', $('').
append(''));
$('', $('')
.append(''));
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
for (let i=0; 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
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
When you (or someone else) find a coding mistake in your code:
BAD: just fixing that specific mistake you found
OK: fixing also all such mistakes you made in your own
code
GOOD: fixing all such mistakes, in the whole codebase, using
smart rgrep's to find them.
GREAT: if this is a common mistake people tend to repeat, then
once a month repeat the search to find such NEW mistakes.
EXCELLENT: add a rule to zlint to auto detect and if possible
auto fix the mistake.
Spark specific API
Disable feature
dynamic using reconf: preferred
env: usually when needed in init, for example port numbers
if (0): unittests and non production code
TBA: code examples
etask
Overview
etask
is Spark'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 Spark'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?)
Code is shorter and the indentation reduce readability.
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 Spark
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
title1
text
title2
text2
let Feature = props=>
{props.title}
{props.children}
;
text1
text2
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
;
return ;
return
;
return
;
return (
);
return (
);
return (
{show_panel &&
}
)
);
return (
{show_panel &&
}
);
CSS
Unittest
TBD
', $('')
.append(''));
return $('', $('')
.append(''));
var s = ''
+'';
s = x ? '' : ''
+'';
s = ''
+'';
return ''
+'';
.
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));
$('', $('').
append(''));
$('', $('')
.append(''));
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
for (let i=0; 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
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
When you (or someone else) find a coding mistake in your code:
BAD: just fixing that specific mistake you found
OK: fixing also all such mistakes you made in your own
code
GOOD: fixing all such mistakes, in the whole codebase, using
smart rgrep's to find them.
GREAT: if this is a common mistake people tend to repeat, then
once a month repeat the search to find such NEW mistakes.
EXCELLENT: add a rule to zlint to auto detect and if possible
auto fix the mistake.
Spark specific API
Disable feature
dynamic using reconf: preferred
env: usually when needed in init, for example port numbers
if (0): unittests and non production code
TBA: code examples
etask
Overview
etask
is Spark'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 Spark'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?)
Code is shorter and the indentation reduce readability.
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 Spark
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
title1
text
title2
text2
let Feature = props=>
{props.title}
{props.children}
;
text1
text2
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
;
return ;
return
;
return
;
return (
);
return (
);
return (
{show_panel &&
}
)
);
return (
{show_panel &&
}
);
CSS
Unittest
TBD
', $('')
.append(''));
var s = ''
+'';
s = x ? '' : ''
+'';
s = ''
+'';
return ''
+'';
.
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));
$('', $('').
append(''));
$('', $('')
.append(''));
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
for (let i=0; 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
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
When you (or someone else) find a coding mistake in your code:
BAD: just fixing that specific mistake you found
OK: fixing also all such mistakes you made in your own
code
GOOD: fixing all such mistakes, in the whole codebase, using
smart rgrep's to find them.
GREAT: if this is a common mistake people tend to repeat, then
once a month repeat the search to find such NEW mistakes.
EXCELLENT: add a rule to zlint to auto detect and if possible
auto fix the mistake.
Spark specific API
Disable feature
dynamic using reconf: preferred
env: usually when needed in init, for example port numbers
if (0): unittests and non production code
TBA: code examples
etask
Overview
etask
is Spark'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 Spark'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?)
Code is shorter and the indentation reduce readability.
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 Spark
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
title1
text
title2
text2
let Feature = props=>
{props.title}
{props.children}
;
text1
text2
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
;
return ;
return
;
return
;
return (
);
return (
);
return (
{show_panel &&
}
)
);
return (
{show_panel &&
}
);
CSS
Unittest
TBD
.
of .method()
.
of .method()
MUST be the first
character on a method call continuation line.
', $('').
append(''));
$('', $('')
.append(''));
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
for (let i=0; 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
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
When you (or someone else) find a coding mistake in your code:
BAD: just fixing that specific mistake you found
OK: fixing also all such mistakes you made in your own
code
GOOD: fixing all such mistakes, in the whole codebase, using
smart rgrep's to find them.
GREAT: if this is a common mistake people tend to repeat, then
once a month repeat the search to find such NEW mistakes.
EXCELLENT: add a rule to zlint to auto detect and if possible
auto fix the mistake.
Spark specific API
Disable feature
dynamic using reconf: preferred
env: usually when needed in init, for example port numbers
if (0): unittests and non production code
TBA: code examples
etask
Overview
etask
is Spark'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 Spark'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?)
Code is shorter and the indentation reduce readability.
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 Spark
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
title1
text
title2
text2
let Feature = props=>
{props.title}
{props.children}
;
text1
text2
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
;
return ;
return
;
return
;
return (
);
return (
);
return (
{show_panel &&
}
)
);
return (
{show_panel &&
}
);
CSS
Unittest
TBD
', $('')
.append(''));
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
for (let i=0; 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
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
When you (or someone else) find a coding mistake in your code:
BAD: just fixing that specific mistake you found
OK: fixing also all such mistakes you made in your own
code
GOOD: fixing all such mistakes, in the whole codebase, using
smart rgrep's to find them.
GREAT: if this is a common mistake people tend to repeat, then
once a month repeat the search to find such NEW mistakes.
EXCELLENT: add a rule to zlint to auto detect and if possible
auto fix the mistake.
Spark specific API
Disable feature
dynamic using reconf: preferred
env: usually when needed in init, for example port numbers
if (0): unittests and non production code
TBA: code examples
etask
Overview
etask
is Spark'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 Spark'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?)
Code is shorter and the indentation reduce readability.
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 Spark
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
title1
text
title2
text2
let Feature = props=>
{props.title}
{props.children}
;
text1
text2
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
;
return ;
return
;
return
;
return (
);
return (
);
return (
{show_panel &&
}
)
);
return (
{show_panel &&
}
);
CSS
Unittest
TBD
Variable declarations
var
declarations longer than one line must have their own
var
.
Comparing variables
When comparing to undefined
use ===
and
!==
On any other case, use ==
and !=
Prefer .bind()
over this
When assigning functions that depend on this
,
use bind()
.
Shorten using locality
Use locality to shorten names, relying on the contexts of the local code scope.
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.
Positive naming
Default to using positive naming, rather than negative (no/not/disable...). This helps avoid double negation (not not).
Simplicity and usability
Command line options, or option variable (opt), use common usage as default even if negative
Default value
Using implicit undefined
as default value
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.
Saving the value of this
When saving the value of this
for use in lexically nested
functions, use _this
as the variable name.
Use __this
and ___this
... for deep code.
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
ES6
Arrow functions
No spaces around =>
.
Prefer to drop ()
Drop ()
around single argument.
Prefer to drop {}
around single short statement.
Preferred Methods
never use .indexOf()
for arrays/strings when
.includes()
fits.
never user .indexOf()
for strings when
.startsWith()
fits.
Generators
No spaces around *
.
Class definition
Class name start with capital leter
Add space between class name and {
Etask methods
Indentation reducing is allowed, but class methods should be indented
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.
Early return
Avoid if()
on 50% or more of a function.
No defensive code
No function argument validation
Assigning in a truth value (if or while)
Assigning truth value in if
while
for
helps shorten and simplify code.
Temporary disabling a test
When temporary disabling test code that fail:
Do not indent the code of
the disabled tests.
If it is only one test (one statement), then don't use { }
even if the statement is written in 2 lines:
Open '{' on the same if() line:
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
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.
Deep Fix
Spark specific API
Disable feature
TBA: code examples
etask
Overview
etask
is Spark'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:
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 Spark's etask
s and ES6 generators.
etask ES6 (perfect!):
Compare this with other possible approaches:
callbacks (callback-hell...):
promise (includes ugly recursion to emulate a loop, nested
then
, and obscure execution flow):
async function
(no support for cancelation if the parent
function exits early):
etask ES5 (when generators are not available):
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:
Call a callback driven function:
Wait on an event emitter:
Scheduled resource cleanup (like Go's defer
statement):
Coding
When possible, use ES6 arrow function with no brackets and no
return
return etask()
in the middle of a function should be
indented to the function level. Should be used rarely, only when fast
path needed
etask class indentation
No hidden (automatic) yield
in return.
etask name for lib API
No etask name for internal functions
Avoid enclosing a large portion of a function in a try block.
Prefer this.on('uncaught', ...) and this.finally when applicable.
(why?)
React code
Read React docs and follow guidlines and recommendations unless they conflict with Spark 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
title1
text
title2
text2
{props.title}
{props.children}
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.
CSS
Unittest
TBD