Nettuts+ Updates - Key Principles of Maintainable JavaScript
|
Key Principles of Maintainable JavaScript Posted: 20 Jun 2012 01:41 PM PDT JavaScript is a curious language. It’s easy to write, but difficult to master. By the end of this article, hopefully, you’ll transform your spaghetti code into a five-course meal, full of readable, maintainable yumminess! Why is it So Tough?The thing to remember, above all else when writing JS code, is that it’s a dynamic language. This means there are a lot of ways to do things. You don’t have to deal with strongly typed classes, or some of the more complex features from languages, like C# and Java. This is both a blessing and a curse. The “hardness” of JavaScript is clearly evident when considering the following image: The teeny tiny book on the left is Douglas Crockford’s MUST READ book, JavaScript: The Good Parts. Towering next to it, on the right, is, JavaScript The Definitive Guide, by David Flanagan. While both of these books are excellent reads, The Good Parts illustrates that, although JavaScript has a LOT of stuff in it, the good parts can be summed up in a considerably shorter read. So, if you’re looking for a good, quick read, go with The Good Parts – and read it a few times!
You can read an article on the history of JavaScript here, but the gist of it is that Brandon Eich, in 1995, was hired by Netscape to design a language. What he came up with was the loosely typed language that we know as JavaScript. Over the years, it became “standardized” as ECMAscript, but, throughout all the browser wars, the various browsers implemented these features differently. This, naturally, lead to a lot of sleepless nights for web developers. This problem, when combined with the fact that JavaScript was considered to be most applicable for manipulating images and performing quick bits of validation, led JavaScript to, incorrectly, be viewed as a terrible language. It’s time to fix that! While, yes, there are plenty of bad things about JavaScript, when used properly, it can be a fantastic language – and it’s dynamic nature will grow on you! Making it BetterNamespaces
One of the downfalls of how JavaScript is implemented is that it operates on top
of a global object. In the case of browsers, this will wind up
being the function doStuff(){ alert('I am doing stuff'); } function doMoreStuff(){ var images = document.images.length; console.log("There are " + images + "on this page"); } doStuff(); doMoreStuff();
The functions window.DoMoreStuff
This means that if anyone comes along and attempts to write a function, which is
also called, 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 NS = NS || {}; // "If NS is not defined, make it equal to an empty object" NS.Utils = NS.Utils || {}; NS.Models = NS.Models || {}; NS.Views = NS.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 Design Patterns and PracticesIn every language, there exists a set of design patterns. Addy Osmani says…
There are lots, and, when used correctly, they can greatly impact your application’s maintainabilty. Addy wrote a great JavaScript design patterns book, called Essential Design Patterns. Absolutely give it a read! Another commonly used pattern is the Revealing Module Pattern. NS.App = (function () { // Initialize the application var init = function () { NS.Utils.log('Application initialized...'); }; // Return the public facing methods for the App return { init: init }; }()); NS.App.init();
Above, an 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
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,
Next, there’s a private variable, called
Next, we wrap the App 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! Observer Pattern
Another excellent pattern is the Observer Pattern – sometimes referred to
as “Pubsub.” Pubsub is essentially allows us to subscribe to DOM events,
such as // 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 (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.Login.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 // 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 Documentation and Files/MinificationOne 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:
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 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 // 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
Then, there is ConclusionKeeping your applications maintainable is extremely important for development. It reduces bugs, and makes the process of fixing ones that you do find easier.
|