views:

263

answers:

5

The TDD circle is:

"Write failing Test" -> "Write Code to fit a Test" -> "Refactor"

At "Coding" step we are assumed to write code as simple as possible, just to fix a failing test. We should not write complex code until it is really required.

The next step is Refactor. Should we refactor just written code? I think there is no real sence, as we should be happy with code as far as tests are passing.

Probably Refactoring activity should be forced by some thing, like Code writing is foced by failing Tests. Here some possible factors:

  1. The next test to be written requires some changes in system (refactoring)
  2. Performance is bad. We need to improve it without breaking the functionality
  3. Code review reveals that the code written is hard to understand.

What other reasons you see to start the Refactoring?

Also, is this scheme correct:

"Write failing Test" -> "Code" -> "Refactor" -> "Write failing Test"

or may be it should be seen as

"Write failing Test" -> "Code/Refactor" -> "Write failing Test"
+
"External factor (like bad performance)" -> "Refactor".
+1  A: 

Hmm... I usually consider these sort of "external" refactorings separate from the TDD cycle itself. After finishing up feature XYZ using TDD, one ought to have a healthy set of tests to prevent from introducing bugs via refactoring (assuming code coverage is optimal, etc.). Performance bottlenecks and hard-to-understand code usually crop up after the fact anyway, so doing refactoring at that point would be ideal, I think. You can improve performance, make the code easier to understand, and do whatever else that needs doing while using the tests to make sure that you're not introducing bugs into the system.

So to answer your question, I do not believe that external refactoring fits into the TDD pattern, although the identifiers (code smells if you will) are definitely items to keep track of as you are developing the code.

Secret Agent Man
If we exclude "external" refactoring... Do you think TDD has Refactoring as a separate step, or it will be a "Code with Refactor"? Refactor only in case when a new test can't be implemented without changing existing code.
alex2k8
Well, the idea of the third step of the pattern is to make the code better now that the test is passing. And since the test is in place, this can be done safely. In terms of refactoring just to be able to write the next test, the compiler itself is one aspect of a failing test. You write the code for the test, watch it "fail" (i.e. not compile), then refactor the code to make it compile.
Secret Agent Man
+7  A: 

You can write some pretty ugly code while getting a test to pass; refactor now not because it doesn't work, but it's not very maintainable. That's the point case.

After writing code to fit several tests, you can start taking a bigger picture look -- are there overlaps between those pieces of code, where you can factor out some duplication?

Steve Gilham
Hm. Makes sence.
alex2k8
Also note that refactoring is done when code "smells" not when it is not working or only after a code review. So wrote a code that makes test pass, feel the smell - refactor.
Yaroslav Yakovlev
+1  A: 

Other than refactorings triggered by the need to improve performance, I usually will notice while I am writing the code to pass a test a place where I could share code or a better, more elegant way to accomplish something. At this point, you finish the code to pass the test, make sure all tests are passing, then go back and do your refactorings. This may or may not involve the exact code that you wrote, most likely it will, but it may be something that you just noticed while writing the new code.

Sometimes I'll note something that could be refactored, but decide that what I'm working on right now is more important. At that point I'll note the issue for a later refactoring. Eventually, that refactoring will become as or more important than the next feature and it will get done in the refactor phase of a red/green/refactor cycle. In this case, it probably is unrelated to what I was just working on.

tvanfosson
+1  A: 

Is there a bad time to refactor other than a week or two before a major release?

Stephane Grenier
Hehe, I think this leads to an other model: [Refactor] -> Test -> [Refactor] -> Code -> [Refactor]
alex2k8
There is no bad time to refactor, because you are going to run your tests again to prove you didn't break anything before committing the change. Its only bad if you're only ever refactoring and never adding new functionality as required by the current project.
quamrana
Assuming your tests truly cover everything. I prefer to leave major refactorings to the start of the release cycle. I think it's less risky.
WW
+2  A: 

TDD is a great tool to keep you on track/on task. The problem with:

"Write failing Test" -> "Code/Refactor" -> "Write failing Test"

you propose, is it can easily become:

"Write failing Test" -> "Refactor" -> "Code" -> "Write failing Test"

or then

"Write failing Test" -> "Refactor" -> "Refactor" -> "Refactor" -> "Code" -> "Write failing Test"

which is what you want to avoid. By refactoring at the beginning of implementation, you are indulging in speculative development, and, not achieving the goal of the coding session. It's easy to head off on tangents and build things you don't necessarily need. If you have the feature working and tests passing, it's much easier to decide when's the right time to stop refactoring. And you can stop at any time because your tests are passing.

Additionally, you don't want to refactor when your tests aren't green.

A couple other small pts:

  1. I think most of the literature has a slightly different definition what refactoring is. It's not "some changes to the system" or performance enhancements, but specific changes that don't change the behavior but improve the design. If you accept the definition, then performance improvements don't really qualify: they are normal development tasks that need their own acceptance tests. I usually try to frame these as end-user facing stories, where the benefit of doing them is clear. Make sense?

  2. I think you're right that the TDD practice doesn't specifically address the design problems revealed during code reviews. (See reflections and pair programming for other solutions to this.) These tend to be bigger, cross-story issues built up as "code debt", and have to take some time to clean it up periodically. This could be a separate project, but I, personally, always like to do this as part of another "real" story. Last time I did this, we identified we had a problem, but ended up waiting a few weeks until we had a related story to work on it. We followed the TDD practice of implementing the new feature first-- even though we knew it was way wrong. But then we really understood what was going on and why it was messy, and then spent longer than usual on the refactor phase. Worked well.

ndp
I just want to emphasize that the `Refactor->Refactor->Refactor` section leads to unbounded changes which can absorb huge amounts of development time. Also just to note that after **every** code change you should run the tests again, whether that code change is new functionality or just a refactoring.
quamrana
Consider we write an application and to this moment it allows us to list tasks and publish new tasks. But to pass tests it was enought to have a global list of tasks. Sure, the customer wants "the tasks to be persisted". I can decide that a good practice is to implement IRepository. And I need existing code to use it. So I refactor methods making sure they call to IRepository. This time refactoring was caused by the new user story. In short, what I try to figure out is - can we use user stories/tests to drive not only the Code writing, but the Refactoring itself? What do you think?
alex2k8
We're moving off from TDD a bit, so I'll keep it short. The definition (within TDD world) of *refactoring* is you don't start refactoring until your tests are green, and you're not done unless they are green (again/still). So they are a helpful tool, but they can't really "drive" refactoring.I am fairly disciplined about the INVEST in user stories practice. I always try to recast necessary refactors to be part of a user story, much like you have done above. I will never have a story "Refactor into IRepository", but instead I will make it an integral part of a valuable user-facing feature.
ndp