Saturday, June 30, 2012

Eight Simple Rules for Maintainable Javascript

All projects start out small. This is true for the javascript part of any web app. In the beginning, it's always easy to read, and easy to debug.

I've put together some of my learnings for client side Javascript

1. Do not let your .js file be the entry point to your code

I've seen this anti pattern in most of the codebases I've inherited.

The Trap: The javascript that binds to the DOM

$(".submit-button").click(function(){
  server.submit();
});

This is a trap for multiple reasons:

  1. It's really hard to figure out what happened when you clicked on a button. Did some callback get triggered? Where is the code for this?
  2. You'll can easily get a bunch of callbacks that interfere with each other. Another page of mine has another button called submit button. I need to make sure that this JS is not loaded on that page
  3. It's not possible to compile all your javascript assets into a single application.js

Solution: Wrap everything that is binding to an element, and call that from the html

function bindElementToSubmit(element) {
  element.click(function() {
    server.submit();
  });
}

Binding things in the HTML helps you keep in your head what is going on in a particular page. It also helps you answer things like how an element is bound (was it ".submit-button", "#page-submit-button", or "form > .submit-button"), and makes sure that it's easy to find when searching for code later.

Caveat: One caveat to look out for is the order of loading of JavaScript files. The above code requires jQuery loaded for $(document).ready() to work. One alternative is to use window.onLoad(), though that waits for the entire DOM and assets to load.

2. Understand how this works in Javascript

Everything is lexically scoped in Javascript. Except for 'this', which is something that is set depending on how a method is called. Some libraries like jQuery even set the value of 'this' to mean things like the object on which a callback is called.

The Trap: Using a this pointer that's pointing at the wrong object.

$("some-div").click(function() {
  this.html("loading"); // replaces the current div with 'loading'
  $.get("/foobar", function(result){
    this.html(result); // no such function html!!
  });
});

Granted, this isn't all that dangerous because it will be obvious that your code is not working.

The Solution: Have a convention for what the current object is. I prefer self.

$("some-div").click(function() {
  var self = this;
  self.html("loading");
  $.get("/foobar", function(result){
    self.html(result);
  });
});

3. Structure your JS code into logical units

Javascript is a prototype based language. There are a lot of cool things you can do with a prototype based language, but very few of them will help you write a good UI. Most people take the lack of 'class' keyword in Javascript to mean the lack of Object Orientation.

The Trap: A bunch of methods floating around in space

function processResults(results) {
  // do Something
}

function bindElementToSubmit(element) {
  element.click(function(){
    server.submit(function(result){
      processResults(results);
    });
  });
}

bindElementToSubmit($(".submit-button"));

This is a trap because you aren't able to group logically related methods together. This would be similar to a bunch of static methods on your server side.

The Solution: Build some sort of object oriented abstraction over your javascript functions. You can either hand roll your own using javascripts prototype functionality, or you can just drop in something like class-js

SubmitPage = Class.extend({
  processResults: function(results) {
    // do Something
  },

  constructor: function(element) {
    var self = this;
    element.click(function(){
      server.submit(function(result){
       self.processResults(results);
      });
    });   
  }
});

new SubmitPage($(".submit-button"));

4. Use client side templates to render content

Again, this can go into another blog post by itself, and I'll be writing about this soon.

The Trap: Having your server return HTML output as the response to an AJAX request, and having the client just dump it into the DOM somewhere

The Solution: Use something like Mustache.js to render your server side template. I'll be posting more about this soon.

5. Test your javascript

Just because this is javascript, and not the backend code, does not mean that you should ignore your JS. JS faces just as many (if not loads more) regression bugs than your backend code.

The Trap: A lot of code without any test coverage

The Solution: Test your code with something like jasmine. Use one of the addons to jasmine like jasmine-jquery, which lets you bootstrap your tests with a sample DOM, and then run your tests against that DOM.

Check out the example specs

6. MetaProgramming #ftw

Ruby programmers would already be familiar with some sort of metaprogramming, and JS is almost as powerful. The main feature I miss from ruby is the method_missing.

You can open up a class and add methods:

String.prototype.alert = function() {
  alert(this);
};

"foobar".alert();

You can use [] like a poor man's send:

alertFunction = this["alert"];
alertFunction("blahblahblah"); // Note that alert will no longer get the same value of this

You can also use [] to define dynamic methods:

