JustHandled Labs
// preview-first repository cleanup

How to remove commented-out code without deleting useful comments

Dead code hidden inside comments makes a repository harder to read. A blind deletion pass can be worse: it can erase the license, the reason behind an unusual decision, a formatter directive, or the test someone disabled on purpose.

The short version

Start from a clean, version-controlled branch. Inventory candidate comment blocks, classify each one, and preserve legal text, documentation, TODOs with real context, tool directives, disabled tests, and comments that explain why. Preview the exact diff before writing, keep the cleanup separate from functional changes, then run the repository's normal verification. If a removed block matters later, Git history—not commented-out source—is the recovery path.

What this cleanup covers

Version history is a better archive than the active file

Commented-out code usually begins as a temporary safety blanket: an old implementation, a debug call, a feature experiment, or a branch the author may want later. It then becomes ambiguous. A future reader cannot tell whether the block is deliberately preserved, accidentally abandoned, or still required.

GitHub's explanation of version control notes that earlier versions can be recovered and that contributors can compare changes across the repository's history. The Pro Git guide describes the same core function: record changes so a specific version can be recalled later. In a healthy Git repository, obsolete code does not need to remain inside a comment to be recoverable.

That does not mean every code-looking comment is dead. Google's code-review guidance says useful comments often explain why code exists rather than narrating what it does. Its documentation guidance distinguishes inline reasoning, API contracts, and module documentation. Cleanup should remove abandoned implementation while leaving information the code cannot express.

Delete, keep, or review: classify before changing

Comment typeDefault decisionReason
Obsolete executable codeDeleteIt adds a second, non-running implementation and Git already preserves the old version.
Temporary debug callsDelete or make explicitRemove abandoned calls; if the behavior is still needed, implement a real debug flag or supported logging path.
Why or tradeoff explanationKeepThe reasoning may not be derivable from syntax, especially around compatibility, performance, or a rejected alternative.
API, docstring, or module contractKeep and verifyDocumentation describes purpose and behavior; update it if the implementation changed.
TODO, FIXME, or action itemReviewKeep only when the item is still real and specific enough to act on; otherwise resolve, file, or remove it.
License or copyright headerKeepIt can carry legal obligations and should never be removed by a generic cleanup pass.
Formatter, linter, compiler, or coverage directiveKeep and verifyComments such as formatter-off regions can change tool behavior even though they are not executed by the application.
Disabled test or fixtureReview separatelyIt may represent dead work, a known defect, or a deliberately retained example. Do not silently erase that decision.
Generated, vendored, or example codeExclude by defaultThe source of truth may live elsewhere and a local edit may be overwritten or distort documentation.

A real style guide can settle ambiguous categories. For example, Google's Java guide gives TODOs a defined format with an issue or specific event, while clang-format recognizes comment directives that deliberately disable formatting. Those are operational comments, not clutter.

A preview-first cleanup workflow

1

Define the cleanup boundary

Name the directories, languages, generated paths, vendored code, fixtures, and documentation examples that are in scope. Exclude dependencies, build output, minified assets, snapshots, and generated files unless their source of truth is also being changed.

2

Prove the repository can recover

Start from a clean working tree on a branch. Record the current commit. If the files are not version controlled, create a separate backup before any write. A recovery plan must exist before the deletion pass, not after a surprising diff.

git status --short
git rev-parse --short HEAD
git switch -c chore/comment-cleanup
3

Build a candidate inventory

Search for comment blocks that contain language signals such as assignments, calls, control-flow keywords, braces, semicolons, or closing tags. Also inventory preservation signals: TODO, FIXME, license, copyright, formatter, lint, coverage, noinspection, generated, and test-skip markers.

The result is a review queue, not a deletion list. A code-looking example inside documentation and an old function commented out in production can look identical to a regular expression.

4

Classify with surrounding context

Read enough lines around every candidate to identify its role. Compare nearby names with the live implementation. Check whether the comment explains a non-obvious constraint, serves as an API example, changes a tool, or records an unresolved decision. When the meaning is unclear, mark it for human review instead of guessing.

