Programmer's bad habits
Every developer acquires habits. Some are deliberate—practices adopted after reading a good book or working with a senior engineer who knew what they were doing. Most are incidental—patterns that calcified early, before there was enough experience to question them. The hard part isn’t identifying bad habits in other people’s code. The hard part is noticing them in your own.
These are four I’ve seen repeatedly, in different teams and different languages. I’ve held some of them myself.
Treating warnings as noise
The compiler is trying to tell you something. Warnings aren’t decoration—they’re the compiler’s best guess that something is probably wrong, or will be wrong under conditions you haven’t tested yet. Ignoring them is a choice to accumulate technical debt one warning at a time.
The habit forms because warnings don’t break the build. They’re easy to tune out, especially when there are fifty of them and they’ve been there for months. But warnings that have been present long enough stop registering at all. They become background. And somewhere in that background is the warning that was actually signalling a real problem that will surface at runtime, in production, when it’s most expensive to fix.
Treat a clean compiler output as a basic standard. It’s achievable, and it matters.
Declaring done at compile time
“It compiles” is a starting condition, not a finishing one. Code that compiles hasn’t been tested. It hasn’t passed unit tests. It hasn’t been reviewed. It hasn’t been run against realistic data. The gap between “compiles” and “works correctly” is where most of the interesting bugs live.
This habit is partly linguistic—“I’m done” is a satisfying thing to say—and partly cultural, in teams where pressure to declare completion is higher than pressure to be correct. The discipline is insisting on a shared definition of done that actually specifies what done means: tests pass, edge cases are handled, the change is reviewed.
Never modifying working code
There’s a version of this that’s reasonable: don’t change code you don’t understand, in a system you don’t own, without good reason. That’s caution. But caution curdles into a different habit when it becomes a rule that running code is untouchable.
Code degrades. Not in the sense that bits flip spontaneously, but in the sense that every change made around a piece of code makes it slightly less coherent, slightly less aligned with the current structure of the system. Code that was correct when written can become an obstacle as the system evolves. Leaving it untouched isn’t preservation—it’s letting rust accumulate.
Refactoring isn’t recklessness. It’s maintenance. The fear of touching working code is, at its root, a testing problem: if you’re afraid to change code, it usually means you have no reliable way to verify it still works after the change.
Assuming new features require new code
This one is subtle. The instinct when asked to add a feature is to write more code. But experienced engineers often find that the right solution involves less code than the naive approach—or different code, rearranged so that the new capability emerges from the existing structure rather than being bolted on top.
This isn’t always possible. Sometimes you genuinely need new code. But the question is worth asking before you start writing: is there a way to deliver this by rethinking what’s already here? A re-engineering effort that delivers business value without adding complexity to the system is usually the better long-term trade.
None of these habits are hard to identify once you’re looking for them. The difficulty is developing the instinct to look. That instinct comes from caring about the craft beyond what the sprint board demands—and from working with people who hold you to a higher standard than “it compiles.”