Wait till I come! » Blog Archive » Five things to do to a script before handing it over to the next developer
Random notes by Chris Heilmann

Five things to do to a script before handing it over to the next developer

Let’s face fact folks: not too many developers plan their JavaScripts. Instead we quickly write something that works, and submit it. We come up with variable and function names as we go along and in the end know that we’ll never have to see this little bit of script ever again.

The problems start when we do see our script again, or we get scripts from other developers, that were built the same way. That’s why it is good to keep a few extra steps in mind when it comes to saying “this is done, I can go on”.

Let’s say the job was to add small link to every DIV in a document with the class collapsible that would show and hide the DIV. The first thing to do would be to use a library to get around the issues of cross-browser event handling. Let’s not concentrate on that for the moment but go for oldschool onevent handlers as we’re talking about different things here. Using a module pattern we can create functionality like that with a few lines of code:


collapser = function(){

var secs = document.getElementsByTagName('div'); for(var i=0;i<secs.length;i++){ if(secs[i].className.indexOf('collapsible')!==-1){ var p = document.createElement('p'); var a = document.createElement('a'); a.setAttribute('href','#'); a.onclick = function(){ var sec = this.parentNode.nextSibling; if(sec.style.display === 'none'){ sec.style.display = 'block'; this.firstChild.nodeValue = 'collapse' } else { sec.style.display = 'none'; this.firstChild.nodeValue = 'expand' } return false; }; a.appendChild(document.createTextNode('expand')); p.appendChild(a); secs[i].style.display = 'none'; secs[i].parentNode.insertBefore(p,secs[i]); } } }();

This is already rather clean (I am sure you’ve seen innerHTML solutions with javascript: links) and unobtrusive, but there are some things that should not be there.

Step 1: Remove look and feel

The first thing to do is not to manipulate the style collection in JavaScript but leave the look and feel to where it belongs: the CSS. This allows for ease of skinning and changing the way of hiding the sections without having to mess around in the JavaScript. We can do this by assigning a CSS class and removing it:

