To Err Is Human
Transcript of To Err Is Human
-
To Err Is HumanAlex Liu @stinkydofu [email protected]
-
avoidance acceptance
-
avoidance acceptance
-
The Road To Happiness
Handling Errors and Exceptions Preserving History The Problem With Catch
-
Handling Errors and Exceptions
1
-
Error
> new Error();
-
kinds of errorstwo
-
Expected
-
Expected
-
Expected
invalid user input bad JSON input file not found request timeout request 500
-
Unexpected
-
Unexpected
-
Unexpected
programmer typos read property of undefined
undefined is not a function
-
How do I communicate expected errors?
-
Exception
> throw new Error();
-
an exception is a
thrown error
-
You could throw anything
> throw { foo: bar }; > throw 0; > throw 'my error';
-
> throw 0; undefined > throw 'something happened'; something happened > throw new Error('something happened'); Error: something happened at repl:1:7 at REPLServer.defaultEval (repl.js:132:27) at bound (domain.js:254:14) at REPLServer.runBound [as eval] (domain.js:267:12) at REPLServer. (repl.js:279:12) at REPLServer.emit (events.js:107:17) at REPLServer.Interface._onLine (readline.js:214:10) at REPLServer.Interface._line (readline.js:553:8) at REPLServer.Interface._ttyWrite (readline.js:830:14)
-
index.js
-
index.js data.js get.js
-
index.js data.js get.js
throw!
-
index.js data.js get.js
throw!catch?
-
index.js data.js get.js
throw!catch?
-
index.js data.js get.js
throw!catch?
-
catch (e) {// run!
}
index.js data.js get.js
throw!
-
CRASH! throw!
index.js data.js get.js
-
throw / try / catch
try { var price = getStockPrice('NFLX'); document.write('NFLX: ' + price); } catch(err) { // err instanceof Error => true document.write('NFLX: unavailable'); }
-
Can we catch?
try { getStockPrice('NFLX', function(err) { if (err) { throw (err); } /* continue as normal */ }); } catch(err) { /* error handling */ }
-
Error first callbacks (errbacks)
getStockPrice('NFLX', function(err, price) { if (err) { /* handle err! */ } document.write('NFLX: ' + price); });
-
Error first callbacks (errbacks)
getStockPrice('NFLX', function(err, price) { if (err) { /* handle err! */ } document.write('NFLX: ' + price); });
-
Error first callbacks (errbacks)
getStockPrice('NFLX', function(err, price) { if (err) { document.write('NFLX: unavailable'); return; } document.write('NFLX: ' + price); });
-
Bubble up if you cant handle it
function drawStockPrice(callback) { getStockPrice('NFLX', function(err, price) { return callback(err); }); }
-
index.js
-
index.js data.js get.js
cb(err)
-
index.js data.js get.js
cb(err) cb(err)
-
if (err) {...
}
index.js data.js get.js
cb(err) cb(err)
-
SWALLOWED! cb(err)cb(err)
index.js data.js get.js
-
errbacks are a
channelfor expected errors
-
> throw err;
> callback(err);
sync
async
-
nopenope
nopenope
nopenope
nopenope
nope
nopenope
nope
nope
nopenope
nope
nope
-
throw / try / catchwhen in async env
sanelyyou cant
-
favor errbacks because of
consistency
-
How do I communicate unexpected errors?
-
unexpected errors are thrown
automatically
-
process.on('uncaughtException', function(err) { ... });
Catch em all, even async
-
process.on('uncaughtException', function(err) { ... });
Catch em all, even async
window.onerror = function(err) { ... });
-
process.on('uncaughtException', function(err) { ... });
Catch em all, even async
window.onerror = function(err) { ... });_BAD
IDEA_
-
Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } }
-
Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } }
-
Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } }
-
Can you recover? function dispatch(doThings) { if (!Array.isArray(doThings)) { throw new TypeError('not an array'); } for (var i = 0; i < doThings.length; i++) { if (typeof doThings[i] !== 'function') { throw new TypeError('not a function'); } } for (var i = 0; i < doThings.length; i++) { doThings[i](); } }
-
Catch and do what?
for (var i = 0; i < doThings.length; i++) { try { doThings[i](); } catch (e) { ... } }
-
Catch and do what?
for (var i = 0; i < doThings.length; i++) { try { doThings[i](); } catch (e) { ... } }
-
programmer err : rollback ::
-
programmer err : rollback ::
rollback err : ?
-
you cant fix unexpected errors because their existence is
unexpected
-
in node.js,
aborton unexpected errors
-
goal is to the blast radius
(not to write bug free code)
minimize
-
continuing after an
unhandled exception is a
trade off
-
_BROKEN_
-
program error : program crash ::
-
program error : program crash ::
OS error : ?
-
program error : program crash ::
OS error : OS crash
-
in the browser,
reportall unexpected errors
-
avoidhandling errors in global exception handlers
-
Preserving History 2
-
Working backward
function getEpicStory(cb) { database.get('iliad', function(err, data) { if (err) { return cb(err); } return cb(null, data); }); }
-
We can do better
database.get('iliad', function(err, data) { if (err) { err.cause = 'db error!'; err.timestamp = new Date(); return cb(err); } return cb(null, data); });
-
VError: we can do even better
database.get('iliad', function(err, data) { if (err) { return cb(new VError(err, 'db error!')); } return cb(null, data); });
-
VError: we can do even better
// redis error [Error: missing key] // our verror { [VError: db error: missing key] jse_shortmsg: 'db error', jse_summary: 'db error: missing key', jse_cause: [Error: missing key], message: 'db error: missing key' }
-
[2015-10-02T06:41:54.389Z] ERROR: test/11497 on lgml-aliu: db error: missing key VError: db error: missing key at Object. (/Users/aliu/Desktop/test/a.js:6:12) at Module._compile (module.js:434:26) at Object.Module._extensions..js (module.js:452:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:475:10) at startup (node.js:117:18) at node.js:951:3 Caused by: Error: missing key at Object. (/Users/aliu/Desktop/test/a.js:5:12) at Module._compile (module.js:434:26) at Object.Module._extensions..js (module.js:452:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:475:10) at startup (node.js:117:18) at node.js:951:3
-
restify-errors: the bees knees
var errs = require('restify-errors');
errs.makeConstructor('DatabaseError', { fooProp: 'someVal' });
var myErr = new errs.DatabaseError('db error!'); // myErr.fooProp => someVal
-
restify-errors: the bees knees
var myErr = new errs.DatabaseError('db error!'); myErr instanceof errs.DatabaseError // => true myErr instanceof VError // => true myErr instanceof Error // => true
-
Serving out the UI
request('/api/story', function(err, fpStoryData) { if (err instanceof HttpError) { return res.render('NetworkErrorPage'); } else if (err instanceof DatabaseError) { return res.render('UnavailablePage'); } else { return res.render('ErrorPage'); } res.render('iliad', fpStoryData); });
-
[2015-10-06T17:50:31.561Z] ERROR: test/64987 on lgml-aliu: rendering error! RenderError: rendering error page! at Object. (/Users/aliu/Desktop/test/test4.js:10:10) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 Caused by: DataValidationError: data validation error at Object. (/Users/aliu/Desktop/test/test4.js:10:32) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 Caused by: RequestError: request error at Object. (/Users/aliu/Desktop/test/test4.js:10:63) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3 Caused by: BadRequestError: http 400 at Object. (/Users/aliu/Desktop/test/test4.js:10:86) at Module._compile (module.js:460:26) at Object.Module._extensions..js (module.js:478:10)
-
[2015-10-06] ERROR: test/64987 on lgml-aliu: rendering error!
Error: something happened at repl:1:7 at REPLServer.defaultEval (repl.js:132:27) at bound (domain.js:254:14) at REPLServer.runBound [as eval] (domain.js:267:12) at REPLServer. (repl.js:279:12) at REPLServer.emit (events.js:107:17) at REPLServer.Interface._onLine (readline.js:214:10) at REPLServer.Interface._line (readline.js:553:8) at REPLServer.Interface._ttyWrite (readline.js:830:14) at Module.load (module.js:355:32) at Function.Module._load (module.js:310:12) at Function.Module.runMain (module.js:501:10) at startup (node.js:129:16) at node.js:814:3
-
use
for expected errors
typed errors
-
3The problem with catch
-
But {abstraction} solved this already!?
-
try: catch em all
try { if (Math.random() > 0.5) { JSON.parse(input); } else { var x = y + 1; } } catch (err) {...}
-
> [SyntaxError: Unexpected token b]
> [ReferenceError: y is not defined]
expected:
unexpected:
JSON.parse(input);
var x = y + 1;
-
Catching typed errors
catch (err) { if (err instanceof SyntaxError) { ... } }
-
Dont forget to rethrow!
catch (err) { if (err instanceof SyntaxError) { ... } else { throw err; } }
-
Centralize throwing
catch (err) { throwUnexpected(err); // handle err if not thrown }
-
expected vs unexpected?
catch (err) { if (err instanceof SyntaxError) { ... } else { throw err; } }
-
expected vs unexpected?
> JSON.parse('bad input');
> vart x;
-
try/catch cannot
differentiatesource of errors
-
try: reduce surface area
try { JSON.parse(input); } catch (err) { ... }
var x = y + 1;
-
be very
targetedwith try / catch
-
promises: catch em all
foo.then(foo2) .then(foo3) .catch(function(err) { ... });
-
promises: unexpected behavior
foo.then(foo2) .then(fooError) .catch(function(err) { if (!(err instanceof ExpectedErr)) { throw err; } });
-
$ node promiseDemo.js Unhandled rejection ReferenceError: y is not defined at foo (/test/promiseDemo.js:14:13) at /test/promiseDemo.js:8:3 at processImmediate [as _immediateCallback] (timers.js:368:17) From previous event: at Object. (/test/promiseDemo.js:7:3) at Module._compile (module.js:435:26) at Object.Module._extensions..js (module.js:442:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:311:12) at Function.Module.runMain (module.js:467:10) at startup (node.js:134:18) at node.js:961:3
-
$
-
$ echo $?
-
$ echo $?0
-
promises: unexpected behavior
foo.then(foo2) .then(fooError) .catch(function(err) { if (!(err instanceof ExpectedErr)) { throw err; } });
-
promises: call done()
foo.then(foo2) .then(fooError) .catch(function(err) { if (!(err instanceof ExpectedErr)) { throw err; } }) .done();
-
bluebird: catch only expected
foo.then(foo2) .then(fooError) .catch(ExpectedError, function(err) { ... }); // EVERYTHING else throws, exits 1!
-
bluebird: throw by default
Promise.onPossiblyUnhandledRejection(function(e) { throw e; });
-
surfacing unexpected errors should NOT be
opt-in
-
Making Catch Better
You don't need to sell me -- we had } catch (e if ...) { in SpiderMonkey and proposed it for ES3 (in 1998).
- Brendan Eich
try { ... } catch (e if ...) { ... }
-
Refutable Pattern Matching
GuardedPattern(refutable) ::= Pattern(refutable) "if" AssignmentExpression
-
Looking Forward
-
for rich error objects
5 npm install verror npm install restify-errors
-
Handle every error from every async API
4
-
Understand how your abstractions handle errors
3
(swallow? rethrow?)
-
Abort immediately on unexpected errors to minimize the blast radius
2
-
Design and build applications with error handling in mind
1
(exceptions are NOT exceptional!)
-
Error handling bible https://www.joyent.com/developers/node/
design/errors
https://www.joyent.com/developers/node/design/errors
-
Aborting on unexpected errors https://github.com/nodejs/node-v0.x-archive/
issues/5114 https://github.com/nodejs/node-v0.x-archive/
issues/5149
https://github.com/nodejs/node-v0.x-archive/issues/5114https://github.com/nodejs/node-v0.x-archive/issues/5149
-
Module up! verror
https://github.com/davepacheco/node-verror restify-errors
https://github.com/restify/errors
https://github.com/davepacheco/node-verrorhttps://github.com/restify/errors
-
Get involved! https://esdiscuss.org/topic/try-catch-conditional-
exceptions-in-light-of-generators http://wiki.ecmascript.org/doku.php?
id=strawman:pattern_matching
https://esdiscuss.org/topic/try-catch-conditional-exceptions-in-light-of-generatorshttp://wiki.ecmascript.org/doku.php?id=strawman:pattern_matching
-
FinAlex Liu @stinkydofu [email protected]
-
Image Credits
-
Image Credits
-
Image Credits
-
Image Credits http://lizclimo.tumblr.com/post/77531229510/youre-doing-it-wrong http://fantasio.deviantart.com/art/Godzilla-in-the-mountains-454304871 http://rockpapercynic.tumblr.com/post/85581052929/first-revealed-pagew-your-bad-
idea-illustration
http://lizclimo.tumblr.com/post/77531229510/youre-doing-it-wronghttp://fantasio.deviantart.com/art/Godzilla-in-the-mountains-454304871http://rockpapercynic.tumblr.com/post/85581052929/first-revealed-pagew-your-bad-idea-illustration