True. It's a lot easier to work with duplicates IF someone keeps tracking them. It becomes shit show if no one watching. See all those MVC projects on c# which are still on .net 4.5.
I always see people adding a DRY violation review comment. This article is an eye opener for them.
This is the TLDR
DRY is not about code duplication; it’s about the representation of knowledge. If two pieces of code that represent different pieces of knowledge happen to be identical, they are not a DRY violation, and it would be a mistake to factor them into a single place.
That's an extreme case but here's a less extreme one in the same spirit. You want to verify hmacs. You have stored requests in the old way and the new way, and you want to reprocess them.
def check_hmac(request):
# we used to use a different kind of secret
if request.v1:
secret = old global secret
else:
secret = go get the user's secret
# we used to use an older hash
if request.v1:
hmaccer = Hmaccer.sha1
else:
hmaccer = HMaccer.sha265
# compute it!
hmac = hmaccer.create with hmaccer
hmac.set secret(secret)
hmac.add(request.body)
if request.v2:
hmac.add(request.salt)
digest = hmac.compute
# oops we used to have a bug here
if request.v1:
if digest is too short:
digest = '0' + digest
return digest
I see this structure often, where it's set up to reduce the duplication of the actual hmac computing bit in the middle but it ends up a mess of configuration paths. If you weren't afraid of a little bit of duplication, you could do
def check_hmac(request):
# if request.v1:
hmac = Hmaccer.sha1
hmac.set secret(old global secret)
hmac.add(request.body)
if digest is too short:
digest = '0' + digest
return hmac.compute
else:
hmac = HMaccer.sha265
hmac.set secret(go get the user's secret)
hmac.add(request.body)
hmac.add(request.salt)
return hmac.compute
This is much easier to reason about. The fact that they both use a few nearly identical lines for these two different algorithms is almost a coincidence, and the work required to make them share the code leads to it being very difficult to read, follow, and fix.
This is a great example of where fear of duplication leads to worse code.
If you think top-down and decompose by domain relevance, it’s obvious: request is a discriminated union (v1 vs v2), so case-analyze it directly. Each variant has its own logic, and that logic should live in its own branch or function. Trying to DRY up similar-looking logic in the middle obscures meaning, increases cognitive load, and makes it easier to mix up paths (e.g., accidentally salting v1, or skipping the ‘0’ fix).
Same goes for create_username vs generate_filename. They start similar, but serve different domain purposes—so you shouldn’t collapse them into a single function. It’s totally fine to extract shared logic into a helper like create_slug, but that function should exist beneath the domain abstractions, not replace them. create_username and generate_filename should each call create_slug, keeping their own names and boundaries so they can diverge later if needed. That way, you reduce duplication without sacrificing clarity or future flexibility.
The key isn’t avoiding repetition—it’s preserving clarity and domain alignment. Composition and separation of concerns will naturally handle reuse when it actually exists.
DRY makes devs building the worst abstractions. When you refactor to combine two things that coincidentally behave the same you signal to the future that they must behave the same. This becomes such a liability when new requirements demand they drift.
It's not always popular to err on the side of repetition but fixing duplication is always easier than de-tangling unnecessary coupling.
I've always thought this quote has all too often led software designers into serious mistakes because it has been applied to a different problem domain to what was intended. The full version of the quote is "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil." and I agree with this. Its usually not worth spending a lot of time micro-optimizing code before its obvious where the performance bottlenecks are. But, conversely, when designing software at a system level, performance issues should always be considered from the beginning. A good software developer will do this automatically, having developed a feel for where performance issues will cause problems. An inexperienced developer will not bother, misguidedly believing that a bit of fine tuning at a later stage will fix any problems.
eocron06@reddit
True. It's a lot easier to work with duplicates IF someone keeps tracking them. It becomes shit show if no one watching. See all those MVC projects on c# which are still on .net 4.5.
Southern-Reveal5111@reddit
I always see people adding a DRY violation review comment. This article is an eye opener for them.
This is the TLDR
BogdanPradatu@reddit
Can you give an example of two identical pieces of code that act on different "knowledge"?
ketralnis@reddit (OP)
That's an extreme case but here's a less extreme one in the same spirit. You want to verify hmacs. You have stored requests in the old way and the new way, and you want to reprocess them.
I see this structure often, where it's set up to reduce the duplication of the actual hmac computing bit in the middle but it ends up a mess of configuration paths. If you weren't afraid of a little bit of duplication, you could do
This is much easier to reason about. The fact that they both use a few nearly identical lines for these two different algorithms is almost a coincidence, and the work required to make them share the code leads to it being very difficult to read, follow, and fix.
csman11@reddit
This is a great example of where fear of duplication leads to worse code.
If you think top-down and decompose by domain relevance, it’s obvious:
request
is a discriminated union (v1 vs v2), so case-analyze it directly. Each variant has its own logic, and that logic should live in its own branch or function. Trying to DRY up similar-looking logic in the middle obscures meaning, increases cognitive load, and makes it easier to mix up paths (e.g., accidentally salting v1, or skipping the ‘0’ fix).Same goes for
create_username
vsgenerate_filename
. They start similar, but serve different domain purposes—so you shouldn’t collapse them into a single function. It’s totally fine to extract shared logic into a helper likecreate_slug
, but that function should exist beneath the domain abstractions, not replace them.create_username
andgenerate_filename
should each callcreate_slug
, keeping their own names and boundaries so they can diverge later if needed. That way, you reduce duplication without sacrificing clarity or future flexibility.The key isn’t avoiding repetition—it’s preserving clarity and domain alignment. Composition and separation of concerns will naturally handle reuse when it actually exists.
BogdanPradatu@reddit
Ok, your hmac example wasn't that convincing, but I agree with the slug thing conceptually.
Coda17@reddit
I've been trying to get my coworkers to understand this and it's been a rough journey.
NinjaComboShed@reddit
DRY makes devs building the worst abstractions. When you refactor to combine two things that coincidentally behave the same you signal to the future that they must behave the same. This becomes such a liability when new requirements demand they drift.
It's not always popular to err on the side of repetition but fixing duplication is always easier than de-tangling unnecessary coupling.
church-rosser@reddit
sauce