Exactly! A lot of developers, even experienced (and I would include the OP, even after his supposed lesson) seem to not get this: duplication is only bad when what is being duplicated MUST behave the same by definition (i.e. if you change one but not the other, the other would've become broken as its definition would still be the same as the changed one). Otherwise, two similar or identical codes are NOT duplicated in a way that violates DRY, they are just coincidental - as they might become different later - so the "duplication" in this case must NOT be removed (but perhaps a smaller portion of the code might be duplication of definition, which should be then extracted and de-duplicated). It really isn't that hard.
Your experience is different, you always know when you have multiple requirements and should DRY, and when instead you have "incidental [or accidental] duplication" and de-duplciation would not be advisable and thus not be called "DRY"?
If so, I wonder what leads to our different experiences.
It's very possible that DRY could apply even where divergence is expected. Suppose we had a requirement that "A user should not be able to resize a TextBlock if they are not allowed to read the text." At the outset, the read & resize permission logic might be identical & DRY is easy to apply. Nobody would be surprised if eventually resizing permissions became more restrictive. Even at that point, DRY applies. If you repeated the shared logic, and later you add new restrictions to the read permissions, it'd be easy to introduce a bug where you forget to restrict resizing permissions accordingly.
Sure, though that’s not quite what the new code did.
Instead, it added a new abstraction (createBox), and then used that to incidentally assign the same behavior to TextBlocks and Rectangles. Your change could easily be accomplished by changing the last line to check the permissions and call createBox or something else, depending. I’d guess that createBox would also be useful for many other shapes, but if you wanted to do something totally different, like a star with handles at each point, you could just....not call createBox, and write a totally independent “let star()” function that does its own thing; it doesn’t seem like a base class constructor is forcing you to call createBox or anything.
So....this code doesn’t actually seem that bad to me. What am I missing?
But they had the same current requirements. So, ideally, the implementation of the common behavior should have been shared but shared in a way which didn't tightly couple the two consumers with each other, so that it would be simple for either to stop using the shared code.
If they had separate classes for TextBlock and Rectangle, then likely there are separate requirements involved. It may have been that at the time they needed separate classes the requirements for resizeTopLeft was identical, but they are still two separate requirements.
I suggest everyone take a minicourse or read a book on systems engineering - at least the part where they discuss requirements. The fact that two distinct entities have an identical requirement for something does not mean there is one requirement. It means there are two requirements which (for now) happen to be identical.
The key phrase is happen to be. You do not apply DRY to separate requirements even if they are the same/similar.
Look at it from a testing perspective[1], which is similar to how a systems engineer will look at it. If you would write a separate test for it, it is a distinct requirement. (Actually, the ordering should be the opposite: If it is a separate requirement, it should have a separate test).
[1] And not from a TDD perspective!
I'd love to do this -- do you have any recommendations for a book like this?
https://news.ycombinator.com/item?id=22022599
https://news.ycombinator.com/item?id=22022842
https://news.ycombinator.com/item?id=22022896
https://news.ycombinator.com/item?id=22022836
The author of this piece was not engaging in a DRY activity even if he thought he was. He (perhaps unwittingly) admits to it himself:
> My code traded the ability to change requirements for reduced duplication, and it was not a good trade.
The acronym DRY was coined in The Pragmatic Programmer, and the author of that book makes it clear that DRY is about not repeating requirements in code. You definitely don't want to deduplicate similar code if it involves multiple requirements because then you entangle yourself, which is what the author of this piece did.