Mutable Mish-Mash
Submitted by craiga on Thu, 06/28/2007 - 17:25.
This post is a bit of a cathartic rant, but in as polite a way as possible. Please bear with me.
There have been a few micro-revolutions on the internet of late regarding software development. Not all of them are sane, some are outright bizarre and one or two are just rehashes of very, very old concepts. For example, functional languages are popular on reddit.com, and dynamically typed languages are popular with the web crowd on dzone.com.
One of the most common dynamically typed languages around at the moment in Javascript. It's a strange thing; not in the slightest object oriented, but has a concept of objects. Instead, people seem intent on recreating OO like behaviour using what passes for a Javascript object and piles upon piles of closures and anonymous functions. While this is certainly a step in a pragmatic direction, it seems that this particular paradigm shift sometimes brings with it a bit too much cleverness, and not quite enough sensibility.
Javascript is a bit of a mad beast; classes are defined as closures that are somehow instantiated. Methods are declared as closures within the closure that is the class. It's all a bit mad. This mass of closures within closures within closures is overshadowed by that most heinous of things; mutable types. Heinous? Surely no! I'll have the dynamic language fanbois all over testiculating about the superiority of such things. However, I do have reasons.
Consider this implementation:
classdef.js:
function myClass() {
this.eatDinner = function() {
alert("Yum yum! "+this.getFood()+"!")
}
}
pagefunc.js:
obj = new myClass()
obj.eatDinner()
So what does that do? Well, nothing as it happens. The getFood method of the myClass class isn't defined so it causes an error. This is deliberate because we want the getFood method to be able to return different foods for different situations. Say on one page we include carrot.js:
carrot.js:
myClass.prototype.getFood = function() {
return "Carrot"
}
This actually modifies the myClass class so that, as long as carrot.js is included before obj.eatDinner() is called, it will work and will alert the user with the wonder message "Yum yum! Carrot!". Marvellous. Can anybody spot any problems with this approach? OK, some hints.
The first problem is that myClass is incomplete. It will fail at runtime without intervention from another piece of code. This means that it is possible for the entire script to fail if that method isn't added later. In other words, the class is very brittle.
The second problem is that a person reading the code in classdef.js and pagefunc.js must also know (through some other means) to read carrot.js as well in this instance. In another instance they may have to read potato.js. Who knows? The problem is that it is impossible to know exactly what is implemented for getFood (be it a function or even nothing at all) without either running the code, or by tracing back through the code to find out where it was implemented.
These two problems can lead to debugging issues. A developer other than the one who originally wrote it could come to a failing script and see that it is dying in eatDinner because getFood is not defined. "Of course it's not," thinks developer, "I can see that from the code". Unfortunately, now the developer has to figure out which particular implementation of getFood should be used, and include the right file for the instance of myClass that is having problems. This is a lot of work for the developer.
As the kind of guy who thinks that the developer shouldn't have to work so hard, I propose another solution that still uses closures (ooh, how very modern) but avoids the problems of mutable types. Try this:
classdef.js:
function myClass(foodClosure) {
this.getFood = foodClosure
this.eatDinner = function() {
alert("Yum yum! "+this.getFood()+"!")
}
}
pagefunc.js:
getFood = function() {
return "Carrot"
}
obj = new myClass(getFood)
obj.eatDinner()
In this case, every implementation of myClass is identical. Identical, that is, except instance information. In this case, the instance information is a closure representing the getFood method. This fixes the problems I already outlined and adds some new benefits.
The problem of the brittle class is fixed. The class is complete and doesn't need to change at runtime. Of course, the user could specify a duff closure as a parameter, but that's OK. You can debug that by checking the instantiation, not having to dig through piles of potential candidates.
This also means that the problem of knowing what is implemented is fixed. To find out what a particular instance will do, simply look at the instantiation code. There is also the added benefit that each instance can have different behaviour. By altering the class structure via the prototype property, the script is limited to a single behaviour.
So, three wins. The getFood method is always defined, the behaviour is obvious from the instance and you aren't arbitrarily limited to a single behaviour per script. So why, I ask myself, do people insist on doing it the other way? I can only assume it's because it's "cleverer". That mythical quality that means that code is harder to read, harder to maintain, but makes the original developer feel slightly smug. At that point, the old saying about debugging being twice as hard as developing springs to mind.
