Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with:

Object Calisthenics In JavaScript - My First Attempt

By Ben Nadel on

Last week, I brought up the topic of Object Calisthenics which is an idea defined by Jeff Bay as an exercise to force people to think in "objects." Object Calisthenics imposes very strict rules that require you to jump through potentially unnecessary hoops. The point of the exercise, as I understand it, is not to use rules that apply in every situation; rather, it's to overuse rules that are generally considered good so that you may force yourself to see code from a different point of view.


 
 
 

 
  
 
 
 

Essentially, this exercise is designed to get you out of your comfort zone. It's designed to force you to think about your code in a way that necessarily doesn't allow for procedural paradigms. While this exercise was fun and exhilarating, it definitely caused me a lot of anxiety. This was about as far outside my comfort zone as you can get (in the world or programming). And yet, there is definitely something about it that I found very worthwhile. It has certainly changed the way that I think about classes and, especially, about collections.

The code for this Object Calisthenics exercise can be found on my GitHub account.

For my first attempt at this exercise, I tried to model a Match Maker; that is, someone who tries to connect single people that may form a successful, long-term relationship. Of course, this is the simplest, most obtuse version of this scenario; but, I thought it was complex enough to offer small behaviors and business logic, polymorphism, and composition.

To start off, I did some rough sketching of possible domain model relationships:


 
 
 

 
 Object Calisthenics domain model and sketching. 
 
 
 

I didn't do anything too in-depth because I knew that my understanding of the domain model would be incredibly iterative. Mostly, what the domain model sketching did was help me think about was how to define a Person; specifically, how to define gender identity and gender preference. And, to do so all within the confines of Rule #8 - no classes with more than 2 instance variables.

For example, since a person with a "name", "gender", and "preference" would violate rule #8, I created a secondary object - Orientation. The orientation defined both the gender identity and the gender preference. While this seemed like overkill at first, the more I used this composition, the more I liked its flexibility. It would be able to handle situations like asexuality and transgender; and, it would be able to do so in a fluid manner (ie. not changing the containing class).

