KENSHO
> 200 public NPM modules
> 300 GitHub repos
Node / Angular / QUnit / Mocha / ...
> 300 blog posts
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate, orci non porta dignissim, ipsum neque hendrerit neque, in molestie metus dolor quis erat. Sed semper sit function orci sed sagittis. Nulla ut ultricies arcu, eu rutrum nisi. In hac habitasse platea dictumst. Ut vel add (tellus. Ut a, suscipit b) diam interdum tortor tincidunt, { eu consectetur leo efficitur. Proin return a + b; tortor nisi, dignissim eu porta non, auctor in nisi. Cras suscipit augue nec felis ornare, non } elementum dolor blandit. Vestibulum auctor commodo nunc, id interdum ex auctor eu. Vivamus gravida interdum viverra. Nunc porta sodales ipsum, a tempus leo placerat vel. Quisque ut massa id elit facilisis porttitor at efficitur velit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vulputate, orci non porta dignissim, ipsum neque hendrerit neque, in molestie metus dolor quis erat. Sed semper sit function orci sed sagittis. Nulla ut ultricies arcu, eu rutrum nisi. In hac habitasse platea dictumst. Ut vel add (tellus. Ut a, suscipit b) diam interdum tortor tincidunt, { eu consectetur leo efficitur. Proin return a + b; tortor nisi, dignissim eu porta non, auctor in nisi. Cras suscipit augue nec felis ornare, non } elementum dolor blandit. Vestibulum auctor commodo nunc, id interdum ex auctor eu. Vivamus gravida interdum viverra. Nunc porta sodales ipsum, a tempus leo placerat vel. Quisque ut massa id elit facilisis porttitor at efficitur velit.
var add = function (a, b) {
return a + b;
};
4 entities: a, b, add and a function
Each entity can interact with other 3
Kensho Node
var add = function (a, b) {
return a + b;
};
for N entities: N * (N - 1) interactions
Kensho Node
Kensho Node
function add(a, b) {
return a + b;
}
Removed unnecessary variable add
Kensho Node
Kensho Node
Kensho Node
var path = require('path');
var first = path.join(__dirname, '../foo');
...
var second = path.join(__dirname, '../bar');
Kensho Node
var join = require('path').join;
var first = join(__dirname, '../foo');
...
var second = join(__dirname, '../bar');
Kensho Node
var relativePath = require('path')
.join.bind(null, __dirname);
var first = relativePath('../foo');
...
var second = relativePath('../bar');
Kensho Node
var first = path.join(__dirname, '../foo');
// vs
var second = relativePath('../bar');
Kensho Node
var relativePath = path.join.bind(null, __dirname);
relativePath('foo.js');
Kensho JavaScript
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
newFn(valueC);
Kensho JavaScript
Place on the left arguments that are likely to be known first
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
newFn(valueC);
Kensho JavaScript
name and argument order
function updateUserInfo(userId, newInfo){ ... }
// vs
function updateUserInfo(newInfo, userId){ ... }
Kensho JavaScript
// user-service.js
function updateUserInfo(userId, newInfo) { ... }
// user-controller.js
var updateUser;
function onLogin(id) {
updateUser = updateUserInfo.bind(null, id);
}
Kensho JavaScript
$('form').on('submit', function onSubmitted(form) {
updateUser(form);
});
function fn(a, b, c) { ... }
var newFn = fn.bind(null, valueA, valueB);
Kensho Lodash and Ramda
// or
var _ = require('lodash');
var newFn = _.partial(fn, valueA, valueB);
// or
var R = require('ramda');
var newFn = R.partial(fn, valueA, valueB);
['1', '2', '3'].map(parseInt); // [1, NaN, NaN]
// function parseInt(x, radix)
// but Array.map sends (value, index, array)
['1', '2', '3'].map(function (x) {
return parseInt(x, 10);
});
const base10 = _.partialRight(parseInt, 10)
['1', '2', '3'].map(base10);
// [1, 2, 3]
// radix is bound,
// index and array arguments are ignored
Kensho Lodash and Ramda
// function parseInt(x, radix)
const base10 = _.partialRight(parseInt, 10)
['1', '2', '3'].map(base10);
// [1, 2, 3]
// or
var S = require('spots');
const base10 = S(parseInt, S, 10)
['1', '2', '3'].map(base10);
// [1, 2, 3]
Kensho Lodash and Ramda
// Express
// same callbacks to check if the user is logged in and authorized
app.get('/repos',
passport.isAuthenticated, passport.isAuthorized, ctrl.getRepos);
app.get('/repos/:user/:name',
passport.isAuthenticated, passport.isAuthorized, ctrl.getRepo);
app.get('/repos/view/:user/:name',
passport.isAuthenticated, passport.isAuthorized, ctrl.viewFile);
app.get(<url>, passport.isAuthenticated, passport.isAuthorized, ...);
Kensho Lodash and Ramda
// prefill 2 middle arguments using spots
var S = require('spots');
var authGet = S(app.get, S,
passport.isAuthenticated, passport.isAuthorized)
.bind(app);
// authGet(<url>, <controller>)
authGet('/repos', ctrl.getRepos);
authGet('/repos/:user/:name', ctrl.getRepo);
authGet('/repos/view/:user/:name', ctrl.viewFile);
Kensho Lodash and Ramda
// divide by 10
function divide(a, b) { return a / b; }
var selective = require('heroin');
var by10 = selective(divide, { b: 10 });
console.log(by10(10)); // 1 (a = 10, b = 10)
console.log(by10(2)); // 0.2 (a = 2, b = 10)
Kensho Lodash and Ramda
function fn(options) { ... }
var obind = require('obind');
var withBar = obind(fn, { bar: 'bar' });
withBar({ baz: 'baz' });
/*
equivalent to
foo({
bar: 'bar',
baz: 'baz'
})
*/
Kensho Lodash and Ramda
Kensho Node
var numbers = [3, 1, 7];
var constant = 2;
var k = 0;
for(k = 0; k < numbers.length; k += 1) {
console.log(numbers[k] * constant);
}
// 6 2 14
Kensho Node
function mul(a, b) {
return a * b;
}
function print(n) {
console.log(n);
}
numbers.map(function (n) {
return mul(n, constant);
}).forEach(print);
// 6 2 14
Kensho Node
still boilerplate
function mul(a, b) {
return a * b;
}
var byK = mul.bind(null, constant);
var print = console.log.bind(console);
Kensho Node
byK and print expect 1 argument
function mul(a, b) {
return a * b;
}
var byK = mul.bind(null, constant);
var print = console.log.bind(console);
numbers
.map(byK)
.forEach(print);
Kensho Node
pointfree style
function mul(a, b) {
return a * b;
}
var byK = mul.bind(null, constant);
var print = console.log.bind(console);
numbers
.map(byK)
.forEach(print);
Kensho Node
pointfree style
function add (a, b) { }
var result = add (...)
Kensho Unary functions
add
fn1
fn2
always 1 output
function add (a, b) { }
var add10 = add.bind(null, 10)
var result = add10 (3)
Kensho Unary functions
add10
fn1
fn2
1 output
1 input
Kensho Unary functions
Kensho Unary functions
var mul = _.curry(function (a, b) {
return a * b;
});
var byK = mul(constant);
function mul(a, b) {
return a * b;
}
var byK = _.partial(mul, constant);
Curried functions have partial application "built in"
Kensho Curry
var mul = _.curry(function (a, b, c) {
return a * b * c;
});
mul(2)(3)(10) // 60
Curried functions make any function unary
Kensho Curry
function cb(item, index, array) { ... }
// ES5 method
Array.prototype.map(cb);
// Lodash method
_.map(array, cb);
Place on the left arguments that are likely to be known first
Kensho API design
var R = require('ramda');
// callback is first argument
R.map(cb, array);
// all functions are curried
var by5 = R.multiply(5);
by5(10); // 50
Kensho API design
var R = require('ramda')
R.map(R.multiply(5), array)
Kensho API design
// same as
R.map(R.multiply(5))(array)
var printAll = R.forEach(print);
printAll(multiplyAll(numbers));
Kensho API design
var numbers = [3, 1, 7];
var constant = 2;
var R = require('ramda');
var multiplyAll = R.map(R.multiply(constant));
var print = R.bind(console.log, console);
var multiplyAll = R.map(R.multiply(constant));
var printAll = R.forEach(print);
printAll(multiplyAll(numbers));
Kensho FP
var multiplyAll = R.map(R.multiply(constant));
var printAll = R.forEach(print);
var computation = R.compose(printAll, multiplyAll);
computation (numbers);
Static logic
Dynamic data
Kensho FP
var mulPrint = R.pipe(
R.map(R.multiply(constant)),
R.tap(debugLog),
R.forEach(R.bind(console.log, console))
)
mulPrint (numbers)
R.pipe for readability
R.tap for debugging
Kensho FP
var glob = require('glob');
function getJavaScriptFiles(cb) {
glob('*.js', function (err, files) {
if (err) {
return cb(err);
}
cb(null, files);
});
}
getJavaScriptFiles(function (err, files) {
if (err) {
return console.error(err);
}
console.log(files);
});
Kensho Promises
var getJavaScriptFiles = require('q')
.denodeify(require('glob'))
.bind(null, '*.js');
getJavaScriptFiles()
.then(console.log)
.catch(console.error)
.done();
Kensho Promises
No more error handling or callback boilerplate
var Q = require('q');
function asyncF() {
var defer = Q.defer();
process.nextTick(function () {
defer.resolve('f');
});
return defer.promise;
}
Q('f')
// or
Promise.resolve('f')
Kensho Promises
promise
.then(function (x) {
return new Promise(function (resolve) {
resolve(x + 1)
})
})
Kensho Promises
promise
.then(function (x) {
return x + 1
})
// or
promise.then(R.inc)
Kensho Promises
promise
...
.then(function (x) {
console.log(x)
return x
})
promise
...
.then(R.tap(console.log))
Q(...)
.then(R.inc)
.then(R.tap(console.log))
.then(verify)
.done()
var printThenVerify = R.pipeP(
R.inc,
R.tap(console.log),
verify
)
Q(...)
.then(printThenVerify)
.done()
Kensho Promises
fs.readdirAsync('.')
.map(processFile, { concurrency: 3 })
.then(fileResults);
Kensho Promises
Kensho RxJS
QUnit.test('a test', function(assert) {
var done = assert.async();
asyncOperation()
.then(function () {
// assert something
done();
});
});
Kensho testing
// Mocha
it('works', function (done) {
asyncOperation()
.then(function () {
// assert something
done();
});
});
Kensho testing
// Mocha
it('works', function () {
return asyncOperation()
.then(function () {
// assert something
});
});
Kensho testing
// typical AngularJS unit test
describe('typical test', function () {
var $rootScope, foo;
beforeEach(function () {
angular.mock.module('A');
// other modules
});
beforeEach(inject(function (_$rootScope_, _foo_) {
$rootScope = _$rootScope_;
foo = _foo_;
}));
it('finally a test', function () {
$rootScope.$apply(); // for example
expect(foo).toEqual('bar');
});
});
Kensho OSS
ngDescribe({
modules: 'A',
inject: 'foo',
tests: function (deps) {
it('finally a test', function () {
deps.step()
expect(deps.foo).toEqual('bar')
})
})
})
Kensho OSS
it('does something', function () {
...
expect(foo).toEqual('bar');
});
test "does something" failed
Error:
What has actually failed and why?
Kensho defensive code
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo to equal "bar"');
});
test "does something" failed
Error: expected foo to equal "bar"
Why did it fail?
Kensho defensive code
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo ' + foo + ' to equal "bar"');
});
test "does something" failed
Error: expected foo something to equal "bar"
Message repeats the predicate!
Kensho defensive code
it('does something', function () {
...
expect(foo).toEqual('bar',
'expected foo ' + JSON.stringify(foo) +
' to equal "bar"');
});
Kensho defensive code
// require('lazy-ass');
it('does something', function () {
la(foo === 'bar',
'expected foo', foo, 'to equal "bar"');
});
Kensho defensive code
var helpfulDescribe = require('lazy-ass-helpful');
helpfulDescribe(function tests() {
it('does something', function () {
la(foo === 'bar');
});
});
Kensho defensive code
var helpfulDescribe = require('lazy-ass-helpful');
helpfulDescribe(function tests() {
it('does something', function () {
la(foo === 'bar',
'condition [foo === "bar"], foo is', foo);
});
});
Kensho defensive code
lazy-ass-helpful rewrites code using falafel
Kensho defensive code
Small performance penalty is worth it for shorter unit tests
Tests taking too long? Your project is too large.
// ES5: need all arguments after "y"
function f(x, y) {
var a = Array.prototype.slice.call(arguments, 2);
...
};
// using lodash
function f(x, y) {
// need all arguments after "y"
var a = _.toArray(arguments).slice(2);
...
};
Kensho ES6
// ES5: need all arguments after "y"
function f(x, y) {
var a = Array.prototype.slice.call(arguments, 2);
...
};
// using ES6 Array.from
function f(x, y) {
// need all arguments after "y"
var a = Array.from(arguments).slice(2);
...
};
// using ES6 rest parameter
function f(x, y, ...a) {
// a is an array of arguments after "y"
...
};
Kensho ES6
function f (x, y, z) {
if (y === undefined)
y = 7;
if (z === undefined)
z = 42;
return x + y + z;
};
// es6
function f (x, y = 7, z = 42) {
return x + y + z
}
Kensho ES6
var foo = '...';
var o = {};
o[foo] = 42;
// es6
var foo = '...';
var o = {
[foo]: 42
}
Kensho ES6
numbers.map(function (v) { return v + 1; });
// es6
numbers.map(v => v + 1);
// I prefer
var R = require('ramda');
var add1 = R.add(1);
numbers.map(add1);
Kensho ES6
Kensho Node
KENSHO
1PM in room "Deborah Sampson" - Securing the front end, from a Node server