Maintainable JavaScript: Don’t modify objects you don’t own
The first talk I gave after arriving at Yahoo! was entitled Maintainable JavaScript (video). As with most topics I write or speak about, I didn’t think it would be terribly controversial. The basis of the talk is that hacking around on your own and writing code in an enterprise environment are two different things. Web developers are truly unique in that none of us learned what we know in school; we all began as hobbyists one way or another and taught ourselves most (if not all) of what we know.
Professionalization
The professionalization of web development has been a difficult journey because of our disparate beginnings. Even those who end up at large companies such as Yahoo! inevitably began on their own, hacking around. Perhaps you were even “the web guy” at a small company and could do pretty much whatever you wanted. When the large companies started tapping this previously undiscovered resource, it brought a lot of hackers into a corporate environment where they were met with constraints. No longer a lone soldier in a small battle, all of these self-taught, self-directed individuals had to figure out how to work together as a team.
At the time that I gave the talk (2007), web development was evolving into front-end engineering and people were having trouble with the transition. Smart folks like Nate Koechley talked about the professionalization of front-end engineering (video) and how our discipline was evolving. My talk was aimed at the same goal: helping front-end engineers adapt to JavaScript development in a team environment by making sure that their code was as maintainable as possible.
Why can’t I modify objects I don’t own?
I still gets email and comments about Maintainable JavaScript, and the most popular question is, “why can’t I modify objects I don’t own?” JavaScript is, of course, a dynamic language that allows you to add and remove objects and their members at any point in time. For many, this is precisely why they enjoy the language: there are very few constraints imposed by the language. And I was telling them not to do this. Why?
Dependability
The simple explanation is that an enterprise software product needs a consistent and dependable execution environment to be maintainable. In other languages, you consider already-existing objects as libraries for you to use in order to complete your task. In JavaScript, people saw already-existing objects as a playground in which you could do anything you wanted. My point was that you should treat the already-existing JavaScript objects as you would a library of utilities. Don’t override methods, don’t add new methods, don’t remove existing methods.
When you’re the only one working on a project, it’s easy to get away with these types of modifications because you know them and expect them. When working with a team on a large project, making changes like this cause mass confusion and a lot of lost time. I still remember a bug that occurred while working on My Yahoo! because someone had overridden YAHOO.util.Event.stopEvent()
to do something else. It took days to track this problem down because we all assumed that this method was doing exactly what it always did. Once we discovered this, we also found other bugs because the same method was being used in other places with its original intended usage…but of course, it wasn’t behaving in that way. Unraveling this was an incredible mess and I’d be very happy if no engineers ever had to go through a similar exercise.
Incompatible implementations
But developer confusion isn’t the only problem. Another peril of modifying objects that you don’t own is the possibility of naming collisions and incompatible implementations. Take a lesson from the history of the Prototype JavaScript library. John Resig wrote about this a while ago, so I’ll just quickly summarize. Prior to version 1.6, Prototype implemented its own document.getElementsByClassName()
method long before it was part of HTML5 and long before any browser thought about implementing it natively. In addition, Prototype also added the each()
method to Array
objects. Thus, users of the Prototype library began writing code such as:
document.getElementsByClassName("myclass").each(doSomething);
This wasn’t a problem until the native document.getElementsByClassName()
method was implemented. While Prototype’s version returned an instance of Array
, the native implementation returns a NodeList
object. Since NodeList
doesn’t have an each()
method, either natively or added by Prototype, the above coding pattern caused a JavaScript error when executed in browsers that had a native implementation of document.getElementsByClassName()
. The end result is that users of Prototype had to upgrade both the library code and their own code; what a maintenance nightmare.
What if everyone did it?
Looking at a few isolated examples doesn’t really represent the enormity of the maintenance problem when you modify objects that you shouldn’t. To understand this point of view, it’s helpful to take a step back and look at moral philosophy (aka ethics). Moral philosophy is all about determining if an action is moral. There are many schools of thought on the topic, but I point towards a favorite modern philosopher, Immanuel Kant.
While I don’t want to get too deeply into moral philosophy and open this up for philosophical debate, Kant was famous for trying to determine “universal law” as the basis for moral action. In short, you can determine if an act is moral by asking, what would happen if everyone did it? For example, what if everyone cheated on a test? In that case, the test becomes useless, so this must not be a moral action.
Applying this same line of reasoning to the topic at hand, what if everyone on your team started modifying objects that they didn’t own? What if I went in and made modifications to document
and so did everyone else on my team? What if everyone on the team created their own global variables? I hope that it’s obvious just how detrimental these actions could be to a team development environment.
Simply put: if everyone on your team modified objects that they didn’t own, you’d quickly run into naming collisions, incompatible implementations, and maintenance nightmares.
As a side note, I find Kant’s question incredibly relevant to any system that must scale. “What if everyone did it?” can really save you some trouble when considered as part of a technical design.
Conclusion
Maintainable code is code that you don’t need to modify when the browser changes. You don’t know how browser developers will evolve existing browsers and the rate at which those evolutions will take place. The code you write needs to continue working in future browsers and with future versions of JavaScript libraries without modification, and you cannot ensure that when you’re modifying objects that you didn’t create in the first place. The only code you can be certain will remain the same is the code you write yourself.
I can’t state this strongly enough: your code is not maintainable when it requires modifications to objects you didn’t create. Stepping down that path only leads to maintenance nightmares going forward.
P.S. If you’re interested in learning more, check out my presentation on Scalable JavaScript Application Architecture (video).
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.