flickr.com/photos/jontysewell/4526861658/




             Maintainable JavaScript
                                   Nicholas C. Zakas | @slicknet
Who's this guy?




     5 years             Co-Creator                Contributor,
Tech Lead, Yahoo!        csslint.net            Creator of YUI Test




 Author         Lead Author       Contributor           Lead Author
Maintainability
Why do we care?
flickr.com/photos/indraw/4857101224/




       Most of your time is spent maintaining code
Who cares?




Your Employer           Your Co-workers,
                       Present and Future
flickr.com/photos/protestphotos1/4726566233/




                          We all want to be rock stars
                 "Don't mess with my process, man! It's about the music!"
flickr.com/photos/the_junes/3120810156/




                        At work, you're part of a team
                  Awesome happens when everyone is on the same page
Maintainable code is



Understandable        Adaptable   Debuggable


                                         Testable
          Intuitive       Extendable
Code Conventions


               Understandable



                         Intuitive
flickr.com/photos/29271559@N02/5799773313/




      "Programs are meant to be read by humans and
        only incidentally for computers to execute."
                                             Donald Knuth
flickr.com/photos/polinasergeeva/3052378826/




                                                              Tabs for
                                                             indentation




                4 spaces for
                 indentation




                                               Indentation
if (wl && wl.length) {
            for (i = 0, l = wl.length; i < l; ++i) {
         p = wl[i];
         type = Y.Lang.type(r[p]);
         if (s.hasOwnProperty(p)) { if (merge && type
    == 'object') {

    Y.mix(r[p], s[p]);
} else if (ov || !(p in r)) {
                    r[p] = s[p];
                }
            }
        }
    }
if (wl && wl.length) {
    for (i = 0, l = wl.length; i < l; ++i) {
        p = wl[i];
        type = Y.Lang.type(r[p]);
        if (s.hasOwnProperty(p)) {
            if (merge && type == 'object') {
                Y.mix(r[p], s[p]);
            } else if (ov || !(p in r)) {
                r[p] = s[p];
            }
        }
    }
}
flickr.com/photos/polinasergeeva/3052378826/




                                               Comments
/**
 * Returns a new object containing all of the properties of
 * all the supplied objects. The properties from later objects
 * will overwrite those in earlier objects. Passing in a
 * single object will create a shallow copy of it. For a deep
 * copy, use clone.
 * @method merge
 * @for YUI
 * @param arguments {Object*} the objects to merge.
 * @return {object} the new merged object.
 */
Y.merge = function() {
    var a = arguments, o = {}, i, l = a.length;
    for (i = 0; i < l; i = i + 1) {
        Y.mix(o, a[i], true);
    }
    return o;
};
                     Every method
if (mode) {
    switch (mode) {
        case 1: // proto to proto
            return Y.mix(r.prototype, s.prototype, ov, wl, 0,
                         merge);
        case 2: // object to object and proto to proto
            Y.mix(r.prototype, s.prototype, ov, wl, 0, merge);
            break; // pass through
        case 3: // proto to static
            return Y.mix(r, s.prototype, ov, wl, 0, merge);
        case 4: // static to proto
            return Y.mix(r.prototype, s, ov, wl, 0, merge);
        default: // object to object is what happens below
    }
}



             Difficult-to-understand code
while (element &&(element = element[axis])){ //NOTE: assignment
    if ( (all || element[TAG_NAME]) &&
       (!fn || fn(element)) ) {
            return element;
    }
}




        Code that might seem to be wrong
Naming
flickr.com/photos/kaatje/243834320/
Naming
• Use logical names for variables and functions
   – Don't worry about length
• Variable names should be nouns
• Function names should begin with a verb (i.e.
  getName())
   – Functions return booleans should begin with
     "is", such as isValid()
• Avoid useless names such as foo and temp
if (wl && wl.length) {
    for (i = 0, l = wl.length; i < l; ++i) {
        p = wl[i];
        type = Y.Lang.type(r[p]);
        if (s.hasOwnProperty(p)) {
            if (merge && type == 'object') {
                Y.mix(r[p], s[p]);
            } else if (ov || !(p in r)) {
                r[p] = s[p];
            }
        }
    }
}
Loose Coupling


            Adaptable   Debuggable



                 Extendable
Front End Layers



