DEV Community

Cover image for Why the Best Software Advice Is the Hardest to Follow
Remo H. Jansen
Remo H. Jansen

Posted on

Why the Best Software Advice Is the Hardest to Follow

Readers debate the limits of rigid rules

Not all software advice is created equal. Some advice is practical. It tells you exactly what to do:

  • Write small functions.
  • Keep functions focused.
  • Use constants instead of magic numbers.
  • Give variables meaningful names.
  • Keep your classes small.

This kind of advice is useful because it is easy to understand, easy to teach, and easy to verify. You can look at a piece of code and ask: Is this function too large? Is this a magic number? Is this constant named correctly?

There is usually a relatively clear answer. And because this advice is so easy to follow, it tends to spread. Teams put it into coding standards. Linters enforce it. Code reviews check it. Developers learn it early in their careers. But there is another kind of advice. It is much harder to follow. And, in my experience, it is often much more valuable.

Two kinds of advice

I think about software advice as belonging roughly to two categories. The first is practical advice. It is close to black and white:

Do this.
Don't do that.

The second is judgment-based advice.

It doesn't give you a recipe. Instead, it gives you a principle that you have to interpret depending on the situation. It might even contradict another principle you have learned. This kind of advice is closer to a balance between two opposing forces. There isn't always a universally correct answer. And that's what makes it difficult.

Practical advice is easy to scale

Take a piece of advice like:

Write small functions.

That's generally useful advice. A junior developer can understand it. A senior developer can teach it. A code reviewer can point at a 200-line function and say, "This should probably be split up."

The same is true for advice such as:

Don't use magic numbers.

Or:

Use meaningful names.

Or:

Keep your classes focused.

These are great pieces of advice because they reduce the number of decisions you need to make.

You don't need years of experience to understand what they mean. You can learn them from a book, apply them tomorrow, and see an immediate improvement. This is one of the reasons books like Clean Code became so influential. A large part of the advice is actionable.

You can take a principle and immediately translate it into something you do while writing code. And that's valuable. But there is a danger. We can start confusing following good practices with becoming a good engineer.

When advice stops being black and white

Consider a different piece of advice:

The wrong abstraction is more costly than duplication.

This sounds simple, but try turning it into a rule. Should I duplicate this code? Sometimes yes. Sometimes no.

How much duplication is acceptable?

How similar do two pieces of code need to be before they should be abstracted? What if they look similar today but are likely to evolve differently?

What if the abstraction makes the current code more complicated but might save us from duplication later? There is no simple checklist that answers these questions. You need judgment. And judgment comes from experience. You have to have seen abstractions that worked well. You also have to have seen abstractions that turned into monsters.

You need to experience the cost of changing an abstraction that was designed too early. You need to see duplication that was completely harmless. And you need to see duplication eventually become a maintenance nightmare. Only then does the advice start to become useful at a deeper level. It stops being a rule and becomes a way of thinking.

Experience changes the meaning of advice

This is something I find fascinating about software engineering. You can hear exactly the same advice at different stages of your career and understand something completely different each time. Early in your career, someone tells you: "Don't repeat yourself".

You learn DRY. So you start looking for duplication everywhere. Two pieces of code look similar? Abstract them. A few parameters are repeated? Create a common object. Several classes have similar behavior? Create a base class. You are following the advice. Then, years later, you encounter another principle:

Prefer duplication over the wrong abstraction.

And suddenly DRY doesn't look so simple anymore. You realize that duplication isn't necessarily the problem.

Sometimes duplication is the price you pay for keeping two concepts independent. Sometimes an abstraction creates more coupling than the duplication ever would have. Sometimes the best thing you can do is repeat yourself and wait until the domain tells you what the abstraction should actually be.

The advice didn't change. Your ability to interpret it changed. That's what experience gives you.

From rules to intuition

