Maintainable JavaScript

If you are working on a project by your own, you might not worry about the structure, convention and organisation of your code given you fully understand how it works. But working in a team environment it is necessary to make sure that your team members understand your code, easily adaptable and extensible so the importance of maintainable javascript is inevitable.


Why is it challenging?

Javascript is a dynamic langauge, which meanins you don’t have to deal with strong typing and the complexities of the other object oriented languages like C#, C++ and Java. You can do things in a lot of different ways, but this can be a  blessing in disguise. But this same dynamic feature can also be used to make it a great tool, which is what we are discussing here.


Design Patterns and Practices

Design patterns are reusable solutions to commonly occurring problems in software design. There are lots out there, but you have to make sure to select the most relevant pattern for your requirement. Another commonly used pattern is the Revealing Module Pattern.

My.App = (function () {
// Initialize the application
var init = function () {
My.Utils.log('Application initialized...');
};
// Return the public facing methods for the App
return {
init: init
};
}());
My.App.init();

Above, an App function is defined within the My object. Inside, a function variable for init is defined, and returned as an anonymous object literal. Notice that, at the end, there’s that extra set of parenthesis: }());. This forces the My.App function to automatically execute and return. Now, you can call My.App.init() to initialize your app. The anonymous function above is a best practice in JavaScript, and is referred to as a Self-Executing Anonymous Function. Because functions in JavaScript have their own scope – i.e. variables defined inside of functions are not available outside of them – this makes anonymous functions useful in multiple ways.

// Wrap your code in a SEAF
(function (global) {
// Now any variables you declare in here are unavailable outside.
var somethingPrivate = 'you cant get to me!';
global.somethingPublic = 'but you can however get to me!';
}(window));
console.log(window.somethingPublic); // This works...
console.log(somethingPrivate); // Error

In this example, because this function is automatically executed, you can pass the window in to the executing part }(window));, and it will be made available asglobal inside of the anonymous function. This practice limits the global variables on the window object, and will assist in preventing naming collisions. Now, you can start using SEAF’s in other areas of your application to make the code feel more modular. This allows for your code to be re-usable, and promotes good separation of concerns. Here’s an example of a potential use for these ideas.

(function ($) {
var welcomeMessage = 'Welcome to this application!'
NS.Views.WelcomeScreen = function () {
this.welcome = $('#welcome');
};
NS.Views.WelcomeScreen.prototype = {
showWelcome: function () {
this.welcome.html(welcomeMessage)
.show();
}
};
}(jQuery));
$(function () {
NS.App.init();
});
// Modify the App.init above
var init = function () {
NS.Utils.log('Application initialized...');
this.welcome = new NS.Views.WelcomeScreen();
this.welcome.showWelcome();
};

So, above, there are a few different things going on. Firstly, jQuery is passed as an argument to the anonymous function. This ensures that the $ is actually jQuery inside of the anonymous function. Next, there’s a private variable, called welcomeMessage, and a function is assigned to NS.Views.WelcomeScreen. Inside this function, this.welcome is assigned to a jQuery DOM selector. This caches the selector inside the welcomeScreen, so that jQuery doesn’t have to query the DOM for it more than once. DOM queries can be memory intensive, so please ensure that you cache them as much as possible. Next, we wrap the App init within $(function(){});, which is the same thing as doing $(document).ready(). Finally, we add some code to the app initializer. This keeps your code nice and separated, and will be considerably easy to come back to and modify at a later day. More maintainability!


Namespaces

One of the disadvantages of JavaScript is that it operates on top of a global object, which is  the the window object of the browsers .

function doSomething(){
alert('I am doing something');
}
function doSomethingMore(){
alert('I am doing more things');
}
doSomething();
doSomethingMore();

The functions doSomething and the doSomethingMore functions are immediately available to the global window object.

This means that if anyone comes along and attempts to write a function, which is also called, doStuff, there will be a conflict! All script tags are basically taking the code within them, and running it against the window in the order that they are referenced in the HTML. As a result, the second person to implement doStuffwill overwrite the first doStuff. A common technique for eliminating this problem is to take advantage of either self-executing anonymous functions, or namespaces. The object-oriented folks reading this are likely already familiar with the concept of a namespace, but the basic idea is to group functions into different areas for re-usability.

var My = My || {}; // "If My is not defined, make it equal to an empty object"
My.Utils = My.Utils || {};
My.Models = My.Models || {};
My.Views = My.Views || {};

This will prevent pollution of the global namespace, and will aid in readability for your application. Now, you simply define functions in their respective namespace. A commonly defined namespace is app, which manages the rest of the application.


 Observer Pattern

