Ayende @ Rahien

Unnatural acts on source code

Refactor This!

The following method is ugly, what kind of refactoring would you do here?

private void HandleMessageCompletion(
    Message message,
    TransactionScope tx,
    OpenedQueue messageQueue,
    Exception exception,
    Action<CurrentMessageInformation, Exception> messageCompleted,
    Action<CurrentMessageInformation> beforeTransactionCommit)
{
    var txDisposed = false;
    if (exception == null)
    {
        try
        {
            if (tx != null)
            {
                if (beforeTransactionCommit!=null)
                    beforeTransactionCommit(currentMessageInformation);
                tx.Complete();
                tx.Dispose();
                txDisposed = true;
            }
            try
            {
                if (messageCompleted != null)
                    messageCompleted(currentMessageInformation, exception);
            }
            catch (Exception e)
            {
                logger.Error("An error occured when raising the MessageCompleted event, the error will NOT affect the message processing", e);
            }
            return;
        }
        catch (Exception e)
        {
            logger.Warn("Failed to complete transaction, moving to error mode", e);
            exception = e;
        }
    }
    try
    {
        if (txDisposed == false && tx != null)
        {
            logger.Warn("Disposing transaction in error mode");
            tx.Dispose();
        }
    }
    catch (Exception e)
    {
        logger.Warn("Failed to dispose of transaction in error mode.", e);
    }
    if (message == null)
        return;


    try
    {
        if (messageCompleted != null)
            messageCompleted(currentMessageInformation, exception);
    }
    catch (Exception e)
    {
        logger.Error("An error occured when raising the MessageCompleted event, the error will NOT affect the message processing", e);
    } 
    
    try
    {
        var copy = MessageProcessingFailure;
        if (copy != null)
            copy(currentMessageInformation, exception);
    }
    catch (Exception moduleException)
    {
        logger.Error("Module failed to process message failure: " + exception.Message,
                                     moduleException);
    }

    if (messageQueue.IsTransactional == false)// put the item back in the queue
    {
        messageQueue.Send(message);
    }
}

Note, please use pastebin.com/ instead of posting code in the comments.

Comments

Benny Thomas
04/04/2010 09:56 AM by
Benny Thomas

Det test on txDisposed == false in the second block is not needed, because if you reach the part where you set txDisposed to true, you will never reach the second block because of the return statement in the first block

leonard
04/04/2010 10:01 AM by
leonard

1.This method has 6 parameters, to many.

2.We can add a finally clause to dispose transaction

3.Extract a method for "messageCompleted"

Obs. This refactoring it will be useful if we can see more code.

Patrick Smacchia
04/04/2010 10:03 AM by
Patrick Smacchia

I don't see your point, this method is just ridiculously too complex. I wouldn't even spend time to seek if some simplifications could be done in the method itself.

1) Split it in several methods with relevant names to have at most one try/catch per method

2) Maybe see if some of these methods are similar

3) Having 6 parameters represent a whole context. It might be needed creating a complete class to handle the context, and put all smaller methods in the class.

Rik Hemsley
04/04/2010 10:11 AM by
Rik Hemsley

Firstly, I agree 100% with Patrick Smacchia's comment.

I very often write this kind of method and call it (in my head) a 'script method'. It's a set of actions which must be performed in sequence.

I always refactor 'script methods' in the same way: Put each action in its own (well-named) method and turn the original method into a simple sequence of calls to the methods which do real work.

Ayende Rahien
04/04/2010 12:05 PM by
Ayende Rahien

Omer,

I REALY don't like the throw exception for exception != null, it will cause misleading errors in the log, for one thing.

And it is still a pretty big & complex method.

Ayende Rahien
04/04/2010 12:06 PM by
Ayende Rahien

Leonard,

disposing a transaction may throw, and we need to deal with that part specifically.

Ayende Rahien
04/04/2010 12:07 PM by
Ayende Rahien

Patrick,

What do you mean, not see my point?

Don't you think this code is ugly? Don't you think that it should be refactored?

And 1 try/catch will absolutely not work, there are several layers here that you need to deal with.

kibbled_bits
04/04/2010 01:58 PM by
kibbled_bits

I would start with overloading this method so that I can require a transaction scope to always be passed in. The overload would get the transaction in a using block.

I agree this function is over complicated, this code looks like a byproduct of bad design and no AOP.

Uriel Katz
04/04/2010 01:59 PM by
Uriel Katz

you could reduce the code by making this pattern:

try

