DEV Community

Vini Coppi
Vini Coppi

Posted on

Legacy code: How bad are comments (really)?

You have probably heard the commandment: "good code doesn't need comments." The idea, which has become almost a dogma, is that the code should be so clear, expressive, and self-explanatory that any comment would be redundant. And, in an ideal world, this statement would be absolutely correct. But in reality, as is almost always the case in software engineering, it is not that simple to achieve. For a team that manages, evolves, and operates software in production, the journey to completely eliminate the practice of commenting the source code is long and arduous. It is not just about writing cleaner code, but about a profound evolution in processes, software architecture, and team culture. Reaching a state where comments are genuinely unnecessary requires an effort and time that we often underestimate.

There was a time when commenting code was not only acceptable but actively encouraged. Before the popularization of agile methodologies and the focus on expressive code, development processes were more linear (like the Waterfall model). The documentation was a separate and dense artifact, and the comments in the code served as an instruction manual right at the source.

Imagine a piece of code in COBOL or Fortran from the 1980s. It was common to find blocks of comments at the top of each function, explaining what it did, what parameters it received, and what it returned, as the language itself did not offer the same expressiveness that we have today.

*--------------------------------------------------*
* SUBROUTINE: CALCULATE_PROGRESSIVE_DISCOUNT       *
* AUTHOR: J. SILVA                                 *
* DATE: 06/15/1985                                 *
*--------------------------------------------------*
* OBJECTIVE:                                       *
* APPLY DISCOUNT TO THE TOTAL ORDER VALUE BASED    *
* ON THE QUANTITY OF ITEMS PURCHASED.              *
*                                                  *
* PARAMETERS:                                      *
* - P_TOTAL_VALUE (NUMERIC)                        *
* - P_ITEM_QTY (INTEGER)                           *
*                                                  *
* RETURN:                                          *
* - P_FINAL_VALUE (NUMERIC)                        *
*--------------------------------------------------*
Enter fullscreen mode Exit fullscreen mode

This was the standard. The call to this subroutine might be something like CALL "CALCDESC" USING ORDER-VALUE, ORDER-ITEMS GIVING ORDER-FINAL. Without the comment block, the rule of applying progressive discounts from CALCDESC would be obscure. Comments were the main way to transfer knowledge in an era with radically different tools and culture.

With the rise of agile methodologies, the philosophy of software development underwent a transformation. The focus shifted from extensive documentation to functional software, and collaboration and responsiveness to change became paramount. In this new context, a strong school of thought emerged that advocated for the clarity of the code as the main form of documentation. The idea that comments represent a "code smell" — a symptom that the code has failed to communicate its intention — gained traction. The community began to value names of variables, functions, and classes that described their purpose unequivocally. The prevailing view became that if the code needs a comment to be understood, it probably needs to be rewritten. This interpretation, sometimes rigid, left little room for the exceptions and nuances of the real world.

At this point, the discussion reaches a terrain known to all experienced developers: the difference between the "what" and the "why." For many, it is not new that a good comment explains the reason behind a design decision, and not what the code is literally doing. However, in the incessant search for "pure" code, this principle can be forgotten. The code, however expressive, is an artifact that responds to business constraints, technical limitations, time commitments, among other things. It rarely manages to tell the whole story.

Consider this example:

function processPayment(paymentData) {

    if (!isValid(paymentData)) {
        throw new Error("Invalid payment payload.");
    }

    // TODO: Remove this forced pause when the billing API is optimized (TICKET-123)
    // The external billing API has a very aggressive rate limit and returns a 503 error
    // if we receive more than 5 requests per second. A 300ms pause here
    // avoids hitting the limit in batch processing scenarios.
    // There is already a plan to migrate to an API with queues (SQS).
    sleep(300);

    const result = billingAPI.charge(paymentData);
    return result;
}
Enter fullscreen mode Exit fullscreen mode

The code clearly shows a 300ms pause. The comment explains why this pause, which seems inefficient, is crucial for the stability of the system. It transforms what would be "bad code" into a conscious and documented engineering decision, protecting the system from premature optimizations, even if well-intentioned.

Even with this distinction between comments that explain "what" and those that explain "why," some people will still argue that even the context comment in the example above is a "code smell." For them, the code and the ecosystem around it should be sufficient to provide all the necessary context. Reaching this level of clarity is the ultimate goal, but, as we will see, the road is long. Some of the techniques that assist this process are BDD/TDD (Behavior-Driven-Development/Test-Driven-Development), Clean architecture (Use Cases as filenames, for example ProcessPayment.ts, facilitate contextualization), Docs as Code (Markdown documentation files together with the source code), significant commit messages (feat(auth): add Google login to close TICKET-451), among others. However, the adoption of these practices is far from trivial, especially in teams dealing with legacy software. Introducing BDD into a codebase with low test coverage is a monumental task. Refactoring a monolithic system to a clean architecture can be a project of months or years, often without a clear return on investment for the business in the short term. Implementing "Docs as Code" or standardizing commits requires a cultural change, discipline, and the consent of the entire team, something difficult to achieve under the pressure of constant deliveries. For legacy systems, the cost and risk associated with these changes often outweigh the perceived benefits, making incremental improvement and the strategic use of comments a much more pragmatic approach.

