DEV Community

Cover image for How to refactor without overtime and missed deadlines
Nikita Milyanik for Studio M - Song

Posted on • Edited on

How to refactor without overtime and missed deadlines

A lot of software engineers, including myself, are passionate about code quality. This striving for a well-shaped codebase, while getting things done could cost one quite a few hours and nerves, though. I'm constantly looking for ways to achieve these two goals without significant trade-offs. Stand by for the current state.



Refactoring is "a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior" according to Martin Fowler. In other words, the goal pursued by this alteration is a well-structured, readable code doing the same thing, but taking new changes more naturally.

The unchanged observable behaviour he mentioned covers both directions, by the way. Clearly everything should work the same way afterwards. Simultaneously we are liberated from putting any bug fixes or performance improvements on the top of it. It could be a pleasant side effect, but not the destination of refactoring. Better not to feed two birds with one scone to decrease the probability of new bugs. At the end of the day it'll save some debugging time.

It applies, even if optimization is the objective of your current mission. Cold-bloodedly improve structure without any performance, but readability in mind. And then, with much better understanding, start with the optimization. It will often be done in less time, than jumping into a badly structured code right away.

Don't dedicate time

Sounds counterproductive, I know.

However, the point is, a dedicated time for some code restructuring will often sound like a joke. Unless it's a side project on your own, good luck in convincing your manager. They'd have a concrete solid argument – numbers. Totally reasonable, who wants to pay for a prettier code, while software remains literally the same? As I said in the beginning, refactoring won't necessarily bring any billable value to a product. Performance wouldn't jump higher, bugs wouldn't disappear.

Good news is, you don't have to convince anyone, if refactoring is just a part of your daily routine. Like using terminal, commiting in git, reading emails, etc. Such activities are probably seamlessly integrated into your workflow as only a fraction, thus no one really worried about time spent on them. Similar to an artist preparing a canvas before painting, refactoring is about a software engineer preparing a code before editing – a natural part of the job.

However, none of your initially small refactorings should suddenly swell into a fortnight marathon with a broken codebase.

Continually functional software is essential, 'cause it gives full control over the process. You could stop refactoring basically at any moment and leave the building switch to an actual task.

Use the proper technique

I've learned that basically two components shape the controlled refactoring: small transformations and fast testing, performed after each of those changes.

Small step-by-step transformations help a lot with preventing a broken state. A mistake may occur reasonably rarely by changing only a few lines of code.

The steps should ideally be as small as renaming a variable or extracting a block of code into a function. Consider automated tools, like the "Rename Symbol" command in VS Code, as they're often less error prone than manual editing.

Such changes could have a little meaning on their own. But since every of them is a move towards a better code structure, you'll eventually end up with satisfying results. Think of it as a chess game – a coherent strategy leads to a win. Even though a single move could seem quite passive or even odd, they're powerful together.

And even if something goes wrong, it's easy to go a step back and try again. Hence be generous in committing those steps into a version control system (e. g. git). Those are checkpoints. Go with meaningful commit messages like "extract the validation logic into a function" instead of generic ones like "refactor". If something goes wrong, you can easily navigate between those commits. Something broke at the very beginning, but you've noticed it only after a dozen commits? Don't worry and try git bisect. Don't worry about messy git history either, squash it before merging into a main branch.

In spite of how precise changes are, you can't be completely sure based only on a static code analysis whether everything still works the same way. We're still humans, right? Ideally, assure the health of a codebase after each piece of change. That's why fast and easy-to-run testing is required. In the best case, there're automated tests executed against the part of a system you're currently working on. Lack of automated tests? Probably it's a good moment to add some. Otherwise try to find the path of least resistance to test your system manually.

That was a brief overview of the controlled refactoring technique. I just wanted to share essential concepts within my piece of advice without aiming at being a definition guide. If you'd like a deep dive, I recommend starting with Martin Fowler's "Refactoring: Improving the Design of Existing Code" book. I drew quite a bit of thoughts from it, applied to my daily work, which inspired me to write the text you're reading now.

Remember the scope

It's pretty common yet understandable to go too far with a refactoring. Using the technique we just talked about, it might be quite tempting to just follow the rhythm and forget about an actual goal.

In my workflow I usually try to evaluate by asking the following question. Did my code become good enough to start with the actual implementation?

There will always be room for transformations towards perfection. Doubtful, that it could be achieved in the end. Moreover no matter how close you were, software is usually constantly being changed within its lifecycle. Over time and through follow-up fixes there's a good chance for a shift from the perfect match to the "why would one choose that way!?" reaction. So, why would you waste too much of your precious time on it? You might want to consider the Pareto principle for good measure.

Don't get me wrong. I don't encourage you to rush into things without consideration for quality. Refactoring definitely worth itself and increases both quality and speed of development. Better aim for the good than for the best, though. Restructure your code with the future in mind, but mainly focus on the present. Consider edge cases, but don't try to cover unrealistic ones. Follow paradigms, but prefer readability. Keep your own coding style, but don't let it lead you.

When it's better not to refactor

