In case you were wondering...

Technical Bankruptcy

On recent reflection, I've spent quite a lot of my career building v2 of things.

Do I just so happen to join these projects at just the right time? Or is it just me? Probably a combination. With confirmation bias.

We know all about technical debt (and unhedged call options) but one often overlooked and feared idea is technical bankruptcy.

Technical Debt is all about future risks and development. The objection from managers has always been true and pointed: It works. Now. When we talk about the tech debt in the project, we are usually trying to explain either why a new 'quick feature' is taking longer than it 'should', or why we need some time to go back over something which is already delivered. But none of it affects the customer right now, otherwise, it's called a bug and gets fixed, patched or explained away.

And of course, tech debt was coined as a useful analogy to explain why it's not all bad - debt is good. It gets startups off the ground. It validates ideas, and it releases products which earn money to pay our wages and investors.

So what about a code base which has no future? Tech debt becomes less of an issue. There is no future development to be slower. There's still the 'it might explode', granted, but there's not much point untangling code for future developers if there aren't going to be any. We declare Technical Bankruptcy when the Technical Debt has gotten so big that the effort to fix it outweighs the effort to throw it out and start again.

When not to re-write

If you are a startup or small business, and you haven't yet found that glorious 'product market fit', you probably don't need to refactor. Re-Writing is for medium-to-long-term business planning. Startups, prior to market validation (MVP), don't need solid, future-proof code. It would be nice to have, but not nice enough to justify a re-write. Validate the market first, then write elegant code. (That doesn't mean 'write bad code')

If v2 is going to be built with the same team, management and methodology as v1, you will get the same result, and be re-writing a v3 and v4 in no time at all. So what's going to ACTUALLY be different here? Dial up your scepticism.

The customer still has their demands, the business still has a limited budget, waterfall still doesn't work, nor does skipping planning, not communicating, or buzzodologies.

Don't just change it for the sake of changing it. Docker and Microservices? Scrum or KanBan? Node.js, Go, Rust? Won't make a scrap of difference, the tools weren't the problem. None of the tools is that bad. Plan appropriately and the tools may assist you in the rewrite, but they neither the problem, not the solution.

The Process

The first few steps of a re-write are the same as the (probably missing) first steps of any tech project.

Step 1: Problem Overview

What are the lessons from v1? What worked? What didn't work?

Is there a major modelling problem (usually a lack of formal or documented modelling at all) which made 'should-be-simple' things a nightmare?

Step 2: Solution Modelling

There are many, MANY ways to do this. The methodology for modelling a solution is a story for another day.

At some point in this stage, someone will declare someone or something as 'not agile'.

a) Manifesto for Agile Software Development

b) Agility is all about feedback loops, this is a larger feedback loop from v1. You should have learned something in v1, otherwise, you did waste your time. Trust me, you learned something.

You can, and should, be agile when designing the architecture of the solution, and you can and should be agile when developing the solution.

Step 3: The elephant in the room

v1 exists, is live and provides a revenue stream.

It also has bug reports, customer support and feature requests. And they are weighing the team down.

Writing off the tech debt does not solve any of those problems right now. The future is glorious. The present has its problems.

Managing priorities and risks during this time is an art. This is where the management team can really shine (or not).

Step 4: End to End tests

Chances are if you are stepping out into a major refactor, the existing code was not built with testability in mind, so the tests are going to be a bit of a compromise-on-the-ideal.

This takes the compromise to a new level, treat the application to be re-written as a giant single 'unit' with internal 'implementation details', and write tests, automated or manual, which prove the current functionality.

I've had some luck here in that most of my re-writes have been of APIs, so tests are totally easy and automated. Honestly, I'm not sure how this would work for a tightly coupled UI rewrite. It would likely be a combination of splitting and testing, but things will get messy. Automated UI tests are useful when they are useful, but 99% of the time [citation needed] the UI should look very different at the end of a rewrite.

During this phase, I tend to learn many things I didn't know about v1. The good and the bad. The 'oh yeah, that' missing part of the spec. This alone makes the phase useful.

Bonus credit for writing failing tests, but don't forget that the rewrite is in the pipeline. There's a HUGE temptation here to start fixing bugs, only to come right back around to declaring bankruptcy again and wonder where the month went.

Data Migration plans

You may find that the current database schema doesn't work how you want it to. It was probably designed and incremented many, many feature requirements ago.

This is a difficult thing to balance, you don't want to be too constrained by the current database, but you will also need to migrate from it later on, and you may even need to run both at the same time.

It's too late in this particular context, but it's worth noting the importance of data modelling here. The database will be the hardest thing to fix later. Get it right the first time - even as a startup.

Backwards Compatibility

Are the users other computers, or people? Backwards compatibility matters for both, but a user can automatically deal with a minor change. The other computers won't, they'll just break.

You need a plan at this stage on how to manage that change, and that will largely depend on where your application lives and who the users are. An internal enterprise application may require re-training, this can be very expensive. API clients will need to be released.

You may need a cross-over period where both versions run at the same time.

Write Some Code

You already know how to do this. Just don't take out too much tech debt.

The Tools

Pick your Programming Language(s) and Frameworks etc. as usual, I usually try to avoid using the same stack as v1 to avoid the temptation to copy and paste. On the other hand, that could save you some time. YMMV.

The Rules

Styleguide. A scripted style guide. It's important. Think per-language, not overarching. Go always uses tabs, yaml always uses spaces. Each community and language has a whole set of conventions, use them. Don't use a 'company code style guide' or something silly like that.

Naming things

Obey what the language convention dictates (IThingFactoryFactoryService), but for naming the actual name part, use terms from v1 and the problem and solution diagrams. Maybe you even did a Universe of Discourse step earlier. Not forcing it, just saying.

Tracer Bullets

The Tracer Bullet is a complete end-to-end sub-part of a feature which demonstrates the full stack of the software. It's a useful discussion point, allows the team to communicate on best practice etc and forms the unwritten and un-writable style guide for the remainder of the project. It's language agnostic, especially if you are going with a multi-language micro-services approach. It's the style of the approach, not of the code, and it's very difficult to describe in any way other than actually writing the code.