Understanding Bad Code: Causes, Characteristics, Risks, and Solutions
This article examines why developers produce low‑quality code, describes typical symptoms such as unreadability and excessive nesting, outlines the long‑term costs and risks of bad code, and presents expert‑recommended practices—including code reviews, design documentation, technical debt repayment, and disciplined development processes—to improve code quality.
Martin Fowler once said, "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." This article reflects on that principle and explores why bad code is so common.
1. How Bad Code Is Generated?
Developers often blame urgent requirements for low‑quality code, but the real causes are twofold: a lack of solid theoretical guidance combined with long‑term practice, and insufficient psychological resilience under pressure. Those who write high‑quality code tend to be busier even when not rushing to meet deadlines.
Common reasons for producing bad code include:
Rushing to meet tight project deadlines.
Unawareness of best coding practices.
Carelessness and a desire for shortcuts.
Frequent requirement changes that lead to tangled, bloated code.
Absence of contingency plans for emergencies.
Fundamental issues in requirement management, where managers prioritize speed over quality.
2. What Bad Code Looks Like
Bad code typically ignores future changes and maintenance, resulting in several problems:
Hard to read and understand: Developers spend excessive time deciphering the code before they can modify it.
Example of unreadable code:
system.whenTerminated.onComplete(result => session.close()) implicit val materializer: Materializer = Materializer.matFromSystem(system) val dataPath = args(0) var batchSize = 512 if (args.length > 1) { batchSize = Integer.valueOf(args(1)) } var skip = 0 if (args.length > 2) { skip = Integer.valueOf(args(2)) } val lines = scala.io.Source.fromFile(dataPath) try { import session.profile.api._ } val parallels = Runtime.getRuntime.availableProcessors val source = Source.fromIterator(() => lines.getLines()) val action = source.map(line => { val number = counter.incrementAndGet(); (number, line) }) .drop(skip).mapAsync(parallels)(tuple => Future(decode(tuple._1, tuple._2))) .filter(_.isDefined).map(_.get).grouped(batchSize) .mapAsync(1)(articles => { val max = articles.map(_.line).max; val min = articles.map(_.line).min; println(s"group of articles ${articles.size} [$min, $max]"); Future.sequence(articles.map(a => nonExists(db, a))).map(_.flatten).recover { case err: Exception => println(s"unexpected error $err when check in [$min, $max]") } })Excessive nesting: Deeply nested if‑else blocks and loops make the logic hard to follow.
Example of overly nested code:
anchors foreach { anchor =>
if (anchor.hasAttr("href")) {
val href = anchor.attr("href")
val next = completeUrl(href)
if (next.contains(domain) && !roadMap.contains(url) && !next.contains("/email-protection#")) {
roadMap.add(next)
if (process(next)) {
println(s"processed $next")
Thread.sleep(sleep)
} else {
println(s"skip $next")
}
} else {
println(s"skip $next")
}
}
}Poor naming: Using vague names like X, Y, Z makes the code unintelligible to others.
Example of bad naming:
x = "abdon" y = 314 z = 151 r = f(x).f2(y).f3(z)Unnecessary comments: Over‑commenting can drown important notes, causing readers to skip them.
Example of excessive comments:
// comment class Foo { // run program public void run() { // call foo foo(args); // call abc(args); // check return value if (exists(efg(c))) { foo(x); } else { // discount
dazhe(efg(d)); } } }3. The Dangers of Bad Code
While bad code may work in the short term, it becomes a "time bomb" that leads to:
Significant time and monetary cost to fix.
Difficulty or impossibility of adding new features.
Unpredictable failures.
Negative impact on developer morale.
Potential project abandonment.
4. Expert Recommendations to Solve the Problem
Industry expert @宝玉xp suggests several concrete actions:
Implement code reviews with automated testing before merging to the main branch.
Perform thorough system design and documentation before coding.
Regularly repay technical debt: for legacy projects, apply patches only; for actively maintained projects, refactor incrementally with tests.
Adopt a two‑week sprint model: week one for feature development, week two for bug fixing and technical tasks such as debt repayment and exploring new tech stacks.
Establish and evolve best‑practice guidelines after initial architecture design, using them as a reference for all developers and enforcing them through code reviews.
By following these practices, teams can shift from producing low‑quality, “bad” code to delivering clean, maintainable software.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.