In The ThoughtWorks Anthology there is a chapter called "Object Calisthenics". This section lays out a challenge to help push your understanding of object-oriented programming concepts. I decided to take this challenge by building a little Flex application, and I've been quite surprised by its effect. It really does make you think very deeply about what OOP means and how it affects the way you program.

In essence, the challenge is to write a non-trivial (about 1000 lines) program of your choosing that does not violate a set of 9 rules. The rules are very draconian, and they aren't advocating that you actually write all of your software this way (though obeying them in most cases probably would be a good thing). But the goal of the exercise is to really expose any lingering influence of procedural coding and force you to come to terms with them. While I chose to do this in Flex and ActionScript (since that is what I am currently learning in depth), I think some of these would probably need a bit of tweaking in a ColdFusion implementation, but most of it would still apply. Here are the rules:

  1. One level of indentation per method. If you need more than one level in from the start of the method body, create another method and call it. So one level of a loop or if statement is ok, but any deeper and you need to break it out into its own method.
  2. Don't use the ELSE keyword. This one is tough. We're very used to using if/else or switch/case. But good OO designs rely on polymorphism in place of conditional logic.
  3. Wrap all primitives and Strings. That means instead of var zipCode : String, you need var zipCode : ZipCode, and instead of var age : int, you need var age : Age. The idea is to ensure that everything is an object, that the purpose of everything is self-evident from it's type, and that behavior related to that object has somewhere to go.
  4. Use first class collections. This means you can't have var cartItems : ArrayList, but instead have var cartItems : CartItems. This means that behavior related to the collection has a place to live, and that the collection should contain no other instance variables.
  5. One dot per line. This is meant to enforce the Law of Demeter. So this would be a no-no: invoice.lineItems.getLineItem(4). Although this discourages method-chaining in cases where a method returns the same object (a la JQuery), that isn't what this rule is trying to do. It's trying to stop you from reaching across class boundaries and digging into the guts of other objects.
  6. Don't abbreviate anything. This is meant to enforce clarity, as well as identify duplication or misplaced responsibilities. If you're typing mergeUserPreferencesFromDatabaseAndCookies() too often, something is probably wrong, both in terms of what the method is doing and how many things are coupled to it.
  7. Keep entities small. No class over 50 lines, and no package over 10 files.
  8. No classes with more than two instance variables. This is meant to ruthlessly enforce the single responsibility principle for objects. If you need more instance variables, break them into composed objects.
  9. No getter, setter, and property calls to other objects. This mandates the principle to "Tell, don't ask" and enforces strong encapsulation boundaries.

I chose to write a program that generates a game of 10 pin bowling, and then scores it. And wow, writing code to score a game of bowling was actually a lot harder than I initially thought, even without the above rules. But I'm just about done with it and when I am I'll post it here in case anyone wants to take a look at it.

But before I post it, I thought I would ask if anyone else is interested in giving this a shot? Would anyone be up for trying this (with a bowling score card or any other idea you like), sending them to me, and having me post them (anonymously if people prefer) and start a blog discussion about them? I think it would be quite educational to see how different people approach these rules to solve problems, and I'm sure everyone involved would benefit. Any takers?

Comments Comments (14) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 9542 Views

