Hacker News new | ask | show | jobs
by what-no-tests 993 days ago
Rewriting is so attractive.

If the code has aged and is suffering from many code smells and anti-patterns, a rewrite becomes even more attractive.

"Why should I spend all this time adding a simple feature to this crappy code??"

But writing code is the easy part.

Architecting a correct solution that meets today's business needs and can be built upon, as well as walking the data, users, and business workflows over to the new system, are the hard part.

I've never seen it go smoothly. I want to see it happen, because I'm an optimist, but so far it's not gone well.

2 comments

Oddly, your username is relevant here. It's a common refrain of mine when I hear about someone undertaking a rewrite that becomes something of an albatross.

The only times I see rewrites succeed (where success is measured by how often your customers notice you breaking stuff) is when there's a comprehensive set of integration tests to write against. That really seems to be the sole determining factor.

I don't know. Tests freeze design so if you make your tests too comprehensive you'll just rebuild the existing system. This might be the correct approach if you are changing platforms/languages but often what you want is a system that is actually fundamentally different from the one you started with.
I would agree if I were considering unit tests, where the implementation details fall into testing scope, but I was deliberate in my word choice.

Integration tests exercise behavior at the boundary of the system under test, where it integrates with other systems, which is ultimately what you want for steering a rewrite.

Thats probably not a bad thing if it's behavior that is frozen. I find the most successful rewrites change as little as possible and iterate gradually while the least successful rewrites try to be a mix bag of everyone's wishlists.
> But writing code is the easy part.

If it's inherently easier to write new code than maintaining old code, then we should do our best to also make it a good choice operationally. Maybe write a new service/module alongside the old thing and deploy that, instead of opting for a full rewrite immediately. What else are we supposed to do, suffer with codebases that decline over time, instead of using more modern tooling and frameworks?

If adding a new module to a dated legacy Spring system would involve messing around for weeks with XML configuration mechanisms, brittle runtimes and mechanisms in the codebase and integrating everything with the existing solution whilst having risks in regards to overall stability, then you might as well bootstrap a new service in Spring Boot/Quarkus/whatever you're comfortable with and deploy it alongside the old thing. (using Java as an example here, because the old Spring projects I've seen got pretty bad sometimes)

With reverse proxies or other gateway solutions, as well as containers and orchestration, deploying and routing traffic to this new service (even just for new endpoints in your existing domain/API) is not a big issue and many other concerns are covered, in addition to this new service being simpler to change and evolve over time, as well as replace altogether.

However, the situation around the data and integrating various services with one another still is difficult - because with microservices your data model would be strewn across multiple separate databases, whereas with multiple services connecting to the same database you'd end up with something that's hard to reason about. Whereas if you call APIs directly, you're also introducing an unreliable network connection into the mix between the services, which has some overhead as well, or a complex message queue/pub-sub solution.

I'm glad that we're far along enough for the former problems to be at least reasonably solved (things like 12 Factor Apps are great), but I don't think that there are all that many solutions for the latter group of issues, unfortunately. Even worse if you do a full rewrite and there's an expectation of the new thing to 1:1 match the logic and features of the old one, then you're setting yourself up for failure, unless the project is simple.

Wow - I just want to express some gratitude for the thoughtful response here.

You've captured the essence -- although we have solutions for when the impulse to rewrite an application occurs, we don't have solutions that don't bring their own problems.

Further, the natural human expectation for the software engineer to be like Chuck Norris, who can Fix Everything Without Changing Anything, needs to be considered and managed.

The opposite - where, as someone else termed it - the new version is based on a grab-bag/wishlist of everyone's hopes and dreams, generally doesn't work out either.

The true path does seem to be:

* setup integration tests that validate WHAT the system does (blackbox), rather than HOW the system works (vs whitebox).

* build the new system modularly, offloading one part of the old system's work onto the new system, one part at a time.

* keep the new and old system synchronized, using eventing/message-queues/etc (this is going to be difficult)

* continue replacing the old parts of the system with their new replacements until the old system no longer exists

* Now you've created the old system again, but the code and architecture are clean and capable of the changes that were not possible before

* Move forward confidently adding new features and capabilities to your new system which does not suffer from the problems of the old system.