I need to talk about the developer who refactored our entire codebase over a weekend.
He replaced every function longer than 10 lines. Extracted every condition into a named method. Created abstractions for abstractions. He was proud. He sent the PR on Monday morning with the message: "Cleaned up the code ๐งน"
The PR had 4,200 lines changed.
It took three people two weeks to review. We found bugs that didn't exist before. The code that used to be a little messy but obvious was now pristine and completely incomprehensible.
He was following every rule in the book. And the code was worse for it.
The Clean Code trap
Somewhere along the way, "clean code" stopped being a philosophy and became a religion.
You know the commandments:
- Functions should be short
- Names should be descriptive
- Don't repeat yourself
- Single responsibility principle
- No comments โ the code should be self-documenting
Individually, these are fine guidelines. Applied without judgment, they're a disaster.
When short functions make code worse
Here's a real pattern I see constantly:
def process_order(order):
validate_order(order)
calculate_total(order)
apply_discount(order)
save_order(order)
send_confirmation(order)
Looks clean, right? Five short functions. Single responsibility. Uncle Bob would be proud.
Now try to understand what validate_order actually checks. You jump to that function. It calls three more functions. Each of those calls two more. You're seven levels deep and you've forgotten what you were looking for.
Compare:
def process_order(order):
# Validate
if not order.items:
raise EmptyOrderError()
if order.total < 0:
raise InvalidTotalError()
# Calculate
order.total = sum(item.price * item.qty for item in order.items)
if order.coupon:
order.total *= (1 - order.coupon.discount)
# Save and notify
db.save(order)
email.send_confirmation(order.user, order)
Longer? Yes. "Dirty"? By the book, sure. But I can read it top to bottom and understand the entire flow in 30 seconds. No jumping. No context switching. No "what does this function actually do" detective work.
Sometimes a 30-line function is cleaner than five 6-line functions.
The naming disease
public boolean isEligibleForPremiumDiscountBasedOnAccountAgeAndPurchaseHistory()
This is not a good name. This is a sentence pretending to be an identifier.
The "no comments, names should explain everything" rule produces this kind of code. Developers try to encode the entire function's behavior into its name because they've been told comments are a failure.
Comments aren't a failure. A well-placed // Users with 2+ years and $500+ in purchases get 15% off takes one second to read. That method name takes five seconds and still doesn't tell me the thresholds.
DRY gone wrong
I watched a junior extract a shared utility function because two endpoints had similar-looking validation logic. Three months later, that utility had 14 parameters and an options object because every new use case needed slight variations.
The original "duplication" was two functions with 5 lines each. Clear. Independent. Easy to change.
The "clean" version was a 60-line generic monster that nobody dared to touch because changing it might break 8 different endpoints.
Sometimes duplication is cheaper than the wrong abstraction. That's not my hot take โ that's Sandi Metz, and she's right.
Self-documenting code is a myth
Code tells you WHAT is happening. It cannot tell you WHY.
No amount of clean naming will explain:
- Why you're using a timeout of exactly 3000ms (because the vendor's API rate-limits at 20/min)
- Why you're sorting results client-side instead of in the query (because the DB index makes ORDER BY slower for this specific table size)
- Why this weird edge case handler exists (because a customer in Japan discovered this bug in production at 2am and we hotfixed it)
These are comments. They're not code smells. They're context that the next developer needs to not break things.
Deleting comments because "clean code is self-documenting" is deleting knowledge.
The actual rule
Clean code isn't about following rules. It's about empathy.
Will the next person who reads this understand it quickly? That's it. That's the entire metric.
Sometimes that means short functions. Sometimes it means a long one with comments. Sometimes it means duplicating code. Sometimes it means a 40-character variable name.
The answer is always "it depends," and anyone who tells you otherwise is selling a book.
How to actually write readable code
Optimize for reading, not writing. You write code once. People read it hundreds of times. If it's slightly more effort to write but way easier to read โ do that.
Extract functions when they have a reason to exist. Not when a function hits some magic line count. If the extracted function would only ever be called from one place and its name is just a description of the code inside it โ leave it inline.
Write comments for "why," not "what." // increment counter is useless. // retry up to 3 times because the payment gateway drops ~2% of first attempts is invaluable.
Ask your team. "Clean" is not objective. It's whatever your team can maintain. If everyone on the team finds the code clear, it's clean. Even if Uncle Bob wouldn't approve.
What's the worst "clean code" refactor you've witnessed? I know you have a story.
Top comments (0)