Introduction
Imagine building a feature from scratch, deploying it, and then, months or years later, tearing it down and rebuilding it from the ground up. It sounds like wasted effort — but for the developer behind the story published on Habr, it was an eye-opening journey. The article, titled "Дубль один, дубль два: как я делала одну фичу дважды" (Double One, Double Two: How I Made One Feature Twice), chronicles the experience of implementing the same functionality on two separate occasions, with vastly different approaches and outcomes. This isn’t just a tale of code; it’s a case study in how software design decisions age, how context changes requirements, and why rewriting isn’t always a sin.
The Habr piece resonates with anyone who has ever looked at their old code and cringed. It offers a rare, honest look at technical debt, evolving engineering practices, and the personal growth that comes from learning by doing. In this article, we’ll unpack the key insights from that story, examine why features get built twice, and extract practical lessons for teams and individual developers.
The First Implementation: Speed Over Structure
The author’s first attempt at the feature took place under intense deadlines. The primary goal was to ship something functional as quickly as possible. As is common in early-stage development, corners were cut: the code relied on hardcoded values, lacked proper error handling, and mixed business logic with UI rendering in a monolithic component. The database schema was designed ad-hoc, with no migration strategy. Tests were minimal — just a handful of integration checks that covered the happy path.
While the feature worked in production, it quickly became a maintenance burden. Every new requirement required touching multiple files. Debugging was a nightmare because the code didn’t distinguish between data-fetching, state management, and presentation. The author admits that the first version was “embarrassing” to revisit — a sentiment many developers know well. The piece highlights a universal truth: when you optimize for delivery speed without considering future evolution, you inevitably accumulate debt.
Why Rewrite? The Trigger for a Second Take
What prompted the rewrite? Not a bug or a user complaint, but a fundamental change in the underlying platform. The company migrated to a new backend architecture, and the old feature no longer integrated cleanly. Additionally, the team had adopted modern practices (TypeScript, proper state management libraries, automated CI/CD) that the original code never leveraged. Rather than patching a decaying system, the author decided to rebuild the feature from scratch using current best practices.
This decision mirrors a pattern seen across the industry: rewrites are often triggered by architectural shifts, not by a desire to polish old code. The article emphasizes that the second version wasn’t about adding new bells and whistles — it was about achieving the same functionality with a better foundation. The author took the opportunity to simplify the data model, add comprehensive unit tests, and separate responsibilities into clean layers (API client, business logic, UI components). The result was a codebase that was 40% smaller yet handled edge cases that the original never addressed.
Key Differences Between the Two Versions
The article provides a detailed comparison, which we can summarize as a lesson in software maturation:
| Aspect | First Implementation | Second Implementation |
|---|---|---|
| Architecture | Monolithic component with mixed logic | Separated layers: data access, business logic, presentation |
| Error handling | Minimal; most exceptions crashed the UI | Graceful fallbacks, user-friendly error messages, retry logic |
| Testing | 2–3 integration tests covering only success | Unit tests for every business rule, integration tests for API, E2E for critical flows |
| Data management | No caching, repeated API calls | Built-in caching layer with stale-while-revalidate strategy |
| Performance | Loading time >2 seconds on 3G | Under 400ms with skeleton screens |
| Code quality | Inconsistent naming, magic numbers | Linted, typed, documented with JSDoc |
The table alone illustrates why the rewrite was worthwhile: the new version wasn’t just “cleaner” — it was objectively more robust and user-friendly.
Lessons from the Double Build
Reading between the lines, three major takeaways emerge:
-
Context matters more than skill. The first version wasn’t the work of a junior developer; it was the result of a specific set of constraints (time, platform maturity, team size). The second version benefited from a richer context — the author knew exactly which pitfalls to avoid. This reinforces the value of experience: you can’t design a perfect system until you’ve seen it fail.
-
Rewriting isn’t always bad — if you do it surgically. The author didn’t rewrite the entire application; only the problematic feature. The rest of the codebase remained untouched. This targeted approach is often safer than a full rewrite (as Joel Spolsky famously warned against). The Habr article demonstrates that focusing on a high-impact, self-contained module can yield high rewards without the risks of a ground-up rewrite.
-
Document your decisions for future you. The author mentions that during the second implementation, they referenced their own earlier notes (and frustrations) to avoid repeating mistakes. Keeping a simple log of why certain choices were made (or why they weren’t) can be invaluable when a feature comes up for rework.
The Bigger Picture: Software Is a Living Thing
The Habr story is a microcosm of how software evolves. Features that seem stable today may become tomorrow’s bottlenecks. The author’s willingness to revisit her own work, acknowledge its flaws, and rebuild from a place of greater understanding is a hallmark of a mature engineer. The article doesn’t preach “never rewrite” or “always rewrite” — it simply shows that sometimes the best way forward is to start over, armed with hindsight.
For teams reading this, the lesson is clear: create a culture where legacy code can be challenged. Encourage developers to refactor or even rewrite when the payoff is tangible — not for the sake of shiny new tech, but for reliability, performance, and maintainability. The Habr piece is a powerful real-world example that doing something twice can be twice as enlightening.
Conclusion
Building the same feature twice sounds inefficient, but as the Habr article shows, it can be one of the most educational experiences in a developer’s career. The first version teaches you what works under pressure; the second teaches you what works in the long run. The author’s journey from “working but fragile” to “clean and resilient” is a roadmap for anyone facing the temptation to patch a broken system. Next time you look at an old feature that makes you wince, consider: maybe it’s time for take two.
This article is based on the original Habr piece. The views expressed here are a synthesis of the author’s experience.
Comments