Comments

  • # Posted By Bradley Moore | 2/11/09 10:51 AM

    I both like and dislike this idea. I think I will end up giving it a try pretty shortly.

    Primitives and strings converted to objects is odd to me. I see the benefit, but I am probably struggling with some engraved lesson from a professor.

    Let's take Age for example as it seems to be giving me the most trouble. I have not done AS since 1.0 days, so I'm going to pseudo code in Java. ( CF constructors take forever to write in comparison to Java ).

    // int age = 24 becomes
    Age age = new Age( 24 );

    // int age = birthDateToYearsOld( "6/19/84" ) becomes
    Age age = new Age( "6/19/84" );

    We can see one benefit immediately. Age is no longer limited to a single primitive type. Nor does the object containing age need to do the birth date to years old calculation.

    Later we may want to add methods like increase(), reverse(), cryostasis(), etc.

    I am confused about rule 9. How do we ask it how old it is?
    age.years(), age.seconds(), age.output() or something else?

  • # Posted By Bradley Moore | 2/11/09 5:43 PM

    The more I think about this idea, the more I like it.

    I think an exception need to be made for the constructor in ColdFusion. We will need some kind of if/else or switch/case statment to handle overloading. Otherwise, I think the rest is doable.

    age = createObject( ... ).init( 24 );
    age = createObject( ... ).init( '6/19/84' );

    We could create two init functions with different names ( initInt( int ) and initDate( date ) ), but that seems sloppy to me.

  • # Posted By Alan Livie | 2/11/09 8:31 PM

    Great challenge and a good tip for my next read. Martin Fowler's writing is superb and ThoughtWorks recently came to our office to sort us out and they are quality.

    I must admit I'm scared of the challenge!

    One's I'll find tough:

    2. Don't use the ELSE keyword
    8. No classes with more than two instance variables
    9. No getter, setter, and property calls to other objects

    I'm planning a family tree app for my cousin so a good way to take the challenge. If I end up with an app before 2012 I'll send you it :-)

  • # Posted By Alan Livie | 2/11/09 9:07 PM

    Bradley - you said...
    'We could create two init functions with different names ( initInt( int ) and initDate( date ) ), but that seems sloppy to me. '

    Yip and another way around it (but possibly sloppy in a new way!) is using onMissingMethod() to handle init() ... check the args and do stuff based on what was passed

    An api .. forget it but maybe better than initInt() etc

  • # Posted By Bradley Moore | 2/12/09 9:40 AM

    I suppose we could use try/catch in place of if/else, but it is really the same idea.

    init( ... ){
    try{
    this.age = age;
    }catch( ... ){
    try{
    this.age = convertDate( age );
    }catch( ... ){
    ...
    }
    }
    }

  • # Posted By Brian Kotek | 2/12/09 2:14 PM

    Right, using try/catch would defeat the purpose (as well as just being a pretty nasty design).

    As I worked through this I didn't have much trouble getting the majority of my code to rely on object polymorphism instead of conditional logic, but the catch was how to be able to even create the right objects without some kind of conditional check. SOMETHING has to know which object to create. So I might think that allowing limited conditional logic would be acceptable in a factory, but only then if its use is simply to determine which object to return.

  • # Posted By Gareth Arch | 2/14/09 7:55 AM

    A "cheat" way that I write my if/else statements (which once again is probably gaming this system a little)

    instead of
    if ( conditional statement ) {
    x = 12
    } else {
    x = 42
    }

    I do something like this
    x = 42
    if ( conditional statement ) {
    x = 12
    }

    Thus no else and only the "if" part of the statement :)

  • # Posted By Allen | 2/16/09 12:58 AM

    I do that too Gareth. But, as you know, that's not what they're getting at.


    I do like the idea of getting some examples posted. There's are tons of references for relatively basic stuff out there, like how to create a structure in cfscript. But when it comes to how to take a much more OO-like approach to CF, there isn't a lot to be found.

  • # Posted By Alan Livie | 2/16/09 6:15 AM

    @Brian, I am reading "Uncle" Bob Martin's Clean Code and he says pretty much the same thing on the switch statement scenario. he says he'll turn a blind eye if its hidden away in an Abstract Factory somewhere determining the concrete factories to create. I guess the same would apply to having ONE switch/case in a Factory method creating the concrete subclasses.

    I think the point he is making is to avoid lots of duplicate switch/cases in several files by allowing polymorphism to handle it but having it once in a factory is permittable.

  • # Posted By Alan Livie | 2/16/09 6:41 AM

    And

    9: No getter, setter, and property calls to other objects

    Does this mean to comply I'll be writing cfc's with renderPage() or a display() method? Or does this mean from objA I shouldn't be doing calculations using objB.getSomething()

    I really don't fancy going down a renderPage() route but with custom tags etc I still would be able to keep most of the html in cfm templates. It would couple the object to an html front-end though so I would have to use a pattern to de-couple this.

    Jeez. Good OO IS tough :-)

  • # Posted By Bradley Moore | 2/16/09 11:36 AM

    My understanding of rule 9 is that we don't ask for or set properties in other objects. We can ask for or tell objects what we actually need, which I think is more useful overall.

    Math involving properties inside an object seems like a simple transition.
    a.getX() + a.getY() could becomes a.sum().

    We may want to do something a little more interesting, like say compare the age of two people.

    Age a = new Age( 24 );
    Age b = new Age( 26 );

    a.getAge() > b.getAge() could become a.compareTo( b )

    //pseudo
    compareTo( Age b ){
    return a.seconds() gt b.seconds();
    }

  • # Posted By Jon Hartmann | 4/13/09 5:08 PM

    I'm very interested in trying this out... I think though that applying this to CF is much more challenging them some of you are thinking. If I'm reading this right, its not a matter of abstracting some stuff out to custom tags, but rather that a .cfm file would immediately break the rules. I mean, the basis for the challenge is that everything is an object, and .cfm files are something non-object. Your Application.cfc is going to have to handle calling methods that build up the full display from methods on other objects, and all at one level of indention.

  • # Posted By Brian Kotek | 4/13/09 5:35 PM

    No Jon, the point isn't to agonize over how whatever you choose to build gets displayed, or even if it displays anything. The focus is on the object model. If you want to do this and use a .cfm page to display whatever your model is doing, don't worry about that.

  • # Posted By Jon Hartmann | 4/20/09 5:21 PM

    Ah, perhaps I was going a little overboard with the "everying OO" idea... I'm still trying to come up with a good non-trivial example to try and construct in this way. Once I've got something, I'll post a link.