> Well Isaac, refactorings does not preserve functionality > by itself, and you need the support from unit tests to do > exactly that. > I would be more general and say that you need to test your changes to make sure they do what you want. If you're adding new functionality, you need to test to make sure the new functionality works as desired and that you didn't break old functionality. Unit tests are one kind of test, but they aren't the only kind.
> How can you prove that nothing was > broken during refactoring without tests? > Prove is a strong word, but I think you're saying how can we be confident we didn't break anything during refactoring without tests. I think even for the simplest most obvious looking changes, we'd better test it. "I'm sure this will work" often turns out to be overconfidence. But once again, it doesn't necessarily require a unit test.
> Of course you can > do it manually, but then, the costs and dangers of > refactoring just touched the sky! > And I think one should consider the costs and benefits and make decisions using experience and the best information you have at the time. One problem I have with XP is that it seems to suggest you don't think, you always write a unit test first, without doing a cost/benefit analysis. I definitely like TDD, but it is not my religion. It is a tool. If you are facing a legacy system with no unit tests, writing unit tests for the whole thing just so you can be confident you didn't break anything with a bug fix or refactor also has a cost that touches the sky.
Faced with needing to fix a bug in a legacy system that has no tests, and feeling the desire to clean up a bit around where you make the change, I think the best thing to do is write a unit test that fails because of the bug, then fix the bug (verify it with the passing test), make some minor cleanup if you wish, but maybe not just to minimize risk, and then test it by hand.
On the other hand, sometimes it isn't so cheap as just whipping off a unit test. Sometimes to write a unit test, you really need to spend some time thinking about and building a test harness. If the bug fix is urgent, the customer is threatening to go with a competitor's product if they don't get the fix tomorrow, and you think it might take you a few days to figure out how to establish a test harness, then it is much better for the business to forget unit testing altogether, and just make the fix, test it by hand, and ship it off to the customer.
Or, if this legacy code is for a legacy product that's pretty stable, and is being replaced by a different product on a completely different codebase, then maybe you'll judge it isn't worth spending the time needed to figure out and build the test harness at all even if the customer isn't breathing down your neck, that those resources could better be spent elsewhere, such as on the new codebase.
> Dag Blakstad wrote How can you prove that nothing was > broken during refactoring without tests? > By proof :-) (Tests don't prove.)
I would argue that "proof" in the purest form doesn't exist in software development. I bet the developer "proved" his code worked before shipping the product in the first instance yet this bug was still found afterwards. If given a well specified use-case for the failing software condition then an appropriate unit test can prove with as much certainty as a manual test in most cases.
As for Bill's comments on TDD I agree with most of what he says. As Engineers we're compelled to consider such factors as budgets, deadlines, administration and in the cases mentioned there is nothing wrong with cutting some corners on our usual practices (usual practices because TTD is a way of thinking and not a set of rules).
> It's interesting that this discussion doesn't mention > requirements. How can you test something if you don't know > what it is supposed to do? If you reverse-engineer the > requirements from the code than obviously the code is 100% > correct.
You can write tests that demonstrate the actual behavior of the system. Often that is the invariant you have to work against when you refactor.
> If you are facing a legacy system with no unit > tests, writing unit tests for the whole thing just so you > can be confident you didn't break anything with a bug fix > or refactor also has a cost that touches the sky.
I agree. That's why I advise getting tests in place only for areas you are about to change. Go upstream from the change points, find a place to write tests, break dependencies, write the tests, then make the change.
The tough part is breaking dependencies where you need to to write the tests. It is a form of refactoring, but there are a set of very conservative techniques you can use to feel confident that nothing is being broken as you get tests in place. When the tests are in place, it's easier to be more aggressive.
> > You can break things very easily by editing code, but > > not by refactoring - that's the point ;-) > > This reminds me of my youth when my mother wanted me to > get a haircut. She said: You don't have to get it cut, > just "shaped". How can you refactor code without editing > it?
I'm just being boorish about what we actually mean by refactoring ;-)
imo there's something special about refactoring, something that gives it special value - refactoring preserves behaviour.
When we have a tool that automates refactoring then we don't need additional testing, we refactored therefore behaviour was preserved. The only changes we were allowed to make were changes that preserved behaviour.
> When we have a tool that automates refactoring then we > don't need additional testing, we refactored therefore > behaviour was preserved. The only changes we were allowed > to make were changes that preserved behaviour.
This assumes that the autorefactoring tool has no bugs and can refactor any code without any possibility of changing the behavior.
It seems to me that it wouldn't be that hard to write code using reflection whose behavior will change with just a change in method names.
This is artifical example perhaps, but it points out that no method of code transformation is entirely behavior-preserving in the general case unless it understands that underlying behavior.
Now we can say that refactoring is behavior-preserving by definition, but in that case I would conclude that there are no autorefactoring tools because they can't meet the absolute requirements of refactoring.
> It seems to me that it wouldn't be that hard to write code > using reflection whose behavior will change with just a > change in method names. > > This is artifical example perhaps, but it points out that > no method of code transformation is entirely > behavior-preserving in the general case unless it > understands that underlying behavior. > > Now we can say that refactoring is behavior-preserving by > definition, but in that case I would conclude that there > are no autorefactoring tools because they can't meet the > absolute requirements of refactoring.
Previous comments provided other examples of code artefacts which are outside the ken of the "refactoring tool", imo in those situations we don't have a "refactoring tool" - it's broken. ;-)
> Previous comments provided other examples of code > artefacts which are outside the ken of the "refactoring > tool", imo in those situations we don't have a > "refactoring tool" - it's broken. ;-)
So in my view we either take a less strict interpretation of refactoring that may require retesting or use the strict interpretation of refactoring which is not very useful or interesting in practice.
> So in my view we either take a less strict interpretation > of refactoring that may require retesting or use the > strict interpretation of refactoring which is not very > useful or interesting in practice.
Did you mean, take a less strict interpretation of refactoring that always requires retesting?
> not very useful or interesting in practice When software development fits within the limitations of the refactoring tools, we can avoid unnecessary work. YMMV
> Did you mean, take a less strict interpretation of > refactoring that always requires retesting?
No, a less strict interpretation of refactoring that allows for the possibility that the transformation process wasn't entirely behavior-preserving. In other words, that the attempt at refactoring has failed. In some cases we may be able to tell that the refactoring was sucessful by inspection or we may have to perform testing to be sure.
> When software development fits within the limitations of > the refactoring tools, we can avoid unnecessary work. YMMV
My argument is that the restricted set of refactoring that an automated tool can flawlessly perform will often be of little value. For example refactoring a integrated GUI application into a MVC application may improve the design, but I doubt that an automated tool could perform that refactoring entirely on its own. But I agree that YMMV.
Flat View: This topic has 24 replies
on 2 pages
[
«
|
12
]