Micro-refactors to find your way in a difficult codebase
This guide is for developers facing a codebase that looks like a complete write-off and who don't know where to start.
Introduction
When a codebase looks beyond saving, the temptation is to rewrite it from scratch. Before getting there, though, it's worth doing a few small refactors to find your bearings for the real ones.
❌ Don't make the mistake of starting an architectural refactor in code you can't even read yet.
These micro-refactors could also be described as simply "tidying up". In this post we'll look at the method Kent Beck uses in his book Tidy First? — which we won't cover in full, and which we therefore absolutely recommend!
Guard Clauses
Try to use guards instead of boxing your code into a thousand nested conditions, while still staying under 8 guards.
⚠️ If you start adding too many guards the code becomes hard to digest anyway: at that point it's better to break the function into several parts.
Dead Code
Always remove code that's no longer needed. There's no point keeping it commented out either: it's versioned anyway, or at worst it will be rewritten better as things evolve.
Unreachable code only makes everything more cryptic and, because of reflection, it's sometimes not that obvious to spot!
Normalize Symmetries
Pick the simplest way to achieve a result and convert every algorithm so it uses that exact way of getting there.
The more standards we impose on ourselves, the more readable the code becomes without having to think about it.
Reading Order & Cohesion Order
Keeping the language's limitations in mind (sometimes a function can't be called if it's defined further down, etc.), it's very useful to organize files so they deliver information in order: imagine having to explain them to a stranger and make things easy to find.
It's also important to keep everything that's coupled (when decoupling isn't immediate and trivial) as close as possible to the rest: functions within the same file, files within the same folder, and so on!
There's no standard carved in stone, but there is common sense.
Move Declaration and Initialization Together
Variables should be initialized as early as possible once declared. A good rule of thumb is often to put them at the top, but placing them at intermediate stages can make sense too.
What matters is putting them where they're needed, right before they're used and not in random spots.
Explaining Variables & Constants
If a function is too elaborate and full of long expressions, it's a great opportunity to introduce well-named variables, to simplify reading and help other devs at the same time.
The same goes for magic numbers: if they carry a precise meaning within the logic, turning them into constants makes refactoring easier and clarifies their intent.
Chunk Statements
If a function or a file is very large, splitting it into sections is naturally less confusing.
It's the simplest fix of all, but it can be hugely satisfying for orienting yourself — especially in Flutter widgets!
Explicit Parameters
Whenever possible it's better to write pure functions, meaning functions that affect the outside world only through their output. It's also better to make sure input data is explicitly declared in the parameters.
An example of what not to do: reading environment variables inside the body instead of asking for them as parameters.
Extract Helper
Create helper methods, mixins, or files to solve common problems.
In conclusion: before refactoring you have to understand, and to understand you have to tidy up. Micro-refactors are cheap, they don't change the behavior of the code, and they give you the map you need to tackle the real changes.
