Programmer's bad habits II

Some habits are obvious failures. You notice them immediately, fix them, and move on. The more dangerous ones are the habits that feel professional—disciplined, even—while slowly making the codebase harder to work with. This is the second post in a series on those habits. The first covered warnings, premature declaration of doneness, and the fear of touching existing code. This one goes further.

Carrying dead code

We all do it. You comment out a block during debugging, intending to remove it later. Later never comes. Six months on, the code is still there, and nobody—including the author—can explain what it was for or whether it’s safe to delete.

Dead code is noise. It makes the signal harder to find. Worse, it communicates uncertainty: if this code isn’t needed, why is it still here? Is there something the author knew that they didn’t document? Version control exists precisely so you can delete code without fear of losing it. Use it.

Refusing to work with others’ code

There’s a recognisable pattern: a developer joins a project, reads the existing codebase, decides it’s irredeemably bad, and proposes a rewrite. Sometimes the assessment is correct. Usually, it’s not—it’s just code that looks different from what they would have written.

The instinct to rewrite rather than refactor is understandable. It’s faster to build something familiar from scratch than to understand someone else’s decisions. But that instinct destroys continuity, discards domain knowledge encoded in the existing system, and introduces new bugs in places the old code had already been hardened. Refactoring is the discipline. Rewriting is the shortcut that costs more than it saves.

Over-reliance on basic constructs

A system built entirely of if-else chains and for loops will work. It will also become unmaintainable at a scale that surprises you—sooner than you’d think. Object-oriented design exists to manage complexity through composition and abstraction, not as a theoretical exercise. Polymorphism replaces conditionals. Composition replaces inheritance chains that grew too tall. These aren’t advanced topics; they’re the core tools of the craft.

This habit often traces back to how developers learned to program. Languages and curricula that introduce programming through procedural constructs, without a clear on-ramp to object-oriented thinking, produce developers who reach for loops and conditionals even when a better abstraction is sitting right there.

Dismissing testing

“Testing is for the QA team.” This view is still surprisingly common, and it’s worth being direct: it’s wrong. Unit tests are a design activity. Writing a test before writing code forces you to think about the interface, the contract, and the edge cases before you’re committed to an implementation. TDD isn’t about coverage metrics—it’s about the discipline of specifying behaviour before building it.

Saying you don’t have time for tests is saying you don’t have time to think carefully about the code you’re writing. That time gets spent later, investigating bugs, or explaining to a stakeholder why something that seemed to work stopped working.

Misunderstanding code reviews

A code review isn’t a line-by-line audit conducted by a senior developer while a junior one watches in silence. That format is slow, demoralising, and misses the forest for the trees. Effective reviews focus on structure and intent—are the abstractions right? Does this change make the system easier or harder to modify? Automated tools handle the surface-level issues: formatting, obvious errors, style consistency. Reviews are for the things tools can’t catch.

The review is also a conversation, not a verdict. When reviewers and authors refactor together, both learn something. When reviewers just leave comments and walk away, the author either agrees or argues in isolation.

Conflating compilation with a real build

Your code compiles. That’s the start, not the finish. A production build involves code signing, obfuscation, environment-specific configuration, targeting multiple platforms, packaging, and deployment steps. These are not details someone else handles. They’re part of the software you’re building.

Developers who’ve never looked at a build script have a very incomplete picture of how their software gets from a repository to a running system. Continuous integration tools—even at a basic level—make this visible. The build is a first-class artefact. Treat it accordingly.


These habits tend to cluster. Teams that carry dead code also tend to avoid touching existing code. Teams that skip testing also tend to have murky build processes. The underlying cause is usually the same: a culture that values output over craft, and features over maintainability. Agile practices help—but only if the underlying orientation changes, not just the ceremony.