There is a point in your career where you accumulate enough examples that you start recognizing patterns before you can fully articulate them. You look at an abstraction and something feels wrong. You can't immediately explain it. You might say:

"I don't think we should abstract this yet."

Why? Maybe you've seen this exact situation before. Maybe you've experienced how these abstractions evolve. Maybe you recognize that two things that currently look identical are likely to change for completely different reasons.

This is where intuition starts becoming important. Intuition in software engineering isn't magic. It isn't a replacement for thinking. It is often the result of accumulated experience.

You've seen enough situations, failures, trade-offs, and consequences that your brain starts recognizing patterns automatically. The problem is that intuition is much harder to teach than a rule. I can teach you "Use small functions." in five minutes.

I can't give you five minutes of advice that will make you understand when a function is too large, why it is too large, and when splitting it would actually make the code worse. That requires experience.

The uncomfortable part of judgment-based advice

There is another reason this kind of advice is difficult. Sometimes two pieces of good advice conflict. You might hear:

Keep things simple.

And:

Don't duplicate code.

And:

Avoid premature abstraction.

And:

Encapsulate things that change.

All of these can be good advice. But what happens when following one makes it harder to follow another?

There is no compiler warning for this. There is no linter that can tell you which principle should win. You have to make a decision. And that decision depends on context.

This is why experienced engineers can sometimes disagree about a piece of code while both having perfectly reasonable arguments. The disagreement isn't necessarily because one person knows the rules and the other doesn't.

It may be because they're making different judgments about the future, the domain, the cost of change, or the risks involved. That's the gray area of software engineering.

Scrum is another interesting example

I think the same distinction can be seen outside of code. Consider Scrum and the Agile Manifesto. Scrum gives you something concrete. Roles, Events, Artifacts and Rules.

A team can learn Scrum and start implementing it relatively quickly. That makes it attractive to organizations. There is something reassuring about being able to say:

"We are doing Scrum."

There is a framework. There are defined practices. There are ceremonies you can schedule. There are artifacts you can create. There are things you can point to and say, "We're doing it."

The Agile Manifesto is different. It gives you four values and twelve principles, but it doesn't give you a complete operating manual for running a software organization.

It tells you things like valuing individuals and interactions over processes and tools, and responding to change over following a plan. Those statements require interpretation. They require judgment.

For example, valuing individuals and interactions over processes doesn't mean processes are useless. Responding to change doesn't mean planning is useless.

The interesting question is not:

"Which one do I follow?"

The interesting question is:

"How do I apply these principles in this particular situation?"

And that is much harder. You can implement Scrum without necessarily developing the judgment behind Agile.

You can have all the ceremonies, all the boards, all the roles, and all the terminology and still miss the underlying principles.

That's the difference between following a framework and understanding the principles behind it.

Why the easy advice wins

There is a natural reason practical advice dominates.

  • It is measurable.
  • It is teachable.
  • It is reviewable.
  • It is scalable.

A company can create a coding standard that says:

Functions should be small.

It is much harder to create a standard that says:

Use your accumulated experience and judgment to determine the appropriate level of abstraction for the current domain and its likely future changes.

The first can become a rule. The second requires a person who knows what they're doing. Organizations naturally prefer things that can be turned into rules. And this isn't necessarily bad. Practical advice is extremely useful. The problem starts when we believe that collecting enough practical rules will eventually give us good judgment.

It doesn't work that way. Knowing more rules doesn't automatically make you better at making trade-offs. At some point, you need to develop the ability to decide which rule matters in this situation.

The hardest advice may be the most valuable

This is why I think some of the most valuable software advice is also the hardest to follow.

It doesn't tell you exactly what to do.

It makes you think. It gives you a lens through which you can look at a problem. It might even force you to balance two competing ideas. And initially, that can be frustrating. We want software engineering to have recipes.

  • Tell me the right architecture.
  • Tell me when to abstract.
  • Tell me how big a function should be.
  • Tell me exactly when to introduce a design pattern.
  • Tell me the correct process for building software.

