DEV Community

loading...

Discussion on: Clean code, dirty code, human code

Collapse
erikest profile image
erikest • Edited

As with most things, there's all sorts of intersections and cases to consider on this spectrum and what is refactoring for simple clarity/cleanliness versus refactoring for good architecture and reduction of tech debt.

We've all inherited a code base, or written one (like me), where the authors leaned towards 'it works' and wrote code that quickly achieved the intended goal at the time, and then was grown to handle various new requirements. At each iteration a new 'solution' was derived to add widgets 1, 2 and 3 and the code marched forward.

When we, those who come after, then take our turn, we find ourselves deep in the technical debt that was left behind. In those situations, it feels certain that a refactoring like the one Dan Abramov describes would have made our job easier. At some point, someone with enough context and sense for the direction of the project could have reorganized the code in a way that is demonstrably better, that establishes a pattern for extension that we, the people having to add widget4, would be able to quickly grok and build upon, leaving a well ordered and easily understandable chunk of code for the next developer.

Then there's code like Aleksandr below proposes - code that was written quick and dirty, but then thoughtfully refactored for readability or performance - better method names, smaller methods, fewer allocations, added some caching, etc. And this can be great, all that is necessary, except sometimes it's the overall design and structure that becomes limiting regardless of how clearly this chunk attains the localized goal - it may do the same thing almost as well as other code that was written somewhere else or perhaps it could have been segregated or composed in a way that made it easier for similar components to reuse in the future, or it could be clear and quick to write, but it doesn't fit in with patterns established everywhere else (hey, this thing is talking directly to the database, but we have a repository for that!)

Pernicious smells and debts can creep in this way. You can find yourself with a whole lot of code that does what it's supposed to do, and is clear in that purpose, but still is difficult to extend without becoming an expert in the entire system. Or you can end up making improvements that only help there instead of everywhere like there.

There can be value to slowing down and defensively refactoring out repetition across a code base or being more thoughtful in the design, balanced against over engineering. That's the art, choosing enough good design that you leave the hints and the intention for the expansion, so when it's time to grow the system those people looking in with fresh eyes can see where and how it can be most easily extended.

For example, if I can see that a system I'm building has a clear place where a chunk of functionality could be abstracted out, say in a strategy pattern, even if there's only one such strategy now, I may still elect to take a small amount of time to build in this pattern now, because I have the context and understanding to know that this piece is replaceable. Later someone else looking to build a new feature may be determining how difficult it will be and come across this 'hint' and think - "oh, that piece there is 'swappable', which means my feature can simply implement a new strategy there and away I go". It may mean the difference between a feature being 'feasible' or 'a lot of work'. Though on the other hand, someone may never come back to this code and need that extension point, so it's a judgment call - but my feeling is that over time, without refactoring for extension or pulling out the commonality, systems become hard to reason about how to extend them and about how hard that extension will be to implement.