DEV Community

ThankGod Chibugwum Obobo
ThankGod Chibugwum Obobo

Posted on • Originally published at actocodes.hashnode.dev

Self-Documenting Code vs. Comments: Lessons from Maintaining Large-Scale Codebases

In the world of clean code, there is a recurring debate: should code be so clear that it needs no explanation, or is documentation a mandatory duty?

During my time maintaining codebases like Beatify, I’ve learned that the answer isn't "one or the other." Instead, it is about understanding the distinct roles they play. This article explores how to balance self-documenting code with strategic commenting to build truly maintainable systems.

1. The Core Philosophy: "Code Never Lies, Comments Often Do"

The primary danger of comments is obsolescence. When code is refactored, developers often forget to update the associated comments. Over time, these comments "rot," becoming misleading and dangerous.

Self-documenting code avoids this by making the logic the source of truth. If the logic changes, the "documentation" (the code itself) must change by necessity.

Best Practices for Self-Documenting Code

To reach a state where your code is truly readable, focus on these three pillars:

A. Intent-Revealing Naming

Avoid generic abbreviations. Names should convey the "what" and "how" without needing a side-note.

  • Bad: const d = 86400; // seconds in a day
  • Good: const SECONDS_IN_A_DAY = 86400;

B. Encapsulate Conditionals

Complex logic inside an if statement is a major source of cognitive load. Move the logic into a named variable or function.

  • Bad:

    if (user.age > 18 && user.hasSubscription && !user.isBanned) { ... }
    
  • Good:

    const canAccessPremiumContent = user.isAdult && user.hasActivePlan && !user.isAccountFlagged;
    if (canAccessPremiumContent) { ... }
    

C. Leverage the Type System (TypeScript & Dart)

Types are the most powerful form of living documentation. Use Enums and Interfaces instead of "Magic Strings."

  • In Dart: Instead of passing a String for status, use an enum OrderStatus { pending, shipped, delivered }. This tells the next developer exactly what the valid options are.

2. When Comments are the Superior Tool

Self-documenting code tells you what the code is doing. It cannot, however, tell you why certain decisions were made outside the editor.

The "Intent Comment"

Use comments only when the code cannot explain the context. This is essential for:

  • Workarounds: // Using a legacy loop here because the .map() polyfill fails in IE11.
  • Business Logic Why: // We trigger this sync twice to ensure the third-party API acknowledges the handshake.
  • Warnings: // CRITICAL: Do not change the order of these calls; it will cause a deadlock in the database.

3. Comparison: Pros and Cons

Approach Pros Cons
Self-Documenting Always in sync with logic.
Reduces visual noise.
Forces better naming.
Cannot explain external "Why".
Can result in very long variable names.
Comments Explains non-obvious business rules.
Provides IDE tooltips (e.g JSDoc/DartDoc).
High maintenance cost.
Prone to "rotting".
Often hides "smelly" code.

4. The Maintenance Strategy

Drawing from long-term project maintenance, I follow this hierarchy of documentation:

  1. Level 1 (The Logic): Write clean, modular code. If you need a comment to explain "Step 1, Step 2," your function is too big, split it.
  2. Level 2 (The Types): Use TypeScript or Dart types to define the "shape" of your data.
  3. Level 3 (The Doc-String): Use JSDoc or DartDoc for public APIs to assist other developers via IDE tooltips.
  4. Level 4 (The Why-Comment): Add a comment only if a developer would look at the clean code and still ask, "Why on earth did we do it this way?"

Conclusion

The goal of a senior engineer isn't to write "no comments," but to write no redundant comments. Every line of text in your codebase should add value. If your code is clean, your comments can focus on the high-level strategy rather than explaining the low-level syntax.

Top comments (0)