April 2008 Archives

I recently caved and got a 60 day trial Webfaction account, on the advice of Mike Pirnat (be sure to use this url if you sign up so Mike gets a taste).

I was happy right away because Webfaction gives you all the python-y goodness other shared hosts neglect. Things like shell access, easy_install, and a ton of prefab application deployments.

Being a web-framework junkie, I started salivating at my options: Rails, Pylons, Drupal. Hell, they’ve even got Plone. I didn’t think anyone outside the EU ran Plone, but they’ve got it.

Back in my Java days (a story for another day) I worked with older-school platforms like Struts and Spring, and I’ve built a couple of things more recently using Rails.

Being a recent convert to Python in my professional life, I’m eager to see what sweetness Python offers in the framework world.

But all things in moderation. I’ve decided to pick Django, Pylons, and TurboGears to compare. I’ll build a very basic a job board with each, and keep notes on my innermost thoughts and feelings as I do so.

Please keep in mind that I’m coming at this from a pragmatic-purist standpoint. I like beautiful, but I like practical AND beautiful even better.

To keep things in perspective, here are the ground rules:

  1. The app has 3 main ‘pages’: job list, job details, create a job
  2. There is an admin section for the site owner to manage data.
  3. The job list is statically published.
  4. An RSS feed for recent job postings.

First up is Django. Watch this space for the gory details of round 1!

Cleaning out dependencies is pretty much always a good thing. It means more loosely-coupled code, easier testing, and safer execution.

I often catch myself not going far enough in removing dependencies and making my code injectable. Configurations are a good target for some focused DI.

Take a data adapter for user articles. Imagine a blogging platform where users have any number of articles. For the sake of the example, assume that the ‘data’ attribute is an object that is good at getting persistent data from a database or something similar. It’s not crucial.

class ArticleAdapter(object):
    ##
    # Get all the articles authored by a user
    # @param username The username string for a user.
    # @return A List of Article Instances for the user, None if there 
    #       are no matching Articles.

    def get_article_by_user(self, username):
        return self.data.whereUserName(username) 

Very straightforward, and it does the job nicely.

Now our wildly successful blogging system has some users with thousands of articles. We don’t want to get them all every time, so we put a limit on the number of Articles fetched. Being sweet programmers, we make the max count available via a ConfigObj:

class ArticleAdapter(object):
    def __init__(self):
        self.config = ConfigObj('/app/config/articles.cfg')

    def get_article_by_user(self, username):
        max_count = self.config['max_articles']
        return self.data.whereUserName(username, max=max_count) 

Nice. We’ve got a configurable limit to keep Outrageous Things from happening. but, we’ve created a dependency on the ConfigObj. The init method calls the ConfigObj with a hardcoded path. This is going to make testing nasty. Luckily we can shuffle things to extract the dependency, and make the ConfigObj injectable:

class ArticleAdapter(object):

    ##
    # @param config A ConfigObj instance 
    #       (or duck of a similar feather)

    def __init__(self, data):
        self.data = data
        self.config = None

    ##
    # Get all the articles authored by a user.
    # @param username The username string for a user.
    # @return A List of Article Instances for the user, None if there 
    #       are no matching Articles.

    def get_article_by_user(self, username):
        max_count = self.config['max_articles']
        return self.data.whereUserName(username, max=max_count)     

Now the constructor doesn’t need to go and create a ConfigObj. We can use setter injection to set it when the code actually runs. Writing a test for it (using Mocker for the nasty parts) is easy:

def test_gets_article_by_user():
    mocker = Mocker()
    mock_data = mocker.mock()
    mock_data.wherUserName('superdude', max=100)
    mocker.result([ Article(title="War: what is it good for"), Article(title="my favorite Jedi")])
    mocker.replay()

    fake_config = ConfigObj(['max_articles=100'])
    adapter = ArticleAdapter()
    adapter.config = fake_config
    articles = adapter.get_article_by_user('superdude')
    mocker.verify()
    assert len(articles) == 2

