As I stated in the answer to another question, my approach is the following:
- The first time I solve a certain problem, I just get it done.
- The second time (i.e. when I solve a similar problem) I think: hm, maybe I am repeating myself, but I will go for a quick copy-and-paste for now.
- The third time I think: hm, I AM repeating myself -> make it general!
I.e. up to 2, another principle (YAGNI) wins over DRY. But starting from 3 (or 4 if I am really lazy!) it seems I AM gonna need it and so I follow DRY.
Some further ideas from my recent experience. I had to adapt / integrate two components A and B developed by another team into our product. First: the two components A andb B are very similar to each other, so I was already disturbed by the fact that they had a somewhat different architecture. Second: I had to adapt them so I would have been glad to use subclasses and only override what I really needed.
So I started refactoring these two components (each of which consists of about 8 C++ classes): I wanted to have a common architecture for both A and B, and then add the features we need by defining subclasses. In this way, our two new components A' and B' would have been derived from the existing ones.
After two weeks trying to get a common and well-defined structure out of the existing code and having to explain during our daily meetings that I was making little progress because the original code was too messy, I spoke to my boss. We observed that we were not going to need anything more than these two new components A' and B' (there were not going to be four or six of them, just those two).
Ok, so be it: I did a massive copy and rename of classes from A and B and started to adapt the copy of the code. I got it to work in two more weeks (still doing some bug-fixing now).
Advantages: We have the functionality almost finished now and when we have fixed all the bugs we are finished. We have saved all the refactoring and testing of A and B.
Disadvantages: Two weeks ago the other team changed another component C, which is used by A and B. They adapted A and B but A' and B' were also broken and we had to change them ourselves. This introduced a new bug that we had to fix. This extra work would probably have been unnecessary if A' and B' had shared most of their code with A and B.
So: code duplication is always dangerous. I think it is always a matter of finding trade-offs and often it is not easy.