Another excellent pattern is the Observer Pattern – sometimes referred to as “Pubsub.” Pubsub essentially allows us to subscribe to DOM events, such asclick and mouseover. On one hand, we’re listening to these events, and, on the other, something is publishing those events – for example, when the browser publishes (or announces) that someone clicked on a particular element. There are many libraries for pubsub, as it’s a short bit of code. Perform a quick Google search, and thousands of choices will make themselves available. One solid choice is AmplifyJS’s implementation.

// A data model for retrieving news.
NS.Models.News = (function () {
var newsUrl = '/news/'
// Retrieve the news
var getNews = function () {
$.ajax({
url: newsUrl
type: 'get',
success: newsRetrieved
});
};
var newsRetrieved = function (news) {
// Publish the retrieval of the news
amplify.publish('news-retrieved', news);
};
return {
getNews: getNews
};
}());

This code defines a model to fetch news from some kind of service. Once the news has been retrieved with AJAX, the newsRetrieved method fires, passing through the retrieved news to Amplify, and is published on the news-retrieved topic.

(function () {
// Create a news views.
NS.Views.News = function () {
this.news = $('#news');
// Subscribe to the news retrieval event.
amplify.subscribe('news-retrieved', $.proxy(this.showNews));
};
// Show the news when it arrives
NS.Views.News.prototype.showNews = function (news) {
var self = this;
$.each(news, function (article) {
self.append(article);
});
};
}());

This code above is a view for displaying the retrieved news. In the Newsconstructor, Amplify subscribes to the news-retrieved topic. When that topic is published, the showNews function is fired, accordingly. Then, the news is appended to the DOM.

// Modify this the App.init above
var init = function () {
NS.Utils.log('Application initialized...');
this.welcome = new NS.Views.WelcomeScreen();
this.welcome.showWelcome();
this.news = new NS.Views.News();
// Go get the news!
NS.Models.News.getNews();
};

Again, modify the init function from the app to add the news retrieval… and you’re done! Now, there are separate pieces of the application, each of which is responsible for a single action. This is known as the Single Responsibility Principle .


Commenting, Documentation and Files/Minification

One of the keys to maintainable code of any kind – not just JS – is documentation and commenting. Comments can serve to be invaluble for new developers coming into a project – needing to understand what’s occurring in the code. “Why did I write that one line again?”. An excellent tool for generating documentation is called, Docco . This is the same tool that generates the documentation for the Backbone.js web site. Basically, it takes your comments, and places them side by side with your code. There are also tools, like JSDoc , which generate an API style documentation, describing every class in your code. Another thing, which can prove to be difficult when starting a new project, is trying to determine how to best organize your code. One way is to separate pieces of functionality into separate folders. For example:

  • /app.js
  • /libs/jquery.js
  • /libs/jquery-ui.js
  • /users/user.js
  • /views/home.js

This structure helps keep pieces of functionallity apart from one another. There are, of course, several ways to organize code, but all that really matters is deciding on a structure… and then rolling with it. Next, you can make use of a build and minification tool. There are lots of choices: These tools will strip out whitespace, remove comments, and combine all specified files into one. This reduces the file sizes and HTTP requests for the application. Even better, this means that you can keep all of your files separated during development, but combined for production.


AMD (Asynchronous Module Definition)

Asynchronous Module Definition is a different way of writing JavaScript code; it divides all code into separate modules. AMD creates a standard pattern for writing these modules to load in code asynchronously. Using script tags blocks the page, as it loads until the DOM is ready. Therefore, using something like AMD will allow the DOM to continue loading, while the scripts are also still loading. Essentially, each module is divided into its own file, and then there’s one file that kicks off the process. The most popular implementation of AMD is RequireJS .

// main.js
require(['libs/jquery','app.js'], function ($, app) {
$(function () {
app.init();
});
});
// app.js
define(['libs/jquery''views/home'], function ($, home) {
home.showWelcome();
});
// home.js
define(['libs/jquery'], function ($) {
var home = function () {
this.home = $('#home');
};
home.prototype.showWelcome = function () {
this.home.html('Welcome!');
};
return new home();
});

In the code snippet above, there is a main.js file, which is where the process begins. The first argument to the require function is an array of dependencies.These dependencies are a list of files that are required for app.js. As they finish loading, whatever the module returns is passed as an argument to the function callback on the right. Then, there is app.js, which requires jQuery, as well as a view. Next, the view,home.js, only requires jQuery. It has a home function within it, and returns an instance of itself. In your application, these modules are all stored within separate files, making your application very maintainable.


Conclusion

Keeping your applications maintainable is extremely important for development. It reduces bugs, and makes the process of fixing ones that you do find easier.

Be first to comment

Leave a Reply