At last, what could be better from the time-saving perspective, than just skipping the whole thing?

But as a decent engineer, you'd probably like a good reason to pass.

I consider the rule of thumb, that refactoring of a working code without aiming to utilize it is probably a waste of time.

Every so often for one or another reason you might stumble across a random smelly code. Although the code is unrelated to your current work, it could be easily very tempting to improve it. Especially when flaws are on the surface.

In that case it's better to get going. But, I must say, I'm usually having a bad feeling leaving such a codebase untouched. Sometimes the bad feeling wins. I jump in with the hope to introduce quick low-effort improvements here and there. Then I start to notice how far it goes away from actual work and regretfully revert my changes. Be smarter than me and avoid such traps. It'll probably save you quite a bit of time, drained by possible follow-up hot-fixing.


How to refactor without overtime and missed deadlines? Seamless integration into the daily working routine, healthy amount of discipline, pragmatic level of quality were the answers on my way so far.

Hopefully they'll help you to improve your development experience. At the end of the day it's what matters when building things. Remember, these workflows constantly evolve – it’s an ongoing process, don't struggle to perfect it overnight.

I'm always looking for better approaches, please don't hesitate to share yours in the comments. And to challenge mine, of course.

The credit for the cover photo goes to Naja Bertolt Jensen on Unsplash.

And the special credit goes to @josefine for making this text real in the first place.

Top comments (9)

Collapse
 
jonrandy profile image
Jon Randy 🎖️

The best way I've found - like you say - is to not treat refactoring as a separate task. It should be part of your normal process. I would also suggest not working to deadlines, or doing overtime - neither of these are conducive to good work and should be avoided

Collapse
 
z2lai profile image
z2lai • Edited

You should treat it as a separate task with it's own acceptance criteria and testing though, here's a great post about this advice:

Probably the fundamental observation in Fowler's Refactoring book is that you are much better off separating changes that affect code behavior from changes that don't. Those in the latter category, we call "refactoring". If you mix these changes, you mess them up - so don't. You can switch back and forth between coding and refactoring modes, but don't do both at the same time. Because if you try to, you won't know what you did wrong.
If you try to introduce a behavior-modifying change, and the behavior doesn't modify: you did something wrong; review it and fix it. Maybe just roll it back.
If you try to introduce a refactoring, and the behavior does modify: you did something wrong; review it and fix it. Maybe just roll it back.
You can't make that simple assessment if you attempt both changes at once. Nor can you, if you don't have unit tests. Write them. And do one thing at a time.

softwareengineering.stackexchange....

Collapse
 
fredicious profile image
Fred

Great article, I can totally relate. I think it's worth mentioning that when you practice TDD, refactoring is already part of your work flow (the 3rd step also known as the blue phase: martinfowler.com/bliki/TestDrivenD...)

Collapse
 
nikmilson profile image
Nikita Milyanik • Edited

Thanks, really appreciate it.

I haven't seen yet many engineers intensely practicing TDD, though. For many the technique is clearly quite extreme. But yeah, your addition is surely relevant.

Collapse
 
danielsc profile image
Daniel Schreiber

Totally agree!

There is probably a small typo:

Consider automated tools, like the "Rename Symbol" command in VS Code, as they're often more error prone than manual editing.

Should be „…less error prone…“?!

Collapse
 
nikmilson profile image
Nikita Milyanik

You're absolutely right, thanks! Corrected.

Collapse
 
crebelsky profile image
Christian Rebelsky

They'd have a concrete solid argument – numbers. Totally reasonable, who wants to pay for a prettier code, while software remains literally the same? As I said in the beginning, refactoring won't necessarily bring any billable value to a product.

Not 100% happy with this argumentation, depending on lifetime of code and project. Just sell it to save working hours for all devs involved. A couple of scenarios that come to my mind are:

  • reduce debug time - as you already mentioned
  • making parts of the application reusable to save time in upcoming developments
  • reduce the amount of time onboarding new colleagues

So for most code bases (at least the ones i came across in recent years), it's in my opinion good to start with some initial dedicated time and it's possible to sell this if necessary. If this is done and matches the code quality guidelines of the team (if present), it's fine refactoring on the go. As you implement new features, make improvements, ... .

Great article - thanks!

Collapse
 
nikmilson profile image
Nikita Milyanik

Oh, as a developer, I don't like this rationale either. Unluckily, I experienced it quite often so far. It doesn't mean by any chance a management's failure, though. Probably I wasn't looking quite convincing, 'cause find the argumentation still partly reasonable.

The scenarios you suggesting might work quite well. Especially if you're starting working on a stale codebase and sure about the upcoming area of activity. But still, why wouldn't you just keep those things in mind to dev team and serve it to the management under a sauce of a higher estimation due to poor code quality? From my naive point of view I would rather buy that, if I were a manager.

Thank you! I'm glad you like the article.

Collapse
 
suyogwani11 profile image
Suyog Wani

Nice article.
Takeaway: avoid such traps, quick low-effort improvements here and there.
Simply put
'Always leave the code better than you found it.' - Robert C. Martin (Uncle Bob)