Saturday, October 19, 2024

TDDbE: Refactoring

In TDD we use refactoring in an interesting way.  Usually, a refactoring cannot change the semantics of the program under any circumstances.  In TDD, the circumstances we care about are the tests that are already passing.  So, for example, we can replace constants with variables in TDD and, in good conscience, call this operation a refactoring, because it doesn't change the set of tests that pass.

Once upon a time, there was a video interview with Martin Fowler where he was vehement that changing any observable behavior -- even those which you weren't currently observing -- was out of bounds.

For example, when starting new work, we often complete the GREEN task by returning a fixed constant.  "Everybody" agrees that you can now clean up the implementation as much as you like, so long as the function returns that fixed constant regardless of your inputs.

But this often means data duplication - the constant hard coded into the implementation is a copy of information that lives in the test; the expected return value of the function.

If you think removing this duplication is important, then maybe you aren't going to care so much about the refactoring rule that says that all observable behaviors must be preserved.

On the other hand, if you think the refactoring rule is important, well... it's not like implementing a second test is all that far out of your way, and if you are clever about the refactoring you do beforehand, you won't need very much wall clock time at all to get this second test passing.

My view is that I don't need permission from the test to improve the code ("the code works for me, I don't work for the code").  And if a purist I'm pairing with object that such a change isn't a refactoring - fine, I won't call it a refactoring..., but I'm going to make a case for doing it whatever we call it.

Leap-of-faith refactoring is exactly what we're trying to avoid with our strategy of small steps and concrete feedback.

Mikado method might be a useful technique here - try something that could work, in the event of negative feedback revert the change, and start building out the graph of small changes that you need to make to get to the end.

First, isolate the part that has to change.

Which calls back to Parnas on Information Hiding, or the advice to write code that is easy to delete.

How do you simplify control flows that have become too twisted or scattered?  Replace a method invocation with the method itself.

This is a Big Power Move.  Even if you ultimately revert the code back to its original state, being able to see what the program is doing, released from the abstractions obscuring that, can be a huge time saver.  See also Carmack 1997.


No comments:

Post a Comment