for(i = 0; i < 5; i++) { 
  var times = i; // need to save this in our closure
  String.prototype["alert" + i] = function() {
    alert(this + times);
  }
};

"foobar".alert3()

7. Know your javascript libraries

This could cover multiple blog posts in itself.

Know your options for manipulating the DOM, as well as other things you want to do

As a primer, check out the following projects:

8. Use CoffeeScript

CoffeeScript is a neat little language that compiles down to JS, and has support for almost all of these features baked right in. It namespaces things for you, and fixes the this/self problem, and provides a lot of the functionality that underscore would provide.

As an additional benefit, it's much less code to read. And it's baked right into rails.

You can read more on coffeescript.org


If you liked this post, you could:

upvote it on Hacker News
or just leave a comment

13 comments:

  1. good points !
    most of it i go with, except coffee script.

    ReplyDelete
  2. hahahaha half way through your blogpost, i was about to say: Duh. Use Coffeescript! and that was your last point! Good one :)

    Also

    http://saidinesh5.wordpress.com/2012/05/19/a-little-makefile-for-our-node-js-project-just-to-sooth-the-soul/

    Might interest you as far as your point 5. goes. feels right at home :)

    ReplyDelete
  3. #1 and #8 I don't agree. I am wary of languages which rely on indentations rather than braces and semi-colons. Perhaps none of your team had a mangled file due to mis-behaving editor

    ReplyDelete
    Replies
    1. Yeah, I'll give you the indentation aware language argument. It's my biggest gripe with CoffeeScript as well. But I still find it more readable than JS. If someone made a version of CoffeeScript stripping out the indenting part, I'd always use that.

      What don't you agree with in the first point?

      Delete
  4. Your points #2, #3 and #6 are merely a common-place misunderstanding of what Javascript is. Javascript is unfortunately treated like trash, while it is a very powerful language that has a programming model quite different from your average OO language. Trying to fit the OO model onto JS is like driving a Ferrari on our potholed roads :)

    ReplyDelete
    Replies
    1. Hi SuVish,

      Once question I keep asking around, is what kind of apps naturally lend themselves to the javascript prototyping? Javascript is a classless OO language. As an application developer what benefits do I get because of it?

      Tejas

      Delete
  5. Dear Tejas Dinkar,
    I would like to comment your examples for at least #1. The others - another day :)

    jQuery doesn't actually bind anything to the DOM element (perhaps it does in older versions...don't know for a fact). Newer versions of jQuery has an, lets call it; internal memory bank. Each DOM element that gets any handler bound to it, is tagged with the expando-attribute (the key). When an event is triggered, jQuery reads the "key" value and opens the corresponding "safe". The "safe" contains a stack of all handlers that was bound to the element and starts to dispath them one by one, with the right context (-> this) etc.

    If an element is removed with jQuery's help, then the corresponding safe is emptied...but if the element is removed without jQuery's help than the safe remains untouched, causing unnecessary memory allocation. Get this right - this is much better solution than letting GC for JS and DOM trying to clean up...thanks to jQuery, the "garbage" is at least not in the "limbo".

    The problems with your examples, both the problem och solution, are that you are binding anonymous functions. Consequenences of doing that way are;

    1. You can't unbind functions

    2. If your page contains multiple submit-button's, then the function has to be instantiated in memory, in order to be put in their own "safe". IOW; if you bind an anonymous function to 1000 elements, in jQuery bank, they have to exist individually in each safe, instead of referencing to named function.

    So, the correct way to "bind" an handler to an element is to first create a reference to the handler/function and bind using the reference.

    ReplyDelete
    Replies
    1. Hi Hakan,

      Thanks for the feedback. I did not know this about jQuery methods.

      In any case, the point I am trying to emphasize is #1 is not to let your JS be the entry point, purely because if you do any binding in the JS, it's typically harder for a developer to track down what's happening. As such, it's orthogonal to your point.

      However, are you saying that the first problem can be improved as follows?
      var submitCallback = function(event) { server.submit(); };
      $(".submit-button").click(submitCallback);

      Delete
    2. Dear gja,
      My suggestion is a general recommendation. Binding to a named function has advantages. Furthermore, it's easier to trace with Firebug profiler and similar tools.

      $(".submit-button").bind('click', submitCallback);
      $(".submit-button").unbind('click', submitCallback);

      I, myself, create a singleton object and structure my ojbects and functions, helping me to develop as well as re-read my old code - hence an easier codeflow and structure.

      Having said that; I like that pattern and promote that way, of course. But everyone should write what ever suits them best. For me, it's easier to "follow" named functions. This kind of discussions tend easily blow out to unnecessary loud debates and that's not what I desire :)

      Though, one thing I would like to suggest is to use;

      'use strict';

      ...if I was a beginner, and I was only allowed to give myself one advice, this would be it. It has many advantages. Among others;

      1. eliminates the potential risk of variables ending up in the global scope...resulting in slow execution performance (scope chain)

      2. minimizes (but not eliminating :( ) the risk of silent errors. I truly hate when browsers are failing silently.

      Sincerely

      Delete
  6. Your this blog giving us very much information thanks for share this.
    access Bomb-mp3 in UK

    ReplyDelete
  7. đồng tâm
    game mu
    cho thuê nhà trọ
    cho thuê phòng trọ
    nhac san cuc manh
    số điện thoại tư vấn pháp luật miễn phí
    văn phòng luật
    tổng đài tư vấn pháp luật
    dịch vụ thành lập công ty trọn gói
    lý thuyết trò chơi trong kinh tế học
    đức phật và nàng audio
    hồ sơ mật dinh độc lập audio
    đừng hoang tưởng về biển lớn ebook
    chiến thắng trò chơi cuộc sống ebook
    bước nhảy lượng tử
    ngồi khóc trên cây audio
    truy tìm ký ức audio
    mặt dày tâm đen audio
    thế giới như tôi thấy ebook

    “Điện hạ, tiểu nhân tự biết như thế nào. Cho dù chết cũng hoàn thành tâm nguyện cho người. Điện hạ, người hãy chờ tin tốt lành của tiểu nhân.” Xoay người bỏ đi nhưng tâm trạng của Triết Biệt rất phức tạp. Hắn mặc dù là một nam nhân bộc trực nhưng không phải là một kẻ ngu. Kỳ thật hắn rất coi trọng tính mạng của mình. Nhưng đối mặt với nữ nhân này, hắn có thể bỏ mạng vì nàng. Hắn cũng không biết làm vậy có đúng hay không, chỉ biết là hắn rất cam tâm tình nguyện làm việc này.


    Thấy hình bóng Triết Biệt khuất dần. Thái tử phi tựa hồ như nhìn thấy được hình ảnh Lưu Phong thống khổ chết đi.

    ……………

    “Kéo ra chút nữa, hở ra chút nữa….” Tại cửa sổ phía trước căn phòng Bạch Vũ. Một người đang khom lưng, miệng lẩm bẩm điều gì đó.

    Lúc này đang là giữa trưa. Bạch Vũ tối qua làm việc quá muộn nên thân thể có chút mệt mỏi. Ăn cơm trưa xong liền nghỉ ngơi. Khuôn mặt nàng khi ngủ vẫn biểu lộ ra vẻ tươi cười. Mái tóc xõa ra tự nhiên, đôi môi căng mọng xinh xắn, gương mặt hồn nhiên ngây thơ, tinh xảo thực sự là quyến rũ kẻ khác.

    Chăn nàng lúc này đã bị trễ xuống một nửa. Có thể thấy nàng đang ngủ rất say.

    Không được chăn che phủ, một nửa thân thể của Bạch Vũ lộ ra, áo ngủ màu đỏ, mỏng manh không thể che dấu được bộ ngực nhấp nhô theo hơi thở, làn da trắng muốt lồ lộ khêu gợi, mời gọi.

    Nhưng làm cho Lưu Phong hưng phấn nhất chính là Bạch Vũ chỉ mặc áo ngủ mà không hề mặc nội y. Dưới ánh sáng mặt trời xuyên qua cửa sổ, hắn có thể dễ dàng nhìn thấy hai đầu vú vươn cao, hai núm vú nhỏ nhắn kiêu hãnh nổi bật lên trên lớp áo ngủ.

    Đối mặt với tặc nhãn của Lưu Phong. Mỹ nữ này không hề biết mà khuôn mặt xinh đẹp tuyệt trần còn như hé nở một nụ cười. Điều chỉnh lại góc độ, Lưu Phong từ từ quan sát kỹ bộ ngực sữa của Bạch Vũ.

    “A!”

    ReplyDelete
  8. Another feature is that it can be used on client side as well as server side. A JavaScript has access to Document object model of browser; you actually change the structure of the web pages at run time.
    web design lessons

    ReplyDelete