Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Writing Maintainable JavaScript

Writing Maintainable JavaScript

The second incarnation of 'Ask Not What JavaScript Can Do For You'. I gave this talk at Yet Another Conference in Moscow on October 2nd 2013.

Jon Bretman

October 08, 2013
Tweet

More Decks by Jon Bretman

Other Decks in Programming

Transcript

  1. WRITING
    MAINTAINABLE
    JAVASCRIPT
    Yet Another Conference 2013

    @jonbretman
    jonbretman.co.uk

    Mobile Web Developer @ Badoo
    http://techblog.badoo.com
    @BadooTech

    http://habrahabr.ru/company/badoo
    @BadooDev 1  

    View full-size slide

  2. !
    !
    !
    My Awesome App!
    !
    !
    !
    !
     
    3  

    View full-size slide

  3. 4  
    What is
    maintainability?

    View full-size slide

  4. 5  
    In engineering, maintainability is the ease with
    which a product can be maintained in order
    to...
    h(p://en.wikipedia.org/wiki/Maintainability  

    View full-size slide

  5. 6  
    In engineering, maintainability is the ease with
    which a product can be maintained in order
    to...
    isolate defects or their cause
    h(p://en.wikipedia.org/wiki/Maintainability  

    View full-size slide

  6. 7  
    In engineering, maintainability is the ease with
    which a product can be maintained in order
    to...
    correct defects or their cause
    h(p://en.wikipedia.org/wiki/Maintainability  

    View full-size slide

  7. 8  
    In engineering, maintainability is the ease with
    which a product can be maintained in order
    to...
    prevent unexpected breakdowns
    h(p://en.wikipedia.org/wiki/Maintainability  

    View full-size slide

  8. 9  
    In engineering, maintainability is the ease with
    which a product can be maintained in order
    to...
    make future maintenance easier
    h(p://en.wikipedia.org/wiki/Maintainability  

    View full-size slide

  9. 10  
    It's about making
    our lives easier

    View full-size slide

  10. 11  
    It's about making
    our work pass the
    test of time

    View full-size slide

  11. 12  
    for(B=i=y=u=b=i=5-5,x=10,I=[],l=[];B++<304;I[B-1]=B%x?B/x%x<2|B%x<2?7:B/x&4?!
    0:l[i++]="ECDFBDCEAAAAAAAAIIIIIIIIMKLNJLKM@G@TSb~?A6J57IKJT576,+-48HLSUmgukgg " +!
    "OJNMLK IDHGFE".charCodeAt(y++)-64:7);function X(c,h,e,s){c^=8;for(var o,!
    S,C,A,R,T,G,d=e&&X(c,0)>1e4,n,N=-1e8,O=20,K=78-h<<9;++O<99;)if((o=I[T=O])&&!
    (G=o^c)<7){A=G--&2?8:4;C=o-9?l[61+G]:49;do if(!(R=I[T+=l[C]])&&!!G|A<3||!
    (R+1^c)>9&&G|A>2){if(!(R-2&7))return K;n=G|(c?T>29:T<91)?o:6^c;S=!
    (R&&l[R&7|32]*2-h-G)+(n-o?110:!G&&(A<2)+1);if(e>h||12|d)!
    {I[T]=n;I[O]=0;S-=X(c,h+1,e,S-N);if(!(h||e-1|B-O|T-b|S<-1e4))return W(),!
    c&&setTimeout("X(8,0,2),X(8,0,1)",75);I[O]=o;I[T]=R}if(S>N||!h&S==N&&!
    Math.random()<.5)if(N=S,e>1)if(h?s-S<0:(B=O,b=T,0))break}while(!R&G>2||(T=O,!
    (G||A>2|(c?O>78:O<41)&!R)&&++C*--A))}return-K+768i="";for(u=18;u<99;document.body.innerHTML=i+=++u%x-9?!
    "+(u-B?u*.9&1||9:"d")+"0f0e0>"+(I[u]?9808+l[67+I[u]]:160):u++&&"")B=b}W()  
    h(p://js1k.com/2010-­‐first/demo/750  

    View full-size slide

  12. 14  
    •  Team of 4 developers

    View full-size slide

  13. 15  
    •  Team of 4 developers
    •  JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver

    View full-size slide

  14. 16  
    •  Team of 4 developers
    •  JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver
    •  60,000+ lines of JavaScript

    View full-size slide

  15. 17  
    •  Team of 4 developers
    •  JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver
    •  60,000+ lines of JavaScript
    •  ~500,000 daily active users

    View full-size slide

  16. 18  
    •  Team of 4 developers
    •  JavaScript, jsDoc, JSHint, Closure Compiler, JsTestDriver
    •  60,000+ lines of JavaScript
    •  ~500,000 daily active users
    •  Code maintainability is key!

    View full-size slide

  17. var topics = [!
    'Type Checking',!
    'Classes and Inheritance’,!
    'Asynchronous Code',!
    'Performance'!
    ];!
     
    19  

    View full-size slide

  18. topics.shift();!
    "Type Checking"!
     
    20  

    View full-size slide

  19. !
    !
    !
    My Awesome App!
    !
    !
    !
    !
     
    21  

    View full-size slide

  20. Api.get('/conversations', function (conversations) {!
    !
    var intros = conversations.map(function (c) {!
    var name = c.theirName;!
    var mostRecent = c.messages[0].text.substring(0, 30);!
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });!
    22  

    View full-size slide

  21. Api.get('/conversations', function (conversations) {!
    !
    var intros = conversations.map(function (c) {!
    var name = c.theirName;!
    var mostRecent = c.messages[0].text.substring(0, 30);!
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });!
    23  

    View full-size slide

  22. Api.get('/conversations', function (err, conversations) {!
    !
    var intros = conversations.map(function (c) {!
    var name = c.theirName;!
    var mostRecent = c.messages[0].text.substring(0, 30);!
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });!
    A lot of things have to go right here
    24  

    View full-size slide

  23. 25  
    Exceptions

    View full-size slide

  24. if (data.value) {!
    callback(data.value);!
    }!
    !
    !
    TypeError: Cannot read property 'value' of null!
    !
    !
    !
    !
     
     
     
    26  

    View full-size slide

  25. if (data && data.callback) {!
    var result = data.callback();!
    }!
    !
    !
    TypeError: Property 'callback' of object #
    is not a function  
     
    27  

    View full-size slide

  26. if (data && data.value) {!
    var index = data.value.indexOf('something');!
    }!
    !
    !
    TypeError: Object # has no method ‘indexOf’
    !
    !
    !
    !
    !
     
     
    28  

    View full-size slide

  27. typeof {};!
    "object"!
    !
    typeof 'hello';!
    "string"!
    !
    typeof 5;!
    "number”!
    typeof function () {};!
    "function"!
    !
    typeof undefined;!
    "undefined"!
    !
    typeof true;!
    "boolean"!
    !
    29  

    View full-size slide

  28. typeof [];!
    "object"!
    !
    typeof null;!
    "object"!
    !
    typeof new Date();!
    "object"!
    typeof /jsconf/;!
    "object"!
    !
    typeof document.body;!
    "object"!
    !
    typeof NaN;!
    "number"!
    !
    30  

    View full-size slide

  29. Object.prototype.toString()!
     
     
     
     
    31  

    View full-size slide

  30. Object.prototype.toString()!
     
     
     
     
    32  
    WHY?

    View full-size slide

  31. •  If the this value is undefined, return "[object
    Undefined]".!

    !
    33  

    View full-size slide

  32. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    !
    34  

    View full-size slide

  33. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    •  Let class be the value of the [[Class]] property of this.
    !
    35  

    View full-size slide

  34. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    •  Let class be the value of the [[Class]] property of this.
    •  Return the String value that is the result of concatenating
    the three Strings "[object ", class, and "]".
    !
    36  

    View full-size slide

  35. •  If the this value is undefined, return "[object
    Undefined]".!
    •  If the this value is null, return "[object Null]".!
    •  Let class be the value of the [[Class]] property of this.
    •  Return the String value that is the result of concatenating
    the three Strings "[object ", class, and "]".
    !
    37  
    Function.prototype.call()!
    or!
    Function.prototype.apply()!

    View full-size slide

  36. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    var match = toString.call(o).match(regex);!
    return match[1].toLowerCase();!
    };!
    38  

    View full-size slide

  37. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    var match = toString.call(o).match(regex);!
    return match[1].toLowerCase();!
    };!
    39  
    this === o  

    View full-size slide

  38. type({});!
    "object"!
    !
    type('hello');!
    "string"!
    !
    type(5);!
    "number”!
    type(function () {});!
    "function"!
    !
    type(undefined);!
    "undefined"!
    !
    type(true);!
    "boolean"!
    !
    40  

    View full-size slide

  39. type([]);!
    "array"!
    !
    type(null);!
    "null"!
    !
    type(new Date());!
    "date"!
    type(/jsconf/);!
    "regex"!
    !
    type(document.body);!
    "htmlbodyelement"!
    !
    type(NaN);!
    "number"!
    !
    41  

    View full-size slide

  40. type([]);!
    "array"!
    !
    type(null);!
    "null"!
    !
    type(new Date());!
    "date"!
    type(/jsconf/);!
    "regex"!
    !
    type(document.body);!
    "htmlbodyelement"!
    !
    type(NaN);!
    "number"!
    !
    ???
    42  

    View full-size slide

  41. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    !
    if (o && o.nodeType === 1) {!
    return 'element';!
    }!
    !
    var match = toString.call(o).match(regex);!
    var _type = match[1].toLowerCase();!
    !
    if (_type === 'number' && isNaN(o)) {!
    return 'nan';!
    }!
    !
    return _type;!
    };! 43  

    View full-size slide

  42. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    !
    if (o && o.nodeType === 1) {!
    return 'element';!
    }!
    !
    var match = toString.call(o).match(regex);!
    var _type = match[1].toLowerCase();!
    !
    if (_type === 'number' && isNaN(o)) {!
    return 'nan';!
    }!
    !
    return _type;!
    };! 44  
    Special case for DOM elements

    View full-size slide

  43. var toString = Object.prototype.toString;!
    var regex = /\[object (.*?)\]/;!
    !
    var type = function (o) {!
    !
    if (o && o.nodeType === 1) {!
    return 'element';!
    }!
    !
    var match = toString.call(o).match(regex);!
    var _type = match[1].toLowerCase();!
    !
    if (_type === 'number' && isNaN(o)) {!
    return 'nan';!
    }!
    !
    return _type;!
    };! 45  
    Special case for NaN

    View full-size slide

  44. 46  
    Now what?

    View full-size slide

  45. Api.get('/conversations', function (conversations) {!
    !
    if (type(conversations) !== 'array') {!
    App.renderMessages([]);!
    return;!
    }!
    !
    var intros = conversations.map(function (c) {!
    !
    if (type(c) !== 'object') {!
    return '';!
    }!
    !
    var name = type(c.theirName) === 'string' ? c.theirName : '';!
    var mostRecent = '';!
    !
    if (type(c.messages) === 'array' ||!
    type(c.messages[0]) === 'object' ||!
    type(c.messages[0].text) === 'string') {!
    mostRecent = c.messages[0].text.substring(0, 30);!
    }!
    !
    return name + ': ' + mostRecent;!
    });!
    !
    App.renderMessages(intros);!
    !
    });  
    47  

    View full-size slide

  46. Api.get("/conversations",function(e){var t=e.map(function(e)
    {var t=e.theirName;var
    n=e.messages[0].text.substring(0,30);return t+":
    "+n});App.renderMessages(t)})!
    !
    !
    !
    !
    !
    !
    !
    !
    !
    Api.get("/conversations",function(e){if(type(e)!=="array")
    {App.renderMessages([]);return}var t=e.map(function(e)
    {if(type(e)!=="object"){return""}var
    t=type(e.theirName)==="string"?e.theirName:"";var
    n="";if(type(e.messages)==="array"||
    type(e.messages[0])==="object"||
    type(e.messages[0].text)==="string")
    {n=e.messages[0].text.substring(0,30)}return t+":
    "+n});App.renderMessages(t)})  
    + 137%
    48  

    View full-size slide

  47. 49  
    How does this
    help
    maintainability?

    View full-size slide

  48. 50  
    Prevents unexpected breakdowns


    View full-size slide

  49. 51  
    Prevents unexpected breakdowns

    Makes future maintenance easier


    View full-size slide

  50. topics.shift();!
    "Classes and Inheritance"!
     
    52  

    View full-size slide

  51. 53  
    Why classes?

    View full-size slide

  52. 54  
    var Controller = {!
    !
    init: function () {!
    // do some initialization!
    },!
    !
    loadView: function () {!
    !
    }!
    !
    };!
    !
    // somewhere else in the app!
    Controller.init();!
    Controller.loadView();  

    View full-size slide

  53. 55  
    var Controller = {!
    !
    init: function () {!
    // do some initialization!
    },!
    !
    loadView: function () {!
    !
    }!
    !
    };!
    !
    // somewhere else in the app!
    Controller.init();!
    Controller.loadView();  
    Feels messy

    View full-size slide

  54. 56  
    var ChatController = {};!
    !
    for (var key in Controller) {!
    ChatController[key] = Controller;!
    }!
    !
    ChatController.loadView = function () {!
    !
    Controller.loadView.apply(this, arguments);
    // do some additional stuff!
    !
    };!
     

    View full-size slide

  55. 57  
    var ChatController = {};!
    !
    for (var key in Controller) {!
    ChatController[key] = Controller;!
    }!
    !
    ChatController.loadView = function () {!
    !
    Controller.loadView.apply(this, arguments);
    // do some additional stuff!
    !
    };!
      Not proper inheritance

    View full-size slide

  56. 58  
    What do we
    want?

    View full-size slide

  57. class Controller {!
    !!
    public Controller () {!
    // do some initialization!
    }!
    !!
    ! public void loadView () {!
    ! !!
    ! }!
    !
    }!
    !
    class ChatController extends Controller {!
    !!
    ! public void loadView () {!
    ! ! super.loadView();!
    ! ! // do some additional stuff!
    ! }!
    !!
    }  
    59  

    View full-size slide

  58. class Controller {!
    !
    constructor () {!
    // do some initialization!
    }!
    !
    loadView () {!
    !
    }!
    !
    }!
    !
    class ChatController extends Controller {!
    !
    loadView () {!
    super.loadView();!
    // do some additional stuff!
    }!
    !
    }  
    60  

    View full-size slide

  59. class Controller!
    !
    constructor: () ->!
    # do some initialization!
    !
    loadView: () ->!
    !
    !
    class ChatController extends Controller!
    !
    loadView: () ->!
    super!
    # do some initialization  
    61  

    View full-size slide

  60. var ChatController, Controller, _ref,!
    __hasProp = {}.hasOwnProperty,!
    __extends = function(child, parent) {!
    for (var key in parent) {!
    if (__hasProp.call(parent, key))!
    child[key] = parent[key];!
    }!
    function ctor() {!
    this.constructor = child;!
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child.__super__ = parent.prototype;!
    return child;!
    };!
    !
    Controller = (function() {!
    function Controller() {}!
    Controller.prototype.loadView = function() {};!
    return Controller;!
    })();!
    !
    ChatController = (function(_super) {!
    __extends(ChatController, _super);!
    !
    function ChatController() {!
    _ref = ChatController.__super__.constructor.apply(this, arguments);!
    return _ref;!
    }!
    !
    ChatController.prototype.loadView = function() {!
    return ChatController.__super__.loadView.apply(this, arguments);!
    };!
    !
    return ChatController;!
    })(Controller);   62  

    View full-size slide

  61. var ChatController, Controller, _ref,!
    __hasProp = {}.hasOwnProperty,!
    __extends = function(child, parent) {!
    for (var key in parent) {!
    if (__hasProp.call(parent, key))!
    child[key] = parent[key];!
    }!
    function ctor() {!
    this.constructor = child;!
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child.__super__ = parent.prototype;!
    return child;!
    };!
    !
    Controller = (function() {!
    function Controller() {}!
    Controller.prototype.loadView = function() {};!
    return Controller;!
    })();!
    !
    ChatController = (function(_super) {!
    __extends(ChatController, _super);!
    !
    function ChatController() {!
    _ref = ChatController.__super__.constructor.apply(this, arguments);!
    return _ref;!
    }!
    !
    ChatController.prototype.loadView = function() {!
    return ChatController.__super__.loadView.apply(this, arguments);!
    };!
    !
    return ChatController;!
    })(Controller);   63  
    Utility method

    View full-size slide

  62. var ChatController, Controller, _ref,!
    __hasProp = {}.hasOwnProperty,!
    __extends = function(child, parent) {!
    for (var key in parent) {!
    if (__hasProp.call(parent, key))!
    child[key] = parent[key];!
    }!
    function ctor() {!
    this.constructor = child;!
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child.__super__ = parent.prototype;!
    return child;!
    };!
    !
    Controller = (function() {!
    function Controller() {}!
    Controller.prototype.loadView = function() {};!
    return Controller;!
    })();!
    !
    ChatController = (function(_super) {!
    __extends(ChatController, _super);!
    !
    function ChatController() {!
    _ref = ChatController.__super__.constructor.apply(this, arguments);!
    return _ref;!
    }!
    !
    ChatController.prototype.loadView = function() {!
    return ChatController.__super__.loadView.apply(this, arguments);!
    };!
    !
    return ChatController;!
    })(Controller);   64  
    Class Definitions

    View full-size slide

  63. var Controller = function () {!
    // do some initialization!
    };!
    !
    Controller.prototype.loadView = function() {!
    !
    };!
    !
    var ChatController = function (name) {!
    Controller.apply(this, arguments);!
    };!
    !
    ChatController.prototype.loadView = function () {!
    ChatController._super.loadView.apply(this, arguments);!
    // do some additional stuff!
    !
    }!
    !
    extends(ChatController, Controller);  
    65  

    View full-size slide

  64. var Controller = function () {!
    // do some initialization!
    };!
    !
    Controller.prototype.loadView = function() {!
    !
    };!
    !
    var ChatController = function (name) {!
    Controller.apply(this, arguments);!
    };!
    !
    ChatController.prototype.loadView = function () {!
    ChatController._super.loadView.apply(this, arguments);!
    // do some additional stuff!
    !
    }!
    !
    extends(ChatController, Controller);  
    66  
    The magic bit

    View full-size slide

  65. 67  
    There is no such
    thing as magic.

    View full-size slide

  66. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    68  

    View full-size slide

  67. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    69  
    Copy static properties / methods

    View full-size slide

  68. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    70  
    Set up prototype chain

    View full-size slide

  69. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    71  
    Set up prototype chain
    (kind of a hack)

    View full-size slide

  70. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    !
    !
    ctor.prototype = Object.create(parent.prototype);!
    !
    !
    child._super = parent.prototype;!
    return child;!
    };  
    72  
    ECMAScript 5

    View full-size slide

  71. var extends = function(child, parent) {!
    for (var key in parent) {!
    if (parent.hasOwnProperty(key)) {!
    child[key] = parent[key];!
    }!
    }!
    function ctor() { !
    this.constructor = child; !
    }!
    ctor.prototype = parent.prototype;!
    child.prototype = new ctor();!
    child._super = parent.prototype;!
    return child;!
    };  
    73  
    Add shorthand to super

    View full-size slide

  72. var controller = new Controller();!
    var chat = new ChatController();!
    !
    controller instanceof Controller; // true!
    !
    chat instanceof Controller; // true!
    chat instanceof ChatController; // true  
    74  

    View full-size slide

  73. 75  
    The rest is about
    good practice

    View full-size slide

  74. Use getter and setter methods
    alert(jon.name);!
    jon.name = 'John';!
    !
    !
    alert(jon.getName());!
    jon.setName('John');!
    jon.set('name', 'John');  
    76  

    View full-size slide

  75. Define all properties on the
    prototype, even if they are null.

    /**!
    * The persons age.!
    * @type {Number}!
    */!
    Person.prototype.age = null;  
    77  

    View full-size slide

  76. Mark private methods with a
    leading or trailing underscore

    // somethings are best kept private :)!
    Person.prototype._singInShower = function () {!
    !
    };  
    78  

    View full-size slide

  77. Use static methods / properties
    when appropriate
    Person.prototype.EVENTS = {!
    WALK: 'WALK',!
    TALK: 'TALK'!
    };!
    !
    Person.EVENTS = {!
    WALK: 'WALK',!
    TALK: 'TALK'!
    };  
    79  

    View full-size slide

  78. 80  
    How does this
    help
    maintainability?

    View full-size slide

  79. 81  
    Correct defects or their causes


    View full-size slide

  80. 82  
    Correct defects or their causes

    Makes future maintenance easier


    View full-size slide

  81. topics.shift();!
    "Asynchronous Code"!
     
    83  

    View full-size slide

  82. 84  
    The big
    question...

    View full-size slide

  83. Callbacks
    or
    Promises
    85  

    View full-size slide

  84. Promises

    •  Requires a library to provide the functionality
    86  

    View full-size slide

  85. Promises

    •  Requires a library to provide the functionality
    •  Different implementations
    •  jQuery Deferred api.jquery.com/category/deferred-object/
    •  rsvp.js github.com/tildeio/rsvp.js
    •  when.js github.com/cujojs/when
    •  promise.js github.com/then/promise
    87  

    View full-size slide

  86. Promises

    •  Requires a library to provide the functionality
    •  Different implementations
    •  jQuery Deferred api.jquery.com/category/deferred-object/
    •  rsvp.js github.com/tildeio/rsvp.js
    •  when.js github.com/cujojs/when
    •  promise.js github.com/then/promise
    •  Kind of complicated…
    88  

    View full-size slide

  87. h(p://promises-­‐aplus.github.io/promises-­‐spec/  
    89  

    View full-size slide

  88. 91  
    But that must
    mean...

    View full-size slide

  89. Callback
    Hell
    92  

    View full-size slide

  90. 93  
    load: function () {!
    !
    Api.get('/profile/own', _.bind(function (ownProfile) {!
    !
    this.ownProfile = ownProfile;!
    !
    Api.get('/profile/' + id, _.bind(function (theirProfile) {!
    !
    this.theirProfile = theirProfile;!
    !
    Api.get('/chatMessages', _.bind(function (messages) {!
    !
    this.messages = messages;!
    this.render();!
    !
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }!
    !

    View full-size slide

  91. 94  
    load: function () {!
    !
    Api.get('/profile/own', _.bind(function (ownProfile) {!
    !
    this.ownProfile = ownProfile;!
    !
    Api.get('/profile/' + id, _.bind(function (theirProfile) {!
    !
    this.theirProfile = theirProfile;!
    !
    Api.get('/chatMessages', _.bind(function (messages) {!
    !
    this.messages = messages;!
    this.render();!
    !
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }!
    !

    View full-size slide

  92. 95  
    load: function () {!
    !
    Api.get('/profile/own', _.bind(function (ownProfile) {!
    !
    this.ownProfile = ownProfile;!
    !
    Api.get('/profile/' + id, _.bind(function (theirProfile) {!
    !
    this.theirProfile = theirProfile;!
    !
    Api.get('/chatMessages', _.bind(function (messages) {!
    !
    this.messages = messages;!
    this.render();!
    !
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }, this), _.bind(function (err) {!
    this.onError();!
    }, this));!
    }!
    !
    Action
    Error Handling

    View full-size slide

  93. “I’ve come to the conclusion
    that callback hell is a design
    choice and not an inherent
    flaw in the concept of
    asynchronous function and
    callback” http://blog.caplin.com/2013/03/13/callback-hell-is-a-design-choice/
    97  

    View full-size slide

  94. doSomething(function (err, response) {!
    !
    });  
    98  

    View full-size slide

  95. 99  
    var handler = function (err, response) {!
    !
    };!
    !
    doSomething(handler);  

    View full-size slide

  96. 100  
    !
    load: function (id) {!
    this.id = id;!
    Api.get('/profile/own', this.onOwnProfile);!
    },!
    !
    onOwnProfile: function (err, ownProfile) {!
    if (err) return this.onError();!
    this.ownProfile = ownProfile;!
    Api.get('/profile/' + this.id, this.onTheirProfile);!
    },!
    !
    onTheirProfile: function (err, theirProfile) {!
    if (err) return this.onError();!
    this.theirProfile = theirProfile;!
    Api.get('/chatMessages', this.onMessages);!
    },!
    !
    onMessages: function (err, messages) {!
    if (err) return this.onError();!
    this.messages = messages;!
    this.render();!
    }!

    View full-size slide

  97. 101  
    !
    load: function (id) {!
    this.id = id;!
    Api.get('/profile/own', this.onOwnProfile);!
    },!
    !
    onOwnProfile: function (err, ownProfile) {!
    if (err) return this.onError();!
    this.ownProfile = ownProfile;!
    Api.get('/profile/' + this.id, this.onTheirProfile);!
    },!
    !
    onTheirProfile: function (err, theirProfile) {!
    if (err) return this.onError();!
    this.theirProfile = theirProfile;!
    Api.get('/chatMessages', this.onMessages);!
    },!
    !
    onMessages: function (err, messages) {!
    if (err) return this.onError();!
    this.messages = messages;!
    this.render();!
    }!

    View full-size slide

  98. 102  
    !
    load: function (id) {!
    this.id = id;!
    Api.get('/profile/own', this.onOwnProfile);!
    },!
    !
    onOwnProfile: function (err, ownProfile) {!
    if (err) return this.onError();!
    this.ownProfile = ownProfile;!
    Api.get('/profile/' + this.id, this.onTheirProfile);!
    },!
    !
    onTheirProfile: function (err, theirProfile) {!
    if (err) return this.onError();!
    this.theirProfile = theirProfile;!
    Api.get('/chatMessages', this.onMessages);!
    },!
    !
    onMessages: function (err, messages) {!
    if (err) return this.onError();!
    this.messages = messages;!
    this.render();!
    }!

    View full-size slide

  99. 103  
    !
    load: function (id) {!
    this.id = id;!
    Api.get('/profile/own', this.onOwnProfile);!
    },!
    !
    onOwnProfile: function (err, ownProfile) {!
    if (err) return this.onError();!
    this.ownProfile = ownProfile;!
    Api.get('/profile/' + this.id, this.onTheirProfile);!
    },!
    !
    onTheirProfile: function (err, theirProfile) {!
    if (err) return this.onError();!
    this.theirProfile = theirProfile;!
    Api.get('/chatMessages', this.onMessages);!
    },!
    !
    onMessages: function (err, messages) {!
    if (err) return this.onError();!
    this.messages = messages;!
    this.render();!
    }!
    Reusable

    View full-size slide

  100. Avoid anonymous functions
    104  

    View full-size slide

  101. Avoid anonymous functions
    105  
    Useless stack traces


    View full-size slide

  102. Avoid anonymous functions
    106  
    Useless stack traces

    Sign of poor structure

    View full-size slide

  103. Keep things shallow

    107  

    View full-size slide

  104. Keep things shallow

    108  
    Means you are probably
    using anonymous
    functions

    View full-size slide

  105. Keep things shallow

    109  
    Means you are probably
    using anonymous
    functions

    Everyone will hate you

    View full-size slide

  106. 110  
    How does this
    help
    maintainability?

    View full-size slide

  107. 111  
    Isolate defects or their causes


    View full-size slide

  108. 112  
    Isolate defects or their causes

    Makes future maintenance easier


    View full-size slide

  109. 113  
    Isolate defects or their causes

    Makes future maintenance easier

    Prevent unexpected breakdowns

    View full-size slide

  110. topics.shift();!
    "Performance"!
     
    114  

    View full-size slide

  111. var i = 0;!
    var thing;!
    for (; i < things.length; i++) {!
    thing = things[i];!
    }  
    things.forEach(function (thing, i) {!
    !
    });  
    115  
    or…

    View full-size slide

  112. 116  
    http://jsperf.com/foreachvsloop
    13x Faster
    13.5x faster
    24x faster

    View full-size slide

  113. $('a').on('click', function (e) {!
    !
    });!
    !
    !
    !
    !
    !
    !
    !
    !
    $('#container').on('click', 'a', function (e) {!
    !
    });  
    or…
    117  

    View full-size slide

  114. 118  
    http://jsperf.com/domevents
    21x Faster
    21x faster
    19x faster

    View full-size slide

  115. 119  
    $('#container').append('');!
    for (var i = 0; i < messages.length; i++) {!
    $('#container')!
    .find('ul')!
    .append('' + messages[i].text + '');!
    }  
    !
    !
    !
    !
    !
    var html = '';!
    for (var i = 0; i < messages.length; i++) {!
    html += '' + messages[i].text + '';!
    }!
    html += '';!
    $('#container').html(html);!
    or…

    View full-size slide

  116. 120  
    http://jsperf.com/renderinghtml
    48x Faster
    44x faster
    15x faster

    View full-size slide

  117. DOM operations
    121  

    View full-size slide

  118. DOM operations

    Iteration / function calls
    122  

    View full-size slide

  119. 123  
    Beware of
    premature
    optimizations

    View full-size slide

  120. 124  
    Bottlenecks

    View full-size slide

  121. var cache = {!
    !
    get: function (key) {!
    return localStorage.getItem(key);!
    },!
    !
    set: function (key, value) {!
    localStorage.setItem(key, value);!
    }!
    !
    };  
    125  

    View full-size slide

  122. var cache = {!
    !
    get: function (key) {!
    return localStorage.getItem(key);!
    },!
    !
    set: function (key, value) {!
    localStorage.setItem(key, value);!
    }!
    !
    };  
    126  
    Disc IO

    View full-size slide

  123. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   127  

    View full-size slide

  124. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   128  
    Memory

    View full-size slide

  125. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   129  
    Quicker reading

    View full-size slide

  126. var cache = {!
    !
    data_: {},!
    !
    get: function (key) {!
    !
    if (this.data_.hasOwnProperty(key)) {!
    return this.data_[key];!
    }!
    !
    var value = localStorage.getItem(key);!
    !
    if (value !== null) {!
    this.data_[key] = value;!
    return value;!
    }!
    !
    return null;!
    },!
    !
    set: function (key, value) {!
    this.data_[key] = value;!
    localStorage.setItem(key, value);!
    }!
    !
    };   130  
    Saving for later

    View full-size slide

  127. 131  
    http://jsperf.com/localstoragevsmemory
    3x faster
    Not much in it

    View full-size slide

  128. 132  
    How does this
    help
    maintainability?

    View full-size slide

  129. 133  
    Makes future maintenance easier

    View full-size slide

  130. 134  
    Makes future maintenance easier

    Prevent unexpected breakdowns

    View full-size slide

  131. 135  
    topics.shift();!
    undefined  

    View full-size slide

  132. 136  
    What is
    maintainability?

    View full-size slide

  133. 137  
    It's about making
    our lives easier

    View full-size slide

  134. 138  
    It's about making
    our work pass the
    test of time

    View full-size slide

  135. Thank you!
    139  
    Yet Another Conference 2013

    @jonbretman
    jonbretman.co.uk

    Mobile Web Developer @ Badoo
    http://techblog.badoo.com
    @BadooTech

    http://habrahabr.ru/company/badoo
    @BadooDev

    View full-size slide