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.

0324ece38e4ae25a75c52f2bbec7967a?s=128

Jon Bretman

October 08, 2013
Tweet

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  
  2. 2  

  3. <!doctype html>! <html>! <head>! <title>My Awesome App</title>! <script src="App.js"></script>! </head>!

    <body>! </body>! </html>   3  
  4. 4   What is maintainability?

  5. 5   In engineering, maintainability is the ease with which

    a product can be maintained in order to... h(p://en.wikipedia.org/wiki/Maintainability  
  6. 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  
  7. 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  
  8. 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  
  9. 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  
  10. 10   It's about making our lives easier

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

    of time
  12. 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||1<e&e==h&&S>2|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+768<N|d&&N}function W(){! i="<table>";for(u=18;u<99;document.body.innerHTML=i+=++u%x-9?! "<th width=60 height=60 onclick='I[b="+u+"]>8?W():X(0,0,1)'style='font-size:50px'bgcolor=#"! +(u-B?u*.9&1||9:"d")+"0f0e0>&#"+(I[u]?9808+l[67+I[u]]:160):u++&&"<tr>")B=b}W()   h(p://js1k.com/2010-­‐first/demo/750  
  13. 13  

  14. 14   •  Team of 4 developers

  15. 15   •  Team of 4 developers •  JavaScript, jsDoc,

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

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

    JSHint, Closure Compiler, JsTestDriver •  60,000+ lines of JavaScript •  ~500,000 daily active users
  18. 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!
  19. var topics = [! 'Type Checking',! 'Classes and Inheritance’,! 'Asynchronous

    Code',! 'Performance'! ];!   19  
  20. topics.shift();! "Type Checking"!   20  

  21. <!doctype html>! <html>! <head>! <title>My Awesome App</title>! <script src="App.js"></script>! </head>!

    <body>! </body>! </html>   21  
  22. 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  
  23. 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  
  24. 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  
  25. 25   Exceptions

  26. if (data.value) {! callback(data.value);! }! ! ! TypeError: Cannot read

    property 'value' of null! ! ! ! !       26  
  27. if (data && data.callback) {! var result = data.callback();! }!

    ! ! TypeError: Property 'callback' of object #<Object> is not a function     27  
  28. if (data && data.value) {! var index = data.value.indexOf('something');! }!

    ! ! TypeError: Object #<Object> has no method ‘indexOf’ ! ! ! ! !     28  
  29. typeof {};! "object"! ! typeof 'hello';! "string"! ! typeof 5;!

    "number”! typeof function () {};! "function"! ! typeof undefined;! "undefined"! ! typeof true;! "boolean"! ! 29  
  30. typeof [];! "object"! ! typeof null;! "object"! ! typeof new

    Date();! "object"! typeof /jsconf/;! "object"! ! typeof document.body;! "object"! ! typeof NaN;! "number"! ! 30  
  31. Object.prototype.toString()!         31  

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

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

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

    •  If the this value is null, return "[object Null]".! ! 34  
  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. ! 35  
  36. •  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  
  37. •  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()!
  38. var toString = Object.prototype.toString;! var regex = /\[object (.*?)\]/;! !

    var type = function (o) {! var match = toString.call(o).match(regex);! return match[1].toLowerCase();! };! 38  
  39. 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  
  40. type({});! "object"! ! type('hello');! "string"! ! type(5);! "number”! type(function ()

    {});! "function"! ! type(undefined);! "undefined"! ! type(true);! "boolean"! ! 40  
  41. type([]);! "array"! ! type(null);! "null"! ! type(new Date());! "date"! type(/jsconf/);!

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

    "regex"! ! type(document.body);! "htmlbodyelement"! ! type(NaN);! "number"! ! ??? 42  
  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;! };! 43  
  44. 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
  45. 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
  46. 46   Now what?

  47. 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  
  48. 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  
  49. 49   How does this help maintainability?

  50. 50   Prevents unexpected breakdowns

  51. 51   Prevents unexpected breakdowns Makes future maintenance easier

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

  53. 53   Why classes?

  54. 54   var Controller = {! ! init: function ()

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

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

    in Controller) {! ChatController[key] = Controller;! }! ! ChatController.loadView = function () {! ! Controller.loadView.apply(this, arguments); // do some additional stuff! ! };!  
  57. 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
  58. 58   What do we want?

  59. class Controller {! !! public Controller () {! // do

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

    initialization! }! ! loadView () {! ! }! ! }! ! class ChatController extends Controller {! ! loadView () {! super.loadView();! // do some additional stuff! }! ! }   60  
  61. class Controller! ! constructor: () ->! # do some initialization!

    ! loadView: () ->! ! ! class ChatController extends Controller! ! loadView: () ->! super! # do some initialization   61  
  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);   62  
  63. 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
  64. 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
  65. 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  
  66. 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
  67. 67   There is no such thing as magic.

  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;! };   68  
  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;! };   69   Copy static properties / methods
  70. 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
  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;! };   71   Set up prototype chain (kind of a hack)
  72. 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
  73. 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
  74. var controller = new Controller();! var chat = new ChatController();!

    ! controller instanceof Controller; // true! ! chat instanceof Controller; // true! chat instanceof ChatController; // true   74  
  75. 75   The rest is about good practice

  76. Use getter and setter methods alert(jon.name);! jon.name = 'John';! !

    ! alert(jon.getName());! jon.setName('John');! jon.set('name', 'John');   76  
  77. Define all properties on the prototype, even if they are

    null. /**! * The persons age.! * @type {Number}! */! Person.prototype.age = null;   77  
  78. Mark private methods with a leading or trailing underscore //

    somethings are best kept private :)! Person.prototype._singInShower = function () {! ! };   78  
  79. Use static methods / properties when appropriate Person.prototype.EVENTS = {!

    WALK: 'WALK',! TALK: 'TALK'! };! ! Person.EVENTS = {! WALK: 'WALK',! TALK: 'TALK'! };   79  
  80. 80   How does this help maintainability?

  81. 81   Correct defects or their causes

  82. 82   Correct defects or their causes Makes future maintenance

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

  84. 84   The big question...

  85. Callbacks or Promises 85  

  86. Promises •  Requires a library to provide the functionality 86

     
  87. 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  
  88. 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  
  89. h(p://promises-­‐aplus.github.io/promises-­‐spec/   89  

  90. 90   TL;DR

  91. 91   But that must mean...

  92. Callback Hell 92  

  93. 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));! }! !
  94. 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));! }! !
  95. 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
  96. 96   No.

  97. “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  
  98. doSomething(function (err, response) {! ! });   98  

  99. 99   var handler = function (err, response) {! !

    };! ! doSomething(handler);  
  100. 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();! }!
  101. 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();! }!
  102. 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();! }!
  103. 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
  104. Avoid anonymous functions 104  

  105. Avoid anonymous functions 105   Useless stack traces

  106. Avoid anonymous functions 106   Useless stack traces Sign of

    poor structure
  107. Keep things shallow 107  

  108. Keep things shallow 108   Means you are probably using

    anonymous functions
  109. Keep things shallow 109   Means you are probably using

    anonymous functions Everyone will hate you
  110. 110   How does this help maintainability?

  111. 111   Isolate defects or their causes

  112. 112   Isolate defects or their causes Makes future maintenance

    easier
  113. 113   Isolate defects or their causes Makes future maintenance

    easier Prevent unexpected breakdowns
  114. topics.shift();! "Performance"!   114  

  115. var i = 0;! var thing;! for (; i <

    things.length; i++) {! thing = things[i];! }   things.forEach(function (thing, i) {! ! });   115   or…
  116. 116   http://jsperf.com/foreachvsloop 13x Faster 13.5x faster 24x faster

  117. $('a').on('click', function (e) {! ! });! ! ! ! !

    ! ! ! ! $('#container').on('click', 'a', function (e) {! ! });   or… 117  
  118. 118   http://jsperf.com/domevents 21x Faster 21x faster 19x faster

  119. 119   $('#container').append('<ul></ul>');! for (var i = 0; i <

    messages.length; i++) {! $('#container')! .find('ul')! .append('<li>' + messages[i].text + '</li>');! }   ! ! ! ! ! var html = '<ul>';! for (var i = 0; i < messages.length; i++) {! html += '<li>' + messages[i].text + '</li>';! }! html += '</ul>';! $('#container').html(html);! or…
  120. 120   http://jsperf.com/renderinghtml 48x Faster 44x faster 15x faster

  121. DOM operations 121  

  122. DOM operations Iteration / function calls 122  

  123. 123   Beware of premature optimizations

  124. 124   Bottlenecks

  125. var cache = {! ! get: function (key) {! return

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

    localStorage.getItem(key);! },! ! set: function (key, value) {! localStorage.setItem(key, value);! }! ! };   126   Disc IO
  127. 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  
  128. 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
  129. 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
  130. 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
  131. 131   http://jsperf.com/localstoragevsmemory 3x faster Not much in it

  132. 132   How does this help maintainability?

  133. 133   Makes future maintenance easier

  134. 134   Makes future maintenance easier Prevent unexpected breakdowns

  135. 135   topics.shift();! undefined  

  136. 136   What is maintainability?

  137. 137   It's about making our lives easier

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

    of time
  139. 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