Feed on
Posts
Comments

Check this heavily redacted Java beauty out:

public int DoSomethingMessy()
{
    // ...horrific 15 indents and 300 lines of code...
                    MessyThing messy = Frustrator.staticMethod();
                    if(messy.requiresValidator())
                    {
                         doThis();
                    }
                    else
                    {
                          doThat();
                    }
                    // ... much further logic based on messy's values
    //...blah-blah-blah more tons of code
}

I’ll be pairing, with the target of bringing some or all of DoSomethingMessy under test. When I notice Frustrator and its annoying static method, I quickly choose and use one of the hacks to get around static calls.

It’s at this point that my pair wants to ctrl-click Frustrator and go see what it does on its way to creating a MessyThing object. Her logic is simple: if we don’t know how Frustrator loads a MessyThing, we won’t be able to simulate it correctly.

<CLICK> The knowing trap closes around her, all unawares. It’s time to coach not knowing.

The Not-Knowing Master

Is it necessary to know how Frustrator loads a MessyThing in order to test the extended logic that occurs after the call? Absolutely not. We can set the values we want based on that extended logic itself: if the calling logic uses messy.requiresValidator(), say, then sure enough, we need to test using both possible values, and so on.

On the other hand, is it expensive to know how Frustrator loads a MessyThing? Usually, yes indeed. As a general rule in legacy, you can bet on ctrl-clicking for hours as you read the code from Frustrator, AnnoyMatic, StinkingInconvenient, and GoodLordThisIsReallyStupid.

This is not knowing.  A developer working in deep legacy has to master the art of knowing just exactly the right amount, and refusing to be distracted by the rest.

Coaching ‘Not Knowing’

Is Key For Deep Legacy Work

14 Responses to “Legacy-TDD: Coach ‘Not Knowing’”

  1. EricSchaefer says:

    I feel tempted to sneak into our source repo of one of our projects and rename some classes… Gotta get some ice cream to fight the urge…

    • GeePawHill says:

      That’d be fun, but prolly must be resisted. :)

      One of my teams had a class called LegacyMonsterData class, and no, it wasn’t a D&D application. They named it that because they knew it was awful. They decided they would be gradually removing/altering parts of its API into other classes so as to reduce and ultimately eliminate its monster-nature.

  2. raul says:

    Hi!
    Do you mind sharing your thought process in “quickly choose and use one of the hacks to get around static calls.”. Does it depend solely in the situation in hand, or is it possible to extract some general advices?

    Thanks

    • GeePawHill says:

      Raul…
      In Java, Extract & Override, where I extract the static call to a protected method and override it during testing is usually my first line of offense: E&O solves every microtesting problem in Java, ultimately.

      Sometimes, tho, you do E&O so many times it feels like you’re testing swiss cheese. Then I will often create a class and assign it as a member during construction, then gradually teach that member to either call the static for me, or to implement the code itself. This is especially handy when there are lots of calls into the static, or when the static is just a global in disguise and is riddled through the code.

      I’m sure the other guys here have more ideas on this, too.

      Seeya!
      Hill

    • Philip Schwarz says:

      See ‘Extract and Override Call’ in following chapter of Working Effectively with Legacy Code: ‘Dependency Breaking Techniques’

      • GeePawHill says:

        Exactly. Industrial Logic also has online learning around this.

        In java-land, E&O is a universal, if ugly, solution. Others, like C++, would need a combo of techniques.

        In general, even in Java, my third E&O on the same class makes me cast about for alternatives, except in the most dire double-dawg-dare situation. :)

        Seeya, Hill

      • Just make sure you explain that E&O is a hack for legacy code. It is not the recommended way to go for green code. I’ve been burned myself by this confusion :-/

        • GeePawHill says:

          Matteo: That’s absolutely right. The E&O is a *hack*. Properly TDD’d greenfield code should never involve having to do one. I use it almost entirely for that first rush to bring a legacy class under some kind of control. It would never be an end-goal of a refactoring.

          Repeat: E&O is a hack. Capability to E&O is *not* a final result on an object, just an instrumental one. –Hill

  3. Cory Foy says:

    Great Post! Too many times we think of the “Not Knowing What We Don’t Know” part of knowledge as something that should be rectified – when in fact, it may just be noise.

    • GeePawHill says:

      Cory, you’re right. That noise can pile on so heavily that you find yourself ctrl-clicking half the source base just trying to hold the call stack in your head.
      Most source files, come down to it, can be brought under microtest with the most cursory glance at their dependents. This is a direct result of knowing that microtests test a class ASSUMING ALL OTHER CLASSES WORK.

  4. Jeff Grigg says:

    Gotta give the team credit for expressive class names that communicate well! >;->

    Looks and sounds like the “DoSomethingMessy” method suffers from the “Feature Envy” code smell: Given that lots of the code in the “DoSomethingMessy” method references values in the “MessyThing” class, this suggests that this code should be moved to (typically new) methods in the “MessyThing” class.

    You’re right that you don’t need to know anything about the “Frustrator.staticMethod()” implementation to be able to do all the unit testing of the “DoSomethingMessy” method that you might want to do. But I’m not such a big fan of limiting myself to just unit testing. “End to end is further than you think.” is one of my favorite XP quotes. So I’m inclined to dive into Frustrator, AnnoyMatic, StinkingInconvenient, and GoodLordThisIsReallyStupid, looking for a good point to forcefully inject a mocking layer. “GoodLordThisIsReallyStupid” sounds, off hand, like something richly deserving of mocking. (…on more than one level!!! 😉

    (Maybe we should address raul’s static hack question. I like to inject a weakly-Singleton concrete implementation of an Abstract Factory class at that point… Singleton in the production code, but substituted with an alternative mock implementation while testing.)

    • GeePawHill says:

      Jeff, I don’t test end-to-end much. I sometimes *do* write tests that are bigger than micros, but not even that, a lot.
      Microtests don’t catch every conceivable defect. They work well because they catch a *lot* of defects at a ridiculously cheap price (once the skill is learned).
      As for the Feature Envy, you’re probably right that that’s what we got going on. The solutions could vary a lot, it would depend for me on all that code I pointed at with //… :)
      Also, as you know, I advocate lots and lots of small refactorings before I tackle anything large, especially in a language like Java with it’s awesome tool support.
      Seeya!
      Hill

      • Jeff Grigg says:

        Yes, I find micro tests easier to write, so they give better ROI… until they inhibit major refactorings. I work a lot with legacy code, so I’m shooting for *MAJOR* refactorings. That makes end-to-end tests much more attractive to me: I’m likely to rip the guts out of the thing, changing its implementation radically. So I want my automated regression tests to be remarkably ignorant of what’s going on in the middle layers — because it’s going to change. ;->

        • GeePawHill says:

          I totally get this, because I’m sure your situation meets my two caveats: only if you are an experienced microtester already, and only if you know to attack duplication already.
          Obviously, you pass with flying colors. :)
          Inexperienced microtesters always think they might just as well go end-to-end, which keeps them from learning how to microtest, and inexperienced refactorers always think the best way to get the new end-to-end test running is to copy the old one and change two lines, with the usual consequences multiplied by the obscurities of getting the whole system to run.
          Seeya, Hill

Leave a Reply

AWSOM Powered