But software is built in contexts.

  • Different teams.
  • Different domains.
  • Different constraints.
  • Different people.
  • Different levels of uncertainty.

The more experience you gain, the more you realize that many of the most important engineering decisions don't have a universally correct answer. They require judgment. And judgment is difficult because you have to own the decision. There is no checklist to hide behind.

Maybe that's the real progression

Perhaps becoming a better software engineer isn't about replacing bad rules with better rules. Maybe it's about gradually moving from rules toward principles.

From:

"I was told to do this."

to:

"I understand why this is usually useful."

and eventually to:

"I understand the trade-off, and I can decide whether it applies here."

That's a much harder skill to develop. But once you develop it, practical advice becomes even more useful. You don't stop using the rules. You understand them better. You know when to follow them. You know when two rules conflict. And, perhaps most importantly, you know when not to follow them. That is where experience turns advice into wisdom.

What do you think?

I'm curious about your experience. What is the most pointless practical software advice you've encountered in your career? And on the other side: What is the most valuable piece of judgment-based advice you've learned? The kind of advice that didn't give you a rule to follow, but changed the way you think about software engineering.

I'd love to hear your examples in the comments.

Top comments (14)

Collapse
 
francistrdev profile image
FrancisTRᴅᴇᴠ • • Edited

Great article Remo! What I learn overtime is that it is easy to follow rules early on, but you have to be flexible as much as possible. When people just start to learn best practices, it's easy to apply because the things they are doing is relatively straightforward. Though as more experience gained, it tends to be complicated like the duplication example you mentioned.

Sometimes, you fall off the rails but as long as you stay near the rails, then you are fine :)

Collapse
 
remojansen profile image
Remo H. Jansen •

Yes. As you gain experience, it’s a lot like playing chess. You start thinking several moves ahead, weighing all the parallel trade-offs before choosing the best path forward.

Collapse
 
ben profile image
Ben Halpern •

Great post

Collapse
 
remojansen profile image
Remo H. Jansen •

Thank you!

Collapse
 
csm18 profile image
csm •

This one:
"Don't reinvent the wheel!"
In production, it makes sense, but while learning if we don't start from scratch, then we can't even make a linked list on our own!

Collapse
 
zxpmail profile image
zxpmail •

Great piece. I agree that practical advice scales because it’s teachable and verifiable, while judgment-based advice is harder to transfer and often more valuable. In the AI era, the real challenge is making that tacit judgment explicit enough for AI to consume—not as rigid rules, but as decision records, boundary conditions, counterexamples, evaluation sets, and context packs.

But I’d add a crucial division of labor: humans should focus on business clarification, domain decomposition, module boundaries, contracts, data ownership, and risk. Inside a module, whether to duplicate or abstract can largely be delegated to AI, as long as boundaries are hard and contracts are testable. Otherwise internal details leak and become architecture problems.

So humans stay on the loop (goals, constraints, accountability), in the seams (conflicts, interfaces, organizational gaps), and at the edge (taste, ethics, irreversible decisions). AI handles implementation; humans own boundaries and intent.

To answer the author’s question: the most pointless practical advice is any one-size-fits-all rule like “functions must be under X lines.” The most valuable judgment-based advice I’ve learned is “the wrong abstraction is more costly than duplication”—and its corollary: make the boundary clear first, then let the inside evolve.

Collapse
 
roc_zin_2006 profile image
Roc Zin •

Great article! Sometimes a little duplication is better than forcing things into the same structure too early. I’d rather wait until the pattern is clear than spend more time undoing a premature refactor.

Collapse
 
nour_dude_314 profile image
nourdude •

this may be offtopic but there's a point where advice or argument stops being advice or argument and starts being dogma. take goto for example. simple primitive. easy to use correctly, easy to use abusively. dijkstra said "a case against the goto statement". nikolas made it "go to statement considered harmful". now it's a dogma. and now everyone calls it evil.