With this type of object composition and Law of Demeter (Rule #5: One dot per line), you really have to think hard about what your class knows and how it interacts with its composed classes. For example, since the idea of Male and Female gender is not built into a "person" class (ie. via sub-classing), you have to find a way for the Person class to easily identify as a particular gender.

In my domain model, I have a gender identity which may extend beyond male and female; however, since Male and Female are the primary genders, I did provide person-level methods for common-gender access:

  • isFemale()
  • isMale()

These methods then have to communicate with the composed Orientation instance to see if the state of the current orientation "identifies as male" or "identifies as female". This goes against my instinct (which is exactly the point of the exercise) to do something like:

  • if (person.getOrientation().getIdentity() instanceof MaleIdentity){ ... }

Violation of the Law of Demeter forced me to do:

  • if (person.isMale()){ ... }

.. and then find a way for the person class to communicate with its orientation doing something like:

  • function isMale(){
  • return( this._orientation.identifiesAsMale() );
  • }

It was this kind of thinking that really got me out of my comfort zone and completely changed my perspective on the approach to the solution. All in all, my code took about 10+ hours to write. Which, when you consider the small amount of code, seems crazy. But, when you think about how different this approach is to my normal procedural programming, the adjustment period is much more understandable.

That said, I'd like to briefly touch on each Rule in the exercise.

Rule #1: One Level Of Indentation Per Method

This one really forces you to break your algorithms down into meaningful chunks. Rather than having several nested FOR-Loops, for example, you actually have to create a method for each level (of indentation) that "describes" the intent of the loop. This was one of the hardest rules to deal with; and, in the context of Collections, was something that I couldn't always find a good solution for.

In some cases, I loved having to think this way; in other cases, I did think it brought unneeded complexity. That said, in terms of readability and maintenance, I am sure I would prefer the smaller conceptual gaps afforded by the rule.

Rule #2: Don't Use The ELSE Keyword

I honestly didn't find this one very hard. To be fair, my example doesn't have a lot of "behavior" in it; but, I'm told that when you start to use polymorphism to model different cases, the ELSE keywords starts to disappear naturally.

Rule #3: Wrap All Primitives And Strings In Classes

I wasn't always sure how to do this. And, I didn't always do this. Primarily, I wrapped the "name" property of the Person class. Other than that, I did have opportunities to wrap things like "index"; but, I wasn't sure if that was the intent of the rule. It probably was - and I simply ignored it.

Rule #4: First Class Collections

I didn't like this one at first; but, I grew to like it. I didn't like having to create a wrapper for basic collection behaviors (ie. push, pop, etc.); but, there was definitely a power to having a place to put collection-based behaviors. This allows tight cohesion between a collection and the ways in which you want to manipulate it. You prevent yourself from putting too much logic in the "calling" context.

Rule #5: One Dot Per Line

This was one of the hardest rules to follow because it really forces you to think about inter-object communication. By only talking directly to your "friends," it forces you to create objects that encapsulate behavior. Rather than just thinking of objects as "data storage," you have to start thinking of objects as smart entities that can respond to messages and can answer questions about data. This one was tough, but very cool!

Rule #6: Don't Abbreviate

I think this rule is designed to help you identify situations that violate the Law of Demeter (Rule #5). I didn't think about this one too much.

Rule #7: Keep All Classes Less Than 50 Lines

I was a little relaxed on this one because I have a particular "white-space oriented" approach to programming. Plus, I am using RequireJS as my class loader, which adds a couple of lines of boilerplate code to each class. That said, when you start to use a lot of composition, keeping your classes cohesive, this wasn't too much of an issue. Then again, my example has so little functionality, it's hard to say if this was a byproduct or a coincidence.

Rule #8: No Classes With More Than Two Instance Variables

My domain model is simple enough to allow for this extreme constraint. That said, as I mentioned above, this constraint did have some really interesting payoff in the way I composed my classes (ex. Person composes Orientation, which composes gender Identity and Preference). While I think "2" is an extremely low number, I did get a lot of value out of this rule as far as changing the way I think about class relationships.

Rule #9: No Getters or Setters

This rule is meant to help enforce Rule #5. I liked it; although, I did create some getters for checking things like object equality. I could have gotten around it, I think, but it seemed overkill at the time.

All in all, this exercise was extremely fun! So much so that I actually laid awake at night thinking about it. I definitely want to try it again, this time picking a scenario that models a little bit more behavior. My objects do have state and have logic around that state; but, I wouldn't call them especially stateful. Next time, I'll get more stateful with it - that should provide all kinds of exciting insight!




Reader Comments

Very interesting concept. Had not heard of this before. Thanks for sharing your experiences in a very informative manner again. Seems like you're getting the hang of it! Your work has been an important inspiration to me, so I just wanted to send you a quick thank-you.

Reply to this Comment

One thing I'm finding very interesting through these posts are the differences between JavaScript and other, more object-oriented languages is that these rules seem to apply in different ways.

For instance, a Java/C/C++ array has a lot less "brains" than a JavaScript array; in fact, the JavaScript array has a lot more in common with a java.util.List. In this way, I almost see a JavaScript array as more of a candidate for a "first class collection" than the arrays in other languages.

Interesting things to think about!

Reply to this Comment

very interesting, but it's just for fun(at least #8,9). Indeed, it may be a useful exercise, but if I think of a class Person with instance properties: name, birthday and nationality - I already have broken rule #8.
thinking to rule #9, I imagine a setter for setting a birthdate in a correct format - and already have broken this rule also.
If we think the rule #9 is "helping" rule #5 - is wrong; I think the real benefits of getters and setters is to validate the assignment and control the format in which a property is returned.
Finally, I think these rules are useful in the context they are not followed blindly.
Thanks

Reply to this Comment

@Johan,

Thanks a lot! Learning to program is such an exciting journey for me; I'm glad that other people can share in the stuff that I'm trying out.

@Mike,

Dealing with collections is definitely one of the weird stumbling blocks for me. I'm so used to using the each() method in jQuery to map() and filter() and act on collections in some way. With closures and functional programming, it's so nice to be able to do things like this (pseudo-code):

  • filter(
  • matches,
  • function( matchA, matchB ){
  • return( matchA.likes( matchB ) );
  • }
  • );

What I like about this "Object Calisthenics" is that if forces you to stop and think things like:

"Should my "calling code" actually know how to filter this collection? Or should that business logic be encapsulated within the collection itself?"

Because functional programming makes it so easy to execute "on the fly" logic, perhaps it makes it much easier to break the core concepts of encapsulation?

I don't know - I'm just going stream-of-consciousness here :)

Of course, one does not exclude the other. For example, you can still use functional programming (ex. the filter() method) inside the collection itself. So, you can get the beauty of the closures and the functionality; but, keep the implementation hidden.

@Ciprian,

100% If you couldn't access data, converting data to a "visual medium" would be incredibly tedious! Imagine if I had to convert a business object to some sort of HTML output, I would definitely want / need to be able to use getters to access data. To try and go down the "I'll just pass you a buffer and you write to it" level of encapsulation would be silly - it would tightly couple the business objects to the particular output (which would make using out output mediums very hard).

I agree with this, "I think these rules are useful in the context they are not followed blindly." Rightly said.

Reply to this Comment

I've also been looking at Object Calisthenics recently in Javascript/Coffeescript, after doing similar exercises in C#. You might be interested in this (https://github.com/iainjmitchell/node-shopping-kata) solution to the shopping cart kata in nodejs which follows many of the rules. The EventEmitter class in node certainly helps get past the no getters and setters rule.

Reply to this Comment

@Nando,

Ha ha, this one was tough to find time for. I did it over 4 days (Fri, Sat, Sun, Mon). I'd like to try it again with an app that has a bit more UI to it - like a Quiz or something. We'll see if that is feasible.

@Iainjmitchell,

Thanks for the link - and for the "Kata" concepts. I had seen those before when first looking into the Object Calisthenics; but, I never really read through any of them. I'll have to look at the Katas to see what kind of fun exercises they have.

I love the idea of event emitters in general. They rock, right?!

CoffeeScript is a bit hard for me to read, but I get the gist of what's going on. Very cool!

Reply to this Comment

@Ben,

On my phone right now so I can't really examine your code. How are you accessing your objects' properties without using getters and setters? Just referencing them directly?

Reply to this Comment

@Sean,

What gets me is that the original phrasing of Rule 9 is "No getters/setters/properties". I'm having a problem with downloads so I can't see what Ben is doing, but he didn't mention "No properties" in the discussion. I'm very curious now and frustrated because the download isn't working for me and I can't SEE anything! :-)

Reply to this Comment

@Sean, @Matt,

I believe the point of the no getters/setters is not really to exclude them from the class; rather, it's to exclude them from the primary business logic (as much as possible). This way, you have to ask objects to do things for you rather than to treat them as simple data-stores (that you can access and mutate).

At the end of the day, if you are going to render and UI, at the least, you'll need getters/setters to translate business objects into visual renderings.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.