5

Preview one narrow diff

Generate a proposed removal set and show file, line range, classification, reason, and the exact before-and-after diff. Keep comment cleanup separate from formatting, renaming, and feature work. Google's review guidance warns that mixing broad style changes with functional work makes a change harder to understand, merge, and roll back.

6

Apply only confirmed deletions

Write after a reviewer has accepted the preview. Preserve line endings and encoding. Do not modify excluded files, do not rewrite the rest of the file, and do not convert an uncertain comment into a confident deletion. A backup copy is useful for non-Git work and adds a second recovery path for a scripted batch.

7

Verify behavior and documentation

Review the final diff, run tests, lint, formatting checks, type checking, and the production build where applicable. Search again for candidates and preservation markers. If an example, docstring, generated reference, or disabled test changed, inspect it as its own artifact.

8

Commit the cleanup as one reversible change

Use a focused commit message that describes the scope and the preservation rules. Another reviewer should be able to see that the change deletes dead comments without changing behavior. Keep the commit small enough to revert without taking unrelated work with it.

The syntax traps a blanket regex misses

  • JavaScript and TypeScript: URLs, regular expressions, JSX, template strings, JSDoc, source-map directives, ESLint controls, and TypeScript suppression comments can all resemble removable text.
  • Python: a triple-quoted string is an expression, not a comment token. Docstrings can define public contracts, doctests can run, and type-checker or coverage pragmas affect tools.
  • Java, Go, and Rust: Javadoc, Go doc comments, Rust doc tests, build or lint controls, generated-code markers, and license headers need separate treatment from abandoned statements.
  • HTML and CSS: template syntax, conditional or build comments, stylelint controls, examples, and code embedded in script or style blocks make file-extension-only detection unreliable.

Google's kernelCTF style guide gives a useful concrete rule: remove unused lines or turn them into an explicit debug mechanism, while retaining comments that explain context. That is a better decision boundary than “delete every comment containing punctuation.”

Evidence that the cleanup was safe

Minimum cleanup record

  • Scope: included and excluded directories, file types, and generated paths.
  • Candidate count: detected, confirmed for deletion, preserved, and sent for review.
  • Preservation rules: legal text, documentation, TODO format, directives, tests, and project-specific markers.
  • Preview: an inspectable diff before the write.
  • Recovery: branch and commit, or the location of explicit backup copies.
  • Verification: commands run, exit results, and any checks that were unavailable.
  • Residual uncertainty: every ambiguous block left unchanged for a human decision.

The goal is not the largest deletion count. It is a smaller, clearer active codebase with the useful intent still attached and enough evidence to reverse the cleanup if the classification was wrong.

Make the repeatable parts easier to review

Use automation to inventory, classify, preview, and record. Keep the final judgment visible.

Common questions

Should commented-out code be deleted?

Usually, yes, when it is truly obsolete source code and the repository preserves history. Classify first: license text, API documentation, TODOs, tool directives, disabled tests, and explanations of intent may still be necessary.

Can I remove commented-out code with a regular expression?

Use a regex to create candidates, not to authorize deletions. It cannot reliably distinguish dead code from prose, documentation syntax, legal notices, examples, or language-specific directives.

How do I remove dead code without losing it?

Work from a clean version-controlled branch, preview a narrow diff, commit the cleanup separately, and confirm the removed lines remain recoverable from history. Create explicit backups first when the files are not version controlled.

What checks should run after comment cleanup?

Review the final diff, search again, and run the repository's tests, linter, formatter, type checker, and production build where applicable. Inspect affected documentation, examples, generated files, and disabled tests separately.

Primary references

See every proposed deletion before it happens.

Code Comment Cleaner classifies code-like comments, preserves defined comment types, previews the exact removal set, and writes backup copies before a confirmed cleanup. Review the findings; it does not make every ambiguous decision for you.

Get Code Comment Cleaner on Agensi