Presentation     Behavior
   (CSS)       (JavaScript)
                Base JS
      Data/Structure
         (HTML)
Don't cross the streams
<button onclick="doSomething()">Click Me</button>




     Keep JavaScript out of HTML
var element = document.getElementById("container");
element.innerHTML = "<div class="popup"></div>";




            Keep HTML out of JavaScript
.foo {
    width: expression(document.offsetWidth + "px");
}




             Keep JavaScript out of CSS
var element = document.getElementById("container");
element.style.color = "red";
element.style.cssText = "background:blue;border:1px solid red";




             Keep CSS out of JavaScript
Programming Practices


              Adaptable Debuggable


                               Testable
                 Extendable
//the wrong way!!!
function handleClick(event){

    var popup = document.getElementById("popup");
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "reveal";

}




    Event handlers should only handle events
//better, but still wrong
function handleClick(event){
    showPopup(event);
}

function showPopup(event){
    var popup = document.getElementById("popup");
    popup.style.left = event.clientX + "px";
    popup.style.top = event.clientY + "px";
    popup.className = "reveal";
}




        Don't pass the event object around
//win!!
function handleClick(event){
    showPopup(event.clientX, event.clientY);
}

function showPopup(x, y){
    var popup = document.getElementById("popup");
    popup.style.left = x + "px";
    popup.style.top = y + "px";
    popup.className = "reveal";
}




        Properly separated event handling
//don't add new methods
Array.prototype.awYeah = function(){
    alert("Aw yeah!");
};

//don't override methods
YUI.use = function(){
    alert("Aw yeah!");
};




        Don't modify objects you don't own
        If you didn't define the object yourself, you don't own it
nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/
function handleClick(event){
    showPopup(event.clientX, event.clientY);
}

function showPopup(x, y){
    var popup = document.getElementById("popup");
    popup.style.left = x + "px";
    popup.style.top = y + "px";
    popup.className = "reveal";
}




       Avoid global functions and variables
var Controller = {
    handleClick: function(event){
        this.showPopup(event.clientX, event.clientY);
    },

     showPopup: function (x, y){
         var popup = document.getElementById("popup");
         popup.style.left = x + "px";
         popup.style.top = y + "px";
         popup.className = "reveal";
     }
};




        Avoid global functions and variables
      Create a single global (if necessary) and attach everything to it
var Controller = {
    addClass: function(element, className){
        element.className += " " + className;
    }
};




                Throw your own errors
                 When you know a function will fail
var Controller = {
    addClass: function(element, className){
        if (!element) {
            throw new Error("addClass: 1st argument missing.");
        }
        element.className += " " + className;
    }
};




                Throw your own errors
                 When you know a function will fail
var Controller = {
    process: function(items){
        if (items != null){
            items.sort();
            items.forEach(function(item){
                //do something
            });
        }
    }
};




                Avoid null comparisons
var Controller = {
    process: function(items){
        if (items instanceof Array){
            items.sort();
            items.forEach(function(item){
                //do something
            });
        }
    }
};




                 Avoid null comparisons
         Test for precisely what you want to know if it matters
Avoid null comparisons
• Use instanceof to test for specific object
  types
   – object instanceof MyType
• Use typeof to test for primitive types
  – typeof value == "string"
  – BEWARE: typeof null == "object"
function validate(value) {
    if (!value) {
        alert("Invalid value");
        location.href = "/errors/invalid.php";
    }
}




              Separate config data
var config = {
    urls: {
        invalid: "/errors/invalid.php"
    },
    strs: {
        invalidmsg: "Invalid value"
    }
};


function validate(value) {
    if (!value) {
        alert(config.strs.invalidmsg);
        location.href = config.urls.invalid;
    }
}


                    Separate config data
Separate Config Data
• All URLs needed by the JavaScript
• Any strings that are displayed to the user
• Any HTML that needs to be created from
  JavaScript
• Settings (i.e., items per page)
• Repeated unique values
• Any value that may change in the future
Build Process


                Understandable


                           Testable
Build Process




   Build
Build
 Add/Remove              Validate
  Debugging               Code

 Concatenate
                        Minify Files
    Files

  Generate                Deploy
Documentation              Files
Build



Development   Testing   Deployment
Recommendations
• One object or object definition per file
  – Track dependencies
