extract class or substitute algorithm?

I have a dilemma. In fact, I frequently have this dilemma, so I thought I’d ask your opinion on which way you would address it.

I just completed a CRC session for the next feature I want to add to my codebase. One of the classes identified for the new scenarios is a particular kind of counting collector, and I realised that the same behaviour already occurs elsewhere in the codebase. So one of the first steps in coding up this new scenario will be to extract a new class from an existing class. The existing class has reasonable micro-tests, and therein lies the dilemma:

  • I could simply use the Extract Class refactoring recipe, and I’d be done quite quickly; the new collector class would be tested indirectly, via the micro-tests for the original class (which would now be the extracted class’s only client).
  • Or I could develop the collecting class test-first, from scratch, so that it had its own micro-tests, and then use Substitute Algorithm, reimplementing the old class to use the new.

The advantage of the second approach is that it gives me precise micro-tests of the new class. But while developing that class, I could drift away from the required usage, or I could make it harder to switch the original class over to using the new one. (I tend to think of Substitute Algorithm as being riskier than Extract Class.)

So which is better — use Extract Class and know that I have a working client-supplier relationship; or TDD from scratch and risk going off track? In practice, I tend to choose Extract Class if I’m feeling lazy or distracted, whereas I go with TDD followed by Substitute Algorithm when I’m feeling organized; in the latter case I usually get stressed by the “substitute” step…

(It seems to me that if we choose the latter approach in general, many refactorings should cease to be used. That can’t be right, can it?)

3 thoughts on “extract class or substitute algorithm?

  1. I’m glad you’ve brought this up, as I keep running into this very dilemma. In the past I’ve tended to think that test-driving the new class is the preferential approach, as it leaves you with directly-tested code, which feels like the desired state. However, in practice I end up choosing to extract the class (as it’s quicker), and then grumble to myself that it doesn’t feel like it has proper test coverage.

    I wonder if an additional step of house-keeping on the tests is required, such as:
    1) Identify & duplicate any tests that indirectly test the new class.
    2) Refactor the duplicates to test the new class directly.
    3) Refactor the original tests on the client to use a mock of the new class to ensure the correct messages are sent & acted upon.

    If only it were easier to maintain the discipline to keep your tests that well organised!

  2. I lean towards using Extract Class in these situations. If I start coding up a new class then the temptation to write unnecessary or overly generic code is too great for me to resist. I would typically move any appropiate micro-tests from the first class to the second class as the last step of the refactoring. I really like all abstract code to be directly derived (refactored) from code dealing with specifics, it gives me confidence that the abstractions that I have coded are the appropriate ones for the problem domain.

  3. Great post. I used to bump into this problem a few times in my last project and still do not know how to cope with it properly.
    The approach 1 is quicker and is suitable in the majority of cases.
    The main drawback is that your new extracted class is not testable itself – you only have it tested indirectly via client.
    If you would like to use new extracted class with different client you could end up writing tests for this new client that could be very similiar to the ones you already have for and old client.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s