Saturday, September 11, 2010

More Boilerplate Code Removal

In my quest to never repeat myself, here’s another little trick that I use.

Say we have a number of operations represented by methods that all take the same parameter. Here’s the static data creation code in Suteki Shop again:

public void Insert1()
{
    var sessionFactory = configuration.BuildSessionFactory();
    using (var session = sessionFactory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
        InsertRoles(session);
        InsertAdministrator(session);
        InsertRootCategory(session);

        transaction.Commit();
    }
}

It’s really boring that we have to repeatedly write ‘(session)’. Is there anther way? Oh yes! Enter my ‘ApplyTo’ extension method:

public static void ApplyTo<T>(this T arg, params Action<T>[] actions)
{
    Array.ForEach(actions, action => action(arg));
}

Now our Insert method looks much cleaner:

public void Insert()
{
    var sessionFactory = configuration.BuildSessionFactory();
    using (var session = sessionFactory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
        session.ApplyTo(
            InsertRoles,
            InsertAdministrator,
            InsertRootCategory
            );

        transaction.Commit();
    }
}

Isn’t that nicer?

15 comments:

Oran Dennison said...

This lets you treat "plain old methods" almost like extension methods, with the added benefit of being able to batch multiple calls into a single statement. Nice!

Anonymous said...

You saved a whole *no* lines of code and made it more difficult to understand.

Neat use of language features though. :)

lpodolak said...
This comment has been removed by the author.
lpodolak said...
This comment has been removed by the author.
lpodolak said...

No mike, it is not nicer, it is overdesign :-). What you started with is more readable than the outcome

Unknown said...

No it isn't nicer. Why don't you make use of OO feature where have a class responsible for this stuff where you have a session data member that can be accessed by its private methods.

Mike Hadlow said...

Hi Podi,
I agree, if you don't initially know what ApplyTo does, it's a little confusing. But this is a simple example. I might well end up with ten or more functions all taking the same parameter, surely there's no need for all that repetition?

Hi Ramon,
Yes, that's a reasonable approach, but I like to avoid side effecting functions as much as possible. By passing the state as an argument to a function, it's more obvious, predictable, and easier to test. Also it gives you greater flexibility, in my example I could define my static data generating functions as static methods of any class.

Kieran Benton said...

Sorry Mike, I agree with everyone else - this is just over design, it isn't shorter, it's definitely not clearer and there are better ways of avoiding passing state in repeatedly.

As Ramon says, this likely should be encapsulated into a class with the state passed in as a constructor.

Your comment about avoiding side effects doesnt make any sense?

Mike Hadlow said...

Hi Kieran, Don't be sorry, it's good to have an opinion :)

If you build a class that takes ISession as a constructor argument and then have void -> void methods that act on the session, you are writing side effecting functions. It's a perfectly valid way of going about things, but I prefer to pass in state to a function if at all possible.

duncanmcrae said...

Interesting, Mike. Do you have any more examples nearby?

Mike Hadlow said...

Hi Duncan, I'll try and remember to post them as I think of them :)

I did consider the case where you have a single function that you wanted to call many times with different arguments:

DoSomething(arg1);
DoSomething(arg2);
...
DoSomething(arg10);

It would be nice if we could write this:

DoSomething.ApplyTo(
arg1,
arg2,
...
arg10);

But you can't use a function as an argument to an extension method, the compiler throws it out. You can write this:


((Action)DoSomething).ApplyTo(
arg1,
arg2,
...
arg10);

But that's starting to look pretty ugly. The case when you have multiple arguments to a function is even worse, mainly because Tuples aren't a first class language feature of C#.

@awerlang said...

Now, you have to supply, say, a "root" password to InsertAdministrator.

How would you handle this *new* requirement?

Mike Hadlow said...

Hi @awerlang,

Of course it only works if each function is applied to the same argument(s). One solution would be to have some kind of context object that you could add both the session and your credentials to, but whether that makes sense or not depends on your particular programming problem.

@awerlang said...
This comment has been removed by the author.
@awerlang said...

My point is, this kind of solution is not robust enough, as new requirements arrive.

Unless you only plan to use it in throw away, repetitive code.

Regards