See also RefactoringTrumpsYagni
There are basically two ways to do a refactoring with fairly large scope. The first way I call "Nose Job" - in order to do a nose job, the plastic surgeon first breaks the nose, then reassembles all the broken bits into the new design. So the Nose Job Refactoring means, first change all
the method signature(s) to match the new design, then compile and see what breaks, and fix all the broken callers. If you're lucky, and the refactoring is straightforward enough, then once it compiles, all tests will pass and you're done.
(In this case, the Nose Job would change all
methods in Library that take a parameter "String email" to "User user", then walk through all
the calling code and clean up.)
Unfortunately, this only seems to work about half the time. The other half, even once you change all the callers, some UnitTest
s fail. If you're lucky, you can easily fix these, but if not, you have to roll back all
your changes (sob) and do the more laborious, incremental, Martin Fowler Approved kind of refactoring: change one signature, fix all callers, pass all tests, check in, repeat.
The reason Nose Jobs are preferable is that you can get into a Flow state walking through a file and changing things to the new design everywhere. In the Incremental refactoring, you're often in a file changing one caller, and you see a line of code calling a different method the wrong way, and you just itch
to clean that up too, and you know
it would take just one second... But you have to control yourself, since you know that that one just might be the evil caller
where the semantics changed enough to cause a test to break.
(What do I mean by causing a test to break semantically? In one case, an XPath expression failed to find a match, and it wasn't a simple thing like accidentally forgetting to change out.print(user) to out.print(user.getEmail()) (leading to "expected:<email@example.com> but was:<polonius.User@43772329>"), but a non-obvious bug.)
Fowler/Beck talk about the temptation to do this kind of thing in RefactoringImprovingTheDesignOfExistingCode
. I used to do it a lot myself. Now, whenever I find myself in a NoseJobRefactoring
, I kick myself. It still happens, sometimes. But less and less often. In general, I have found that the time I lose in splitting into lots of small refactorings is more than outweighed by the time I lose when nose jobs go wrong. Not a pretty sight. -- Anon
In the bad case, NoseJobRefactoring
. -- Anon
has a similar analogy on his HomePage
, but with solving Rubik's cube instead of fixing noses. I rather like it, but I can't think of a suitable pair of names. -- MatthewAstley
I recently avoided the consequences of doing a NoseJobRefactoring
by using a good refactoring IDE, in my case IntellijIdea
for Java. The ChangeMethodSignature?
feature saved me a lot of time. Not every language has refactoring tools, but if you're working in one that does (such as Java or SmallTalk
), I highly recommend using one. -- ??