{

    if (somCallable != null)

        somCallable(currentMessageInformation, exception);

}

catch (Exception e)

{

    logger.Error("My Error", e);

}

a function which recives the callbable,parameters and error message.

it will make 27 lines become 3 lines.

Corey
04/04/2010 02:07 PM by
Corey

Use a behavior chain similar to the way that FubuMVC executes their controller action. This way the BeforeTransactionComplete that was added specifically for Linq DataContext only gets registered as a behavior for things. It really simplifies each behavior into something very manageable.

Corey
04/04/2010 02:10 PM by
Corey

Using a behavior chain would also allow for a lot more reuse of code than it currently does. Each transport has very similar behavior in this area, but is for the most part copied between them. The chain could reuse existing behaviors if it makes sense to do so.

Omer Mor
04/04/2010 02:58 PM by
Omer Mor

Oren:

I wouldn't mind much about throwing exceptions for the sake of keeping the code clear, but I understand your reasoning.

How about this approach:

http://pastebin.com/qAqJeqg6

I think it's clearer.

Patrick Smacchia
04/04/2010 06:11 PM by
Patrick Smacchia

What I meant by 'I don't see the point' is I don't see the point in trying to understand the logic of such flawed piece of code and try to make it easier. It is too error prone and time consuming.

The only way to deal with such low quality code, is to split that in smaller methods with relevant names and eventually create a class to keep the context. This is purely mechanical, one doesn't need to think for that.

Once everything is splited, then it might be time to try to be smart and see if smaller methods can be refactored somehow.

Frank Quednau
04/04/2010 08:43 PM by
Frank Quednau

I'd rename the method to "handleMethodcompletion" to adhere to my code guidelines. Apart from that it's just fine.

Oops, 1st of April is already over?

In that case I second Patrick's opinion.

Josh Schwartzberg
04/04/2010 09:45 PM by
Josh Schwartzberg

Oren, I believe you are beginning to harness your readers as amazon mechanical turk employees..

Rafal
04/05/2010 05:15 AM by
Rafal

Josh, this is cloud computing at work ;)

And regarding the method, if it's ugly and has ugly parameters, its callers must be also a bit ugly.

Ayende Rahien
04/05/2010 06:35 AM by
Ayende Rahien

Omer,

You aren't handling the case where an error was passed to this function

Ayende Rahien
04/05/2010 06:43 AM by
Ayende Rahien

Josh Schwartzberg,

While I certainly do that sometimes, this is not one of those times.

This refactoring was done before I posted this post, and you can look at the results at GitHub if you like, to see my answer.

Ayende Rahien
04/05/2010 06:45 AM by
Ayende Rahien

[ICR],

Now that is a pretty refactoring.

Ayende Rahien
04/05/2010 06:47 AM by
Ayende Rahien

Patrick,

"flawed piece of code" - it works, it is in production, it handles a very complex scenario very well.

90% of the refactoring that were tried broke something essential when they did that.

No, it isn't mechanical at all

Patrick Smacchia
04/05/2010 08:25 AM by
Patrick Smacchia

This piece of code is flawed in the sense that it is unmaintainable. It is so unmaintainable that it became a blog post challenge to refactor it.

it works, it is in production. 90% of the refactoring that were tried broke something essential when they did that.

This is the exact definition of fragile code, it works but any attempt to make it nicer breaks it.

it handles a very complex scenario

I never take a complex scenario for granted. A too complex scenario is just a set of easy scenarios that hasn't been refined enough. First 'refactor' the scenario itself, invent new artefacts, new concepts, new steps, new states, that add sense. Then the implementation that will follow will be easy to read and to maintain despite that fact that it will certainly be bigger.

[ICR]
04/05/2010 11:00 AM by
[ICR]

"you can look at the results at GitHub if you like"

What repository? I can't seem to find it, though that may just be my unfamiliarity with GitHub.

Bryan
04/05/2010 01:46 PM by
Bryan

Corey's approach is the best hands down in my opinion. That's how I would have done it, he beat me to it.

leonard
04/05/2010 02:05 PM by
leonard

I found this repository git://github.com/ayende/rhino-esb.git, but the new refactoring is not commited there.

Ayende Rahien
04/05/2010 02:31 PM by
Ayende Rahien

Leonard,

Opps, they are there now.

andhapp
04/06/2010 01:21 PM by
andhapp

Where are the tests? How can anyone refactor this without tests? How would one ensure that it would still work after refactor?

Don't "Refactor this!". Please "Rewrite this" more like.

Comments have been closed on this topic.