Good Code Really Is Its Own Best Documentation (But Let’s Be Real About It)

Steve McConnell nailed it back in Code Complete:

“Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer.”

Everyone loves quoting the first half. Code review debates, Twitter threads, team Slack wars—someone inevitably drops “good code is its own best documentation” like it ends the argument. But almost nobody remembers (or wants to remember) the second half: the part where you’re supposed to actually refactor instead of slapping a comment on top.

From personal experience: I’ve lost count of how many times I’ve weaponized that quote when defending my own code (“see? no comment needed!”), and how many times I’ve cursed under my breath while trying to decipher someone else’s legendary, comment-free masterpiece.

The harsh truth most of us don’t want to admit: very few programmers (myself included) are consistently skilled enough to make code so self-explanatory that comments become truly redundant. We’ve all inherited code from “rockstar” engineers—the ones whose exploits are legendary in school projects, open-source repos, or company folklore. You finally get handed their codebase and… oof. Thousands of lines, zero comments. Magic public library calls with zero docs, or docs so vague they’re basically “it works, trust me.” You step on the same undocumented landmine three times, swear quietly, and the next time someone gushes about that dev’s brilliance, you just fake a smile and think: “Sure, buddy. Sure.”

The real debate isn’t “can the best code skip comments entirely?” (maybe a handful of 10x wizards can). The practical question for teams is: should we ever encourage the idea that “comments are optional”?

If you broadcast “comments can be omitted if the code is good enough,” what most people hear is “yay, I never have to write comments again.” Especially dangerous for juniors fresh out of college who’ve never even heard the word “documentation” outside a syllabus footnote. Tell them comments are skippable, and their code will be nowhere near self-documenting—it’ll just be undocumented garbage.

In a company setting, comments aren’t just nice-to-have; they’re a team survival mechanism. They affect every collaborator today, every future maintainer tomorrow, and yes, your own professional reputation when someone else has to read your six-month-old mess. So my personal stance hasn’t budged: comments are mandatory. The more thorough the better. Overdo it? Fine—better safe than sorry.

For solo work or personal projects, the “good code = best docs” mantra sounds great… until you open your own code from half a year ago with zero comments and think: “Who the hell wrote this garbage?!”

Moral of the story: Aim for crystal-clear code that minimizes the need for comments. But don’t use that ideal as an excuse to skip them. Write the damn comments. Your future self (and the poor soul who inherits your repo) will thank you.

Law of Conservation of Complexity: Complexity Merely Shifts, It Never Vanishes

Complexity merely shifts; it never vanishes.

In the real world, business complexity is conserved. You can’t eliminate it just by slapping on clever design patterns in your code (at the software level, algorithms and data structures tackle business problems head-on—design patterns just make code reusable and ops smoother).

What actually cuts down on software-side business complexity? Sharp management and streamlined workflows.

Software doesn’t fix management woes; it only amps up process efficiency. It’s a tool, after all—one that demands human operators to wield it. Folks who try to code their way out of management messes are inverting priorities and being downright lazy. The right path: Nail down your management policies and processes first, then build software to turbocharge their execution.

What are Modules, Components, and Services? What Are Their Differences?

Components are also known as building blocks. But what exactly is a component? A component is a software unit that can be independently replaced and upgraded. It has the following characteristics:

  1. It performs a specific function or provides certain services.
  2. It cannot operate independently and must function as part of a system.
  3. It is a physical concept, not a logical one.
  4. It can be maintained, upgraded, or replaced independently without affecting the entire system.

A component is a physically independent entity that can be maintained, upgraded, or replaced on its own. The purpose of creating a component diagram is to perform component-based design for the system, thinking about the physical division of the system, identifying which existing components can be reused, and determining which parts can be turned into components for reuse in future projects.

Question 1: When designing software, we often mention the term “module.” Is a module the same as a component?

Not necessarily. Everyone has different standards for what constitutes a “module.” Sometimes modules are divided based on business logic, and other times they are divided from a technical perspective. Modules are simply a way to divide software into parts for ease of explanation. You can refer to the characteristics of a component listed above to determine whether a “module” qualifies as a component.

Question 2: Software often uses layered design. Is each layer a component?

In most cases, each layer in a layered design is just a logical division and is not physically represented as separate files. In such cases, the layers are not components. However, the actual design may vary, and you can refer to the characteristics of a component to make a judgment.

Question 3: How do we distinguish between a “service” and a “component”?

A “component” refers to a software unit that will be used by other applications beyond the author’s control, but these applications cannot modify the component. In other words, an application using a component cannot alter the component’s source code, but it can extend the component in a predefined way to change its behavior.

Services and components share some similarities: both are used by external applications. In my view, the biggest difference between them lies in the fact that components are libraries used locally, such as JAR files, assemblies, DLLs, or source code imports. Services, on the other hand, are components external to the process, accessed by applications through mechanisms such as synchronous or asynchronous inter-process communication or remote interface calls (e.g., web services, messaging systems, RPC, or sockets).

Services can also call other services since a service is, in itself, an application.

You could map each service to a runtime process, though this is only an approximation. A service could consist of multiple processes, such as the main service application process and a database process used exclusively by that service.

Services can be deployed independently. If an application system is composed of multiple libraries within a single process, any modification to one component requires redeployment of the entire application. However, if the system is divided into multiple services, only the modified service needs to be redeployed—unless changes were made to its exposed interface.

Another outcome of implementing components as services is the availability of more explicit component interfaces.

Compared to in-process calls, remote service calls are more expensive in terms of performance.

References:

  • “Microservices” by Martin Fowler
  • “Inversion of Control Containers and the Dependency Injection pattern” by Martin Fowler
  • “Fireball: UML and the War for Requirements Analysis”