So we’re done. BUT… The dependency we factored out was the ConfigObj. Our class isn’t really depending on the ConfigObj. It depends on the max count value that the config provides. We can go one step further in our thinking if we consider the max row count to be an attribute of the adapter. This is one of the cool things that happens when you start figuring out dependencies: almost everything is an attribute. Moving our max from a config to an attribute, we get:

class ArticleAdapter(object):

    ##
    # @param config A ConfigObj instance (or duck of a similar feather)

    def __init__(self, data, max_articles):
        self.data = data
        self.max_articles = max_articles

    ##
    # Get all the articles authored by a user.
    # @param username The username string for a user.
    # @return A List of Article Instances for the user, None if there 
    #       are no matching Articles.       

    def get_article_by_user(self, username):
        return self.data.whereUserName(username, max=self.max_articles)     

def test_gets_article_by_user():
    mocker = Mocker()
    mock_data = mocker.mock()
    mock_data.whereUserName('superdude', max=100)
    mocker.result([ Article(title="War: what is it good for"), Article(title="my favorite Jedi")])
    mocker.replay()

    adapter = ArticleAdapter(fake_config)
    adapter.max_articles = 100
    articles = adapter.get_article_by_user('superdude')
    mocker.verify()
    assert len(articles) == 2

The big difference is that its easier to wrap your head around plain attribute. Anything fancier requires mental gymnastics, and that ain’t cool, man. So phrases like this are easy to read and understand:

adapter = ArticleAdapter()
adapter.max_articles = 100

While phrases like this require more mental gymnastics:

fake_config = ConfigObj(['max_articles=100'])
adapter = ArticleAdapter()
adapter.config = fake_config

In the first case, the mental dialog of the next developer might be, “Ok, I need to get a max record count from a ConfigObj instance, so I’ve got to create or find some one to get my answer. Better start digging…

In the second case I imagine someone thinking “Allright, max results is an attribute I can set. There may be a config somewhere I can get it from, but no biggie either way.

Converting non-testers is tough, mainly for one reason: There is no substitute for EXPERIENCING unit tests.

You can extol the virtues of unit-tests until your lips go numb, show a non-tester as many tutorials as you like, but there is no substitute for them actually writing the tests and feeling the benefits.

Writing unit tests feels like extra work. When you say “Hey, you should write tests”, you’re telling a person “Trust me. It takes more time and effort now, but in four months you’re gonna LOVE it!”. Not an easy sale.

One think I have found is that it takes a single positive testing experience to totally convert someone. The remission rate is almost zero. That experience boils down to the Golden Hour.

The Golden Hour goes down like this:

  1. Non-believer decides they’ve heard enough about this unit-testing, and are ready to try it. They’re skeptical.
  2. They try to write a test. 
  3. The non-believer hits a wall. Because they aren’t especially motivated to get their tests to work, they conclude that unit-testing stinks, and stop.
This can be repeated many times by the same person. They’re going to get the same results each time unless you help them see the light.

Here’s where you as evangelist come in. Do whatever it takes in that hour. Help them refactor their code to make it testable. Explain different ways a trivial-looking test will help. WRITE A TEST FOR THEM. That’s right, do something that isn’t your job, and help a fellow human.

It’s intense therapy. It should feel intense. You are breaking someone out of their comfort zone and exposing them to something blindingly great. It should be hard.

A single example that applies to exactly what they’re doing is all it takes to create the A-HA moment for the non-believer. You’ll know it when you see it. They will flip from skeptic to convert. After that, you can walk away.  The Golden Hour is over. They’ve swallowed the Kool-aid. They’re testers now.

That’s all it takes. Sixty minutes to make the difference between doing things the Painful Bad Way, or doing them the Fun Good Way. So don’t waste time being all strategic with info sessions and tutorials.Watch for the Golden Hour and strike when you see it. Convert the non-believers.