collapser = function(){
  var secs = document.getElementsByTagName('div');
  for(var i=0;i<secs.length;i++){
    if(secs[i].className.indexOf('collapsible')!==-1){
      secs[i].className += ' ' + 'collapsed';
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = function(){
        var sec = this.parentNode.nextSibling;
        if(sec.className.indexOf('collapsed')!==-1){
          sec.className = sec.className.replace(' collapsed','');
          this.firstChild.nodeValue = 'collapse'
        } else {
          sec.className += ' ' + 'collapsed';
          this.firstChild.nodeValue = 'expand'
        }
        return false;
      }
      a.appendChild(document.createTextNode('expand'));
      p.appendChild(a);
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
}();

Step 2: Remove obvious speed issues

There are not many issues in this script, but two things are obvious: the for loop reads out the length attribute of the secs collection on every iteration and we create the same anonymous function for each link to show and hide the section. Caching the length in another variable and creating a named function that gets re-used makes more sense:


collapser = function(){
  var secs = document.getElementsByTagName('div');
  for(var i=0,j=secs.length;i<j;i++){
    if(secs[i].className.indexOf('collapsible')!==-1){
      secs[i].className += ' ' + 'collapsed';
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = toggle;
      a.appendChild(document.createTextNode('expand'));
      p.appendChild(a);
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
  function toggle(){
    var sec = this.parentNode.nextSibling;
    if(sec.className.indexOf('collapsed')!==-1){
      sec.className = sec.className.replace(' collapsed','');
      this.firstChild.nodeValue = 'collapse'
    } else {
      sec.className += ' ' + 'collapsed';
      this.firstChild.nodeValue = 'expand'
    }
    return false;
  }
}();

Step 3: Removing every label and name from the functional code

This makes a lot of sense in terms of maintenance. Of course it is easy to do a quick search + replace when the label names or class names have to change, but it is not really necessary. By moving everything human readable into an own config object you won’t have to hunt through the code and suffer search + replace errors, but instead keep all the changing bits and bobs in one place:


collapser = function(){
  var config = {
    indicatorClass : 'collapsible',
    collapsedClass : 'collapsed',
    collapseLabel : 'collapse',
    expandLabel : 'expand'
  }
  var secs = document.getElementsByTagName('div');
  for(var i=0,j=secs.length;i<j;i++){
    if(secs[i].className.indexOf(config.indicatorClass)!==-1){
      secs[i].className += ' ' + config.collapsedClass;
      var p = document.createElement('p');
      var a = document.createElement('a');
      a.setAttribute('href','#');
      a.onclick = toggle;
      a.appendChild(document.createTextNode(config.expandLabel));
      p.appendChild(a);
      secs[i].parentNode.insertBefore(p,secs[i]);
    }
  }
  function toggle(){
    var sec = this.parentNode.nextSibling;
    if(sec.className.indexOf(config.collapsedClass)!==-1){
      sec.className = sec.className.replace(' ' + config.collapsedClass,'');
      this.firstChild.nodeValue = config.collapseLabel
    } else {
      sec.className += ' ' + config.collapsedClass;
      this.firstChild.nodeValue = config.expandLabel
    }
    return false;
  }
}();

Step 4: Use human-readable variable and method names

This is probably the most useful step when it comes to increasing the maintainability of your code. Sure, during development sec made a lot of sense, but doesn’t section make it easier to grasp what is going on? What about a, and especially when it needs to be changed to a button later on? Will the maintainer rename it to button?


collapser = function(){
  var config = {
    indicatorClass : 'collapsible',
    collapsedClass : 'collapsed',
    collapseLabel : 'collapse',
    expandLabel : 'expand'
  }
  var sections = document.getElementsByTagName('div');
  for(var i=0,j=sections.length;i<j;i++){
    if(sections[i].className.indexOf(config.indicatorClass) !== -1){
      sections[i].className += ' ' + config.collapsedClass;
      var paragraph = document.createElement('p');
      var trigger = document.createElement('a');
      trigger.setAttribute('href','#');
      trigger.onclick = toggleSection;
      trigger.appendChild(document.createTextNode(config.expandLabel));
      paragraph.appendChild(trigger);
      sections[i].parentNode.insertBefore(paragraph,sections[i]);
    }
  }
  function toggleSection(){
    var section = this.parentNode.nextSibling;
    if(section.className.indexOf(config.collapsedClass) !== -1){
      section.className = section.className.replace(' ' + config.collapsedClass,'');
      this.firstChild.nodeValue = config.collapseLabel
    } else {
      section.className += ' ' + config.collapsedClass;
      this.firstChild.nodeValue = config.expandLabel
    }
    return false;
  }
}();

Step 5: Comment, sign and possibly eliminate the last remaining clash with other scripts

The last step is to add comments where they are really needed, give your name and date (so people can ask questions and know when this was done), and to be really safe we can even get rid of the name of the script and keep it an anonymous pattern.


//  Collapse and expand section of the page with a certain class
//  written by Christian Heilmann, 07/01/08
(function(){

  // Configuration, change CSS class names and labels here
  var config = {
    indicatorClass : 'collapsible',
    collapsedClass : 'collapsed',
    collapseLabel : 'collapse',
    expandLabel : 'expand'
  }

  var sections = document.getElementsByTagName('div');
  for(var i=0,j=sections.length;i<j;i++){
    if(sections[i].className.indexOf(config.indicatorClass)!==-1){
      sections[i].className += ' ' + config.collapsedClass;
      var paragraph = document.createElement('p');
      var triggerLink = document.createElement('a');
      triggerLink.setAttribute('href','#');
      triggerLink.onclick = toggleSection;
      triggerLink.appendChild(document.createTextNode(config.expandLabel));
      paragraph.appendChild(triggerLink);
      sections[i].parentNode.insertBefore(paragraph,sections[i]);
    }
  }
  function toggleSection(){
    var section = this.parentNode.nextSibling;
    if(section.className.indexOf(config.collapsedClass)!==-1){
      section.className = section.className.replace(' ' + config.collapsedClass,'');
      this.firstChild.nodeValue = config.collapseLabel
    } else {
      section.className += ' ' + config.collapsedClass;
      this.firstChild.nodeValue = config.expandLabel
    }
    return false;
  }
})();

All very obvious things, and I am sure we’ve all done them before, but let’s be honest: how often do we forget them and how often do you have to alter code where it’d have been nice if someone had taken these steps?

Technorati Tags: , , , , , , ,

14 Responses to “Five things to do to a script before handing it over to the next developer”

  1. Binny V A Says:

    Use a javascript library rather than trusting on your own homebrew solutions. Of course, this is not something to be done before turning it over - but a decision that should be made at the beginning. But still, the next developer will thank you for it.

  2. bugrain Says:

    Some good tips there, essentially an exercise in good design for more reasons that just those outlined above.

    "...not too many developers plan their JavaScripts..."

    In my experience, this is because JavaScript was not treated as a serious language. Thankfully, the world is changing.

    "...add comments where they are really needed, give your name and date..."

    or even better, use a version control system that records this information.

  3. Michael Thompson Says:

    Step 6: Use whitespace extensively and consistently.

    Toss an extra line or two between functions, loops or variable declarations. Indent like you're writing Python.

    An improperly spaced/tabbed (spaces if you're a ninja) block of code squished atop other improperly spaced/tabbed code is a true sign of an inconsiderate coder. If you're going to be handing your work off to someone else you need to be sure that they can read the code. For the same reason you might update variable names to be a bit more human-friendly, so should you use whitespace.

    Also, if you're using a text editor in Windows, be sure to save the file in a non-Windows standard to avoid those damned ^M's at the end of each line.

  4. Tim Kadlec Says:

    Excellent post! Like you said, obvious things that we've all done before at some point, but definitely worth being reminded about.

    I also agree with Michael - use whitespace consistently. Indentation can go a long way towards making code more readable and usable.

  5. Steven Clark Says:

    Top advice... maintainability and having the next person pick up the code and not be out in the cold is essential software engineering.

    Above all else it makes for an efficient business process (rather than a bedroom black art).

  6. Ward Says:

    Sweet post! Gotta love the homebrew sleight : ) in the first comment by good ol Binny.

  7. Eric Anderson Says:

    The problem with the refactoring without using a library is that using a library would completely change the code. Here is my version using prototype (although I am sure you can do similar or better stuff with JQuery, YUI, Dojo or whatever your favorite flavor is):

    Event.observe(window, 'dom:loaded', function() {

    $$('div.collapsible').each(function(div) {
    var trigger = (new Element('a')).update('expand');
    div.insert({before: new Element('p').update(trigger)}).hide();
    trigger.observe('click', function(event) {
    div.toggle();
    trigger.update(div.visible() ? 'collapse', 'expand');
    event.stop();
    });
    });
    });

    This is just off the top of my head so it has not be tested for performance, memory leaks or even correctness.

    I know you are just using your code as a demonstration of your techniques. My only point is that using a library will greatly affect how your code is structured. A few things I did differently:

    • I left the style code in the JavaScript. The reason is because collapsing and showing are a functional aspect not style aspect. You just happen to be using CSS to implement that functionality. If you removed the CSS (which should be ok since it is just style) then you loose the functionality (collapsing and hiding). What skinning would you want to do? Now if you had other styles issues (maybe gray out a collapsed item or something) then sure add and remove a class name is fine.
    • Put the whole thing in the DOM content loaded event so that it will be automatically executed when the page loads. This way the JS can stay in a JavaScript file in the header (where it should be) and not be forced to be placed at the bottom of a page.
    • Left the labels inline. If I had a large script then I "might" put them in some sort of config but the best thing to do is just avoid a large script. That is the advantage with using a library from the start. It keeps the script small enough that you don't need things like config.
  8. Alan Liang Says:

    I think all code should be written with maintainability and readability in mind at all times, especially if it is in the team's repository. I don't believe it is feasible to do the above tasks you mentioned every time you need to pass on your work. This incurs wasted time. I especially do not think it is a good idea to strip away code optimizations so that it is easier for the next developer to understand the code. The next person will realize the speed optimization themselves and will eventually modify the code regardless. Basically, my argument is that the amount of time spent on the things you've stated is much more than the amount of time it takes for the developer to understand the code without modifications. I believe developers should be smart enough to do that.

  9. Jason Leveille Says:

    I just want to thank you for posting articles such as these. I am constantly looking for best practices and what you have described I see as very elegant and human. Getting into the habit of developing this way may incur some additional time up front, but once the habit is established the long term gains in productivity and maintainability will likely far outweigh any initial time cost.

    Again, thanks.

  10. shyam Says:

    mismatched quotes in line no. 11 on the first code snippet

    if(sec.style.display === none’){

    Edit Good catch, this was actually the Wordpress Textile plugin as it consider === to be an aligned ==.

  11. Steve Streza Says:

    As a professional JavaScipt developer, I just want to point out that for performance reasons, you should be using innerHTML instead of document.createElement.

    http://ajaxian.com/archives/benchmark-dom-vs-innerhtml

    Regarding libraries like jQuery, Dojo, etc., please. If you're doing simple little one-off scripts, don't use a library. It slows down your web site load, and can drive traffic away. If you absolutely do need their functionality (like, say, for jQuery's animation), go for it. But something like this is overkill for a framework.

  12. Chris Says:

    Steve, yes and no, I’d be very careful with truisms and developing with premature optimization. I got contacted by Apple that on the iPhone things might actually look the other way around and I will do some testing of that hypothesis soon.

    To me innerHTML is dangerous for maintenance as it assumes the developer knows HTML as much as the DOM.

    You see it with CMS: you allow people to edit HTML and you will get mismatched tags, deprecated tags and mixing of presentation and structure galore.

    While creating elements with the DOM might be marginally slower it actually leaves you with a trail of what you have done and you can access each element and child you created. It also means your code will be valid and working markup as the browser generates it for you. I’ve blogged about this here in the past and made my points there.

  13. Jonathan Aquino Says:

    I like the tip about signing your work. I read a similar thing in The Pragmatic Programmer.

    [Jon Aquino 2008-02-08]

  14. Frode Danielsen Says:

    A lot of good tips in this article! I doubt you considered this a perfect and polished example all the way through, mostly a demonstration of the actual tips. But I'd like to point out one thing that caught my eye. Wouldn't it be better to leave out DOM dependencies from the trigger function? I mean, in a larger code base you'd never be 100% sure the link and it's container paragraph would always be the previous sibling of the section div. Maybe the section div had another class, which some other JavaScript hooked up against and also generated a previous sibling of the div, eschewing your paragraph and trigger link.

Leave a Reply