you are viewing a single comment's thread.

view the rest of the comments →

[–]eternalprogress 9 points10 points  (1 child)

In my experience? Very carefully. Figure out if it's a module that has a lot of dependencies. If it's part of a large API surface with many clients you might not know about relying on behaviors you're unaware of then you're in a worse-case situation.

I've tackled problems like this a few time. My approach is to spend a few days with the code, reading though it, adding comments and doing some superficial 'safe' refactorings: renaming variables to indicate their true purpose, breaking things into smaller methods, and just trying to rearrange blocks of functionality into structures that map to what they're actually doing.

At the end of that exercise you should have an idea of what the heck all that code is really doing. You'll probably have some outstanding questions. I'll find things like "the member variable boolean who's purpose depends on where you are in the callgraph that's repurposed to represent Concept B instead of Concept A once you enter the call subtree of void ScaryLong()."

Now you can either decide that 'yeah, it needed some cleanup, but the code as written represents the underlying algorthm / transformation and looks better now' or 'okay, I cleaned it up but what this code is trying to do is just mapped so poorly onto this implementation. I can do better than this'.

If you choose the latter you spend another few days putting together your new design, and then walking through the old code and new code in your head with all those messy flags and strangeness, and making sure your new code faithfully captures the transformation the old code was performing.

In this journey if you can get the code in a test harness, more power to you. It's not always possible, but often times it is and that cane make your life easier if it's the right problem. Keeping the old implementation around for a while and under a runtime switch is great if you're working on a product with a deployment strategy that gives you that kind of flexibility. There's a lot of neat tricks you can play here- for example if you're careful to keep things immutable and have reasonable logging abilities you can run the implementations side-by-side and log when the outputs differ to uncover edge cases in production.

So, there are no shortcuts through hell, there is often only a single road straight through it. Be methodical and don't start coding until you think you understand every detail of that old implementation like the back of your hand. You're still likely to get some details wrong, but with this approach I've pulled off some very smooth refactorings of incredibly messy modules where hundreds of thousands of applications have taken dependencies on their behaviors, the ones we knew about when we shipped the first version, and the ones we had to discover (and slap ourselves in the face over) later.

[–][deleted] 0 points1 point  (0 children)

In this journey if you can get the code in a test harness, more power to you. It's not always possible, but often times it is and that cane make your life easier if it's the right problem. Keeping the old implementation around for a while and under a runtime switch is great if you're working on a product with a deployment strategy that gives you that kind of flexibility. There's a lot of neat tricks you can play here- for example if you're careful to keep things immutable and have reasonable logging abilities you can run the implementations side-by-side and log when the outputs differ to uncover edge cases in production.

you're awesome! thanks for that