• Use a build process to combines files
  – Determines correct order
  – Validates code (JSHint)
  – Minifies code (YUI Compressor)
  – Generate documentation (YUI Doc)
http://www.julienlecomte.net/blog/2007/09/16/
Recap
Remember
• Code conventions ensure everyone's speaking
  the same language
• Loose coupling of layers make changes and
  debugging easier
• Good programming practices allow you to
• Code organization and a build process help to
  bring sanity to an otherwise crazy process
Questions?
Etcetera
• My blog:      www.nczonline.net
• Twitter:      @slicknet
• These Slides: slideshare.net/nzakas

Maintainable JavaScript 2011

  • 1.
    flickr.com/photos/jontysewell/4526861658/ Maintainable JavaScript Nicholas C. Zakas | @slicknet
  • 2.
    Who's this guy? 5 years Co-Creator Contributor, Tech Lead, Yahoo! csslint.net Creator of YUI Test Author Lead Author Contributor Lead Author
  • 3.
  • 4.
    flickr.com/photos/indraw/4857101224/ Most of your time is spent maintaining code
  • 5.
    Who cares? Your Employer Your Co-workers, Present and Future
  • 6.
    flickr.com/photos/protestphotos1/4726566233/ We all want to be rock stars "Don't mess with my process, man! It's about the music!"
  • 7.
    flickr.com/photos/the_junes/3120810156/ At work, you're part of a team Awesome happens when everyone is on the same page
  • 8.
    Maintainable code is Understandable Adaptable Debuggable Testable Intuitive Extendable
  • 9.
    Code Conventions Understandable Intuitive
  • 10.
    flickr.com/photos/29271559@N02/5799773313/ "Programs are meant to be read by humans and only incidentally for computers to execute." Donald Knuth
  • 11.
    flickr.com/photos/polinasergeeva/3052378826/ Tabs for indentation 4 spaces for indentation Indentation
  • 12.
    if (wl &&wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
  • 13.
    if (wl &&wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
  • 14.
  • 15.
    /** * Returnsa new object containing all of the properties of * all the supplied objects. The properties from later objects * will overwrite those in earlier objects. Passing in a * single object will create a shallow copy of it. For a deep * copy, use clone. * @method merge * @for YUI * @param arguments {Object*} the objects to merge. * @return {object} the new merged object. */ Y.merge = function() { var a = arguments, o = {}, i, l = a.length; for (i = 0; i < l; i = i + 1) { Y.mix(o, a[i], true); } return o; }; Every method
  • 16.
    if (mode) { switch (mode) { case 1: // proto to proto return Y.mix(r.prototype, s.prototype, ov, wl, 0, merge); case 2: // object to object and proto to proto Y.mix(r.prototype, s.prototype, ov, wl, 0, merge); break; // pass through case 3: // proto to static return Y.mix(r, s.prototype, ov, wl, 0, merge); case 4: // static to proto return Y.mix(r.prototype, s, ov, wl, 0, merge); default: // object to object is what happens below } } Difficult-to-understand code
  • 17.
    while (element &&(element= element[axis])){ //NOTE: assignment if ( (all || element[TAG_NAME]) && (!fn || fn(element)) ) { return element; } } Code that might seem to be wrong
  • 18.
  • 19.
    Naming • Use logicalnames for variables and functions – Don't worry about length • Variable names should be nouns • Function names should begin with a verb (i.e. getName()) – Functions return booleans should begin with "is", such as isValid() • Avoid useless names such as foo and temp
  • 20.
    if (wl &&wl.length) { for (i = 0, l = wl.length; i < l; ++i) { p = wl[i]; type = Y.Lang.type(r[p]); if (s.hasOwnProperty(p)) { if (merge && type == 'object') { Y.mix(r[p], s[p]); } else if (ov || !(p in r)) { r[p] = s[p]; } } } }
  • 21.
    Loose Coupling Adaptable Debuggable Extendable
  • 22.
    Front End Layers Presentation Behavior (CSS) (JavaScript) Base JS Data/Structure (HTML)
  • 23.
  • 24.
  • 25.
    var element =document.getElementById("container"); element.innerHTML = "<div class="popup"></div>"; Keep HTML out of JavaScript
  • 26.
    .foo { width: expression(document.offsetWidth + "px"); } Keep JavaScript out of CSS
  • 27.
    var element =document.getElementById("container"); element.style.color = "red"; element.style.cssText = "background:blue;border:1px solid red"; Keep CSS out of JavaScript
  • 28.
    Programming Practices Adaptable Debuggable Testable Extendable
  • 29.
    //the wrong way!!! functionhandleClick(event){ var popup = document.getElementById("popup"); popup.style.left = event.clientX + "px"; popup.style.top = event.clientY + "px"; popup.className = "reveal"; } Event handlers should only handle events
  • 30.
    //better, but stillwrong function handleClick(event){ showPopup(event); } function showPopup(event){ var popup = document.getElementById("popup"); popup.style.left = event.clientX + "px"; popup.style.top = event.clientY + "px"; popup.className = "reveal"; } Don't pass the event object around
  • 31.
    //win!! function handleClick(event){ showPopup(event.clientX, event.clientY); } function showPopup(x, y){ var popup = document.getElementById("popup"); popup.style.left = x + "px"; popup.style.top = y + "px"; popup.className = "reveal"; } Properly separated event handling
  • 32.
    //don't add newmethods Array.prototype.awYeah = function(){ alert("Aw yeah!"); }; //don't override methods YUI.use = function(){ alert("Aw yeah!"); }; Don't modify objects you don't own If you didn't define the object yourself, you don't own it
  • 33.
  • 34.
    function handleClick(event){ showPopup(event.clientX, event.clientY); } function showPopup(x, y){ var popup = document.getElementById("popup"); popup.style.left = x + "px"; popup.style.top = y + "px"; popup.className = "reveal"; } Avoid global functions and variables
  • 35.
    var Controller ={ handleClick: function(event){ this.showPopup(event.clientX, event.clientY); }, showPopup: function (x, y){ var popup = document.getElementById("popup"); popup.style.left = x + "px"; popup.style.top = y + "px"; popup.className = "reveal"; } }; Avoid global functions and variables Create a single global (if necessary) and attach everything to it
  • 36.
    var Controller ={ addClass: function(element, className){ element.className += " " + className; } }; Throw your own errors When you know a function will fail
  • 37.
    var Controller ={ addClass: function(element, className){ if (!element) { throw new Error("addClass: 1st argument missing."); } element.className += " " + className; } }; Throw your own errors When you know a function will fail
  • 38.
    var Controller ={ process: function(items){ if (items != null){ items.sort(); items.forEach(function(item){ //do something }); } } }; Avoid null comparisons
  • 39.
    var Controller ={ process: function(items){ if (items instanceof Array){ items.sort(); items.forEach(function(item){ //do something }); } } }; Avoid null comparisons Test for precisely what you want to know if it matters
  • 40.
    Avoid null comparisons •Use instanceof to test for specific object types – object instanceof MyType • Use typeof to test for primitive types – typeof value == "string" – BEWARE: typeof null == "object"
  • 41.
    function validate(value) { if (!value) { alert("Invalid value"); location.href = "/errors/invalid.php"; } } Separate config data
  • 42.
    var config ={ urls: { invalid: "/errors/invalid.php" }, strs: { invalidmsg: "Invalid value" } }; function validate(value) { if (!value) { alert(config.strs.invalidmsg); location.href = config.urls.invalid; } } Separate config data
  • 43.
    Separate Config Data •All URLs needed by the JavaScript • Any strings that are displayed to the user • Any HTML that needs to be created from JavaScript • Settings (i.e., items per page) • Repeated unique values • Any value that may change in the future
  • 44.
    Build Process Understandable Testable
  • 45.
  • 46.
    Build Add/Remove Validate Debugging Code Concatenate Minify Files Files Generate Deploy Documentation Files
  • 47.
    Build Development Testing Deployment
  • 48.
    Recommendations • One objector object definition per file – Track dependencies • Use a build process to combines files – Determines correct order – Validates code (JSHint) – Minifies code (YUI Compressor) – Generate documentation (YUI Doc)
  • 49.
  • 50.
  • 51.
    Remember • Code conventionsensure everyone's speaking the same language • Loose coupling of layers make changes and debugging easier • Good programming practices allow you to • Code organization and a build process help to bring sanity to an otherwise crazy process
  • 52.
  • 53.
    Etcetera • My blog: www.nczonline.net • Twitter: @slicknet • These Slides: slideshare.net/nzakas