Collapse
 
remojansen profile image
Remo H. Jansen •

It is not off-topic at all; you are on point. I think treating advice like dogma is a big problem, but it is an even bigger problem when it is in the context of judgment-based advice. As the name implies, it requires judgment, and dogmatic thinking is the opposite of that!

Collapse
 
nour_dude_314 profile image
nourdude •

basically exactly what i'm saying. that advice to dogma problem is now why everyone hates goto

Collapse
 
arthurborba profile image
Arthur Borba •

Good one :)

I remember my first programming teacher ever; pretty much for every question we shouted to him, he would start answering with "It depends..." We never got "an easy" advice from him, and the more I look back to hsi approach, the more I make more sense of it.

Collapse
 
vrunda_chauhan_a52cc23b11 profile image
Vrunda Chauhan •

That Scrum comparison makes a lot of sense. Teams love Scrum because you can schedule 15-minute standups, move Jira tickets, and pretend you're agile without changing how anyone actually works.

Linters and Clean Code checklists are the same thing. They're popular because you can enforce them in a PR without having to think about actual system design.

Collapse
 
unitbuilds profile image
UnitBuilds •

In a single word, constraint. How many developers does it take to screw in a lightbulb? 1 to do, 1 to write the tests? Wrong, it's a hardware problem.

We tend to overreach as developers, we're told 'implement x' and we see y is broken, but we depend on y, so we just patch it? Did you consider all the dependencies of y, before you changed it? How sure are you? That's where we've all been and where we've all failed.

Restraint is the lesson of a 'mature' developer (I'm immature)... Learning how to stick to task EXPLICITLY, is even more difficult now, when every AI you use is screaming at you 'but this needs fixing', 'please can I just fix it quick, PLEASE!!!!'. Ok exaggerating, but you've all seen it? AI either just fixes it for you, tells you afterwards, or it keeps telling you to fix it?

If you've been there, then you're like me, when you build a puzzle, you finish it, you dont just build the picture and leave the last 3 tiles out (because you know where they go). That's great in every scenario in life, EXCEPT development. Document it and leave it alone.

That's why we have issues, create one and backtrack when you're done, or raise it in the slack channel and get clearance first. If you get clearance, trace the dependencies and write tests to make sure there's no regression. Restraint isnt leaving it alone, it's taking your time to do it right, when the time is right.

So learn restraint. You dont need 50 tests for a single method and if you're asked to add a button, just add a button, dont overengineer it. You can always add to it later, but if you overengineer from the start, it's way more dangerous removing it, because you need to strip the docs, strip the tests, strip the code and fix compilation, JUST to backtrack it. Vs document and get clearance, do it incrementally as approved, so you isolate your fixes and dont pollute your worktree with bits and bobs.

I had to correct myself earlier on that 1. I got a hiring task, simple thing, 'create a POC of an abstract filesystem', HIGHLY restricted on the implementation requirements, specifically to catch overengineering. The purpose being that you write a POC, as fast and simple as possible, then expand on it as deemed worth it. It's easier to increment, than backtrack. The exercise is meant to weed out people who'd waste time. If I take 5 hours to complete it, they dont want half of it, it takes me 2 hours to backtrack. That's 7 hours, for 3 hours of work. So it's not just a skill you learn over time, it's 1 you need to actively enforce, especially with AI, because it's far too easy to ask 'what else can be made better' and expand, because the basic POC is 'too easy'.

So next time you implement something, ask yourself, did you keep it simple stupid (KISS)? Or did you overengineer it, making it more difficult to append and more fragile to maintain? Can you justify every single addition? Can you say with utmost confidence that without these additions, that it wouldnt meet the requirement?

Collapse
 
capestart profile image
CapeStart •

Good one, Remo. I’ve always found the annoying software lessons are the ones that don't have a yes/no answer. They usually end up being the ones I remember.