It is precisely in the terrain of legacy code that the debate about comments becomes more pragmatic and less philosophical. For systems that have been in production for years, that support critical operations, and whose original development is a distant memory, an aversion to comments is an unsustainable luxury. In these environments, a well-placed comment is not a "code smell"; it is a strategic risk management tool. The reality of the teams that maintain these systems is dictated by limited budgets, tight deadlines, and a justified fear of breaking what is working. The idea of a large-scale refactoring to "clean up" the code is often unfeasible. In these cases, using comments to map complex areas, explain workarounds, and document inexplicable "magic" is the most responsible approach. It is a way to transfer tacit knowledge and protect the business with an unbeatable cost-benefit ratio. To ignore this reality in the name of an ideal of purity is to neglect the engineer's fiduciary duty to the stability of the software.

The discussion about comments in code needs to evolve from a binary debate of "right vs. wrong" to a conversation about process maturity, pragmatism, and context. A dogmatic aversion to comments is often a sign of an immature view of software engineering, which ignores the complex realities of long-term development and maintenance. The real issue does not lie in blindly following the rules of "clean code," but in understanding when and why to break them. A well-written comment is not an admission of failure; it is an act of empathy towards future developers (including yourself). It is an investment in the sustainability of the project and the sanity of the team. Instead of demonizing comments, we should see them for what they really are: a powerful tool in an engineer's arsenal. In the end, the goal should not be to create code without comments, but rather a robust, sustainable, and, above all, understandable software. And, to get there, a good comment can be your best ally.

Do you agree with me? Please leave a comment and let's help others evolve legacy code.

Thank you for your time, have a great day!

Top comments (4)

Collapse
 
xwero profile image
david duymelinck • Edited

good code doesn't need comments.

People that say that also say stuff like work hard play harder. It is more an ego statement than a well thought out idea.

It is an and-and scenario. Write understandable code, add comments where needed, write tests and document the ideas behind the code.

In the case of todo comments I picked up the habit to add the date the comment is added. This allows me to sort them and see when a todo is beyond an expiration data.
Old todos are like a backlog issue that never gets picked up. It becomes just noise in the system.

Collapse
 
vinicoppi_dev profile image
Vini Coppi

I like your idea of adding dates to TODOs; many times, I’ve spent a few minutes checking git blame/history to find the author and the context behind the comment, only to realize it wasn’t even relevant anymore.

Keeping more robust documentation for tech debt is really good, but sometimes you just want to postpone a small thing you’ll address in another commit or pull request. In that case, I still find a TODO comment useful.

Maybe the reason for TODO leftovers is creating one for a task you won’t do yourself? Many times, I’ve felt that was the case: the comment author thought of an improvement that was never prioritized (sometimes not even documented).

Collapse
 
harroldjr profile image
James Harrold

I would say that commenting code requires the nuance you're describing. I personally keep comments to a minimum in closed source projects with a tight knit team. But even there, if I am struggling with implementing something and I have to do some weird things that don't make sense, I will write a comment and in that kind of scenario it's a must. Or a year down the line someone else or even I will look at the code again and think "why did I do it this way?". Open source projects, commenting the documentation is pretty necessary because you don't know who is going to be looking at it.

Like most things in life, the extremes are too brittle and somewhere in the middle is best.

Collapse
 
syxaxis profile image
George Johnson • Edited

"good code doesn't need comments."

Like most generalisation, it makes far too many assumptions. It assumes the code is neither too verbose or too concise. It assumes the reader is on the same "wavelength" and that they have the experience in the language and the business procedures, not just coding in general.

Personally I spend time on the primary summary at the top of the library as this ensures I have you in the mindset to think about this entire structure. Then I'll state at function what it does with a single line description, and finally where something may have had to be "hacked" to make it make work I clearly point out why I did such a thing as you might come along a year later and know a better way.

To me documentation is to explain WHY not WHAT. I can see what you did 'cos it's right there in 'black and white', I just don't know what was going through your head when you coded that "kludge" so if you can tell me that then I know the direction you were aiming for! ha ha!!

I'm currently pulling apart legacy code that's about 18 years old and the comments about WHY are so valuable as I know I can cut through the BS, then I'm not wasting time on features I know are no longer used and I can get to to the meat.