DEV Community

Dvir Segal
Dvir Segal

Posted on • Originally published at dvirsegal.Medium on

The Joy of Negative Code Lines

There is a common bias in our industry: the instinct to add.

As developers, when we face a problem, our default reaction is to write a new function or add a new library. It feels like progress. But while code drives value, it also carries a continuous maintenance cost.

Every line you write is a line that must be read by teammates, covered by tests, and secured against vulnerabilities. The most efficient system is the one that delivers maximum value with minimum code.

The “Just In Case” Hoarder

It is easy to spot the “Fear of Deletion” in a Code Review.

You see a method that isn’t referenced anymore, but it’s still there. Or maybe it’s commented out. The mindset is: “Better keep it. You know, just in case we need to revert.”

This turns your codebase into a digital junk drawer.

“Zombie Code” — logic that is dead but still present, adds noise. It pollutes “Find Usages” results and confuses the next engineer trying to understand the domain. They waste time analyzing functions that provide no value to the user.

How to verify usages

The hardest part of deleting code is the fear of breaking something hidden. “Find All References” isn’t enough. Here is how to verify usage safely:

  1. The Static Check: Start with the IDE. If a method is private, the compiler is usually right. If it’s public, be suspicious. Tools like NDepend or SonarQube can often spot "dead branches" that visual inspection misses.
  2. The External Audit: Check the edges. Are frontend analytics still firing events for that feature? Are HTTP logs showing hits on the old API endpoint? Are cloud metrics showing activity on that specific storage? If the edges are silent, the core is likely dead.
  3. The Database Audit: Sometimes the code looks valid, but the data is stale. Check your database statistics. If a table hasn’t had an INSERT or UPDATE in two years, the code managing that table is likely ghost-logic.
  4. The “Scream” Test (Use Metrics): If you are unsure about a specific internal method, emit a custom metric: Metrics.Increment("deprecated_feature_x_usage"). If the counter stays at zero for a full business cycle, you have your answer.

Data beats intuition. If you can prove nobody calls it, you can delete it without sweating.

The AI Assistant (Your Cleanup Partner)

We are in the age of LLMs, and ignoring them would be a mistake. While you should never let an AI autonomously delete code in production, it is an incredible force multiplier for the verification and cleanup phases.

  • The “Explain This” Test: Paste suspicious legacy code into an AI and ask: “Identify potential side effects if this were removed.” It often finds hidden dependencies.
  • The “Safety Net” Tests: Before deleting complex logic, ask the AI to “Write comprehensive unit tests for this function.” Having tests that fail after you delete the code confirms you understand its impact.
  • Generate the “Red PR”: Once you identify dead code, ask the AI to do the grunt work: “Refactor this file to remove the unused LegacyProcessor class and all references.”

Remember: AI is a tool for acceleration, not abdication. You are still the one clicking “Merge.”

The Sunset Strategy (Rollout Plan)

Verifying is one thing; deleting is another. You need a safety net.

  • Phase 1: The Warning: Don’t rely on quiet runtime logs. Mark code @Obsolete to warn other developers in their IDE, and clearly communicate removal dates in your changelog and team channels.
  • Phase 2: The “Soft Delete” (Feature Flag): Wrap the entry point in a Feature Flag and turn it off. This is your instant rollback mechanism if a critical user complains.
  • Phase 3: The Observation: Keep the flag off for a safe period (e.g., 2 sprints). Ensure you have alerting on that disabled path. Silence is golden.
  • Phase 4: The Code Delete Once the observation period has passed with zero alerts and no customer complaints, it is safe to delete the code, the flag, and the tests.
  • Phase 5: The Data Cleanup: Deleting code often leaves behind orphaned data. Wait a few extra sprints to be absolutely sure, then archive the data to cold storage before finally dropping unused tables or columns. Your schema should reflect reality, not history.

The Bottom Line

There is a unique satisfaction in opening a Pull Request where the Lines Removed count is higher than the Lines Added  — it means you have simplified the mental model without sacrificing functionality. If code isn’t running in production, it is just noise; trust your git history, verify with data, and hit delete.

Top comments (0)