We’ve all been there - staring at two nearly identical code blocks like confused twins at a family reunion. “Aren’t you Billy?” “No, I’m Bob!” “But you both have the same nose!” This cosmic code duplication is exactly what the DRY (Don’t Repeat Yourself) principle tries to prevent. Let’s explore how to wield this principle without turning our codebase into an over-engineered Rube Goldberg machine.
The DRY Principle Demystified
DRY isn’t just copy-paste prevention - it’s about knowledge management. As defined in The Pragmatic Programmer, it states:
“Every piece of knowledge must have a single, unambiguous, authoritative representation.” Consider this SQL horror show:
-- Report A
SELECT
user_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) AS shipped_orders
FROM orders
WHERE created_at > '2025-01-01'
GROUP BY user_id;
-- Report B
SELECT
user_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'returned' THEN 1 ELSE 0 END) AS returned_orders
FROM orders
WHERE created_at > '2025-01-01'
GROUP BY user_id;
The cure? Abstract the common pattern:
CREATE MACRO order_summary(status_filter) AS (
SELECT
user_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = status_filter THEN 1 ELSE 0 END) AS filtered_orders
FROM orders
WHERE created_at > '2025-01-01'
GROUP BY user_id
);
-- Usage
SELECT * FROM order_summary('shipped');
SELECT * FROM order_summary('returned');
Now when the date filter changes, we update one location instead of playing whack-a-mole.
When DRY Turns Sour
I once encountered a “helper” function that looked like it was trying to solve world hunger. The original developer had abstracted so aggressively that the code required a compass and a sherpa to navigate. Remember: abstractions should reduce complexity, not create it.
# The crime against readability
def process_data(data, format='json', validate=True, sanitize=False,
log_level='DEBUG', callback=None, retries=3):
# 200 lines of conditional spaghetti
...
vs.
# Simple and focused
def parse_json(data):
...
def validate_user_input(data):
...
# Composable and clear
valid_data = validate_user_input(parse_json(raw_data))
The Goldilocks Zone of Code Reuse
- Detect duplication through pattern recognition
- Same code with different literals? Parameterize it
- Similar logic across multiple services? Create shared library
- Apply the Rule of Three
- First occurrence: Let it be
- Second occurrence: Make a note
- Third occurrence: Time to abstract
- Consider volatility
- Will future changes affect all instances equally?
- Is the abstraction domain-specific or generic?
- Test the waters
// Before DRY function calculateTax(price) { return price * 0.2; } function calculateVAT(amount) { return amount * 0.2; } // After DRY const TAX_RATE = 0.2; const calculateTax = (value) => value * TAX_RATE;
But watch out for false positives! Similar-looking code doesn’t always mean identical purpose. I once “fixed” two similar-looking payment processors only to learn one was handling yen and the other euros. Oops!
Survival Tips for DRY Practitioners
- Document abstractions religiously
That clever generic parser you made? Six months later it’ll look like ancient hieroglyphics without comments - Use code generation wisely
For boilerplate that’s tedious but stable:# Generate CRUD endpoints ./generate-endpoint.sh User name:string email:string
- Embrace the cheat code of modern IDEs
Use structural search/replace across projects to find duplication candidates - Know when to break the rules
Prototype code? Maybe duplication is faster than proper abstraction. Just put a// TODO: Refactor before ship
comment (and actually do it!)
The Maintenance Paradox
Here’s the dirty secret no one tells you about DRY: Good abstractions require maintenance too. It’s like buying a purebred dog - yes, it’s elegant and refined, but now you’re committed to regular grooming. The sweet spot? Treat DRY like hot sauce - enough to enhance the flavor, but not so much that it burns down the whole system. Your future self (and your teammates) will thank you when that next requirement change comes rolling in. Now if you’ll excuse me, I need to go check if I accidentally created a circular dependency in my metaphor about code quality…