Good Refactoring vs. Bad Refactoring
Refactoring is vital for code quality, enhancing readability and maintainability. However, poor practices can lead to complexity and bugs. Incremental changes, thorough understanding, and testing are essential for success.
Read original articleRefactoring is essential for maintaining a healthy codebase, but it can easily go wrong. Good refactoring improves code readability and maintainability, while bad refactoring can introduce complexity, bugs, and performance issues. Common pitfalls include changing coding styles drastically, adding unnecessary abstractions, and creating inconsistencies within the codebase. Developers often make the mistake of refactoring without fully understanding the existing code, which can lead to the removal of critical functionalities, such as caching mechanisms. Additionally, failing to consider the business context can result in poor architectural decisions, such as using client-side rendering for SEO-dependent applications. To refactor effectively, developers should make incremental changes, understand the code thoroughly, maintain consistency in coding styles, and avoid introducing new libraries without team consensus. Writing tests before and during the refactoring process is crucial to ensure that the original functionality is preserved. Tools like linting can help enforce coding standards and catch potential issues early in the refactoring process.
- Good refactoring enhances code readability and maintainability.
- Bad refactoring can introduce complexity, bugs, and performance issues.
- Understanding the existing code is crucial before making changes.
- Consistency in coding style is key to maintainability.
- Incremental changes and testing are essential for successful refactoring.
Related
The only way to fix a tech zoo is by focusing on stability first
Klaus Breyer stresses stability as key to addressing outdated tech in a step-by-step process. Enhance observability, assess tech objectively, define a vision, and modernize gradually through teamwork and continuous improvement.
The Transformation Priority Premise (2013)
The Clean Coder Blog discusses Transformations in code development, distinguishing them from Refactorings. It emphasizes maintaining a preferred order of Transformations to enhance the red/green/refactor cycle efficiency. Examples illustrate transitioning from specific to generic code.
Beyond Clean Code
The article explores software optimization and "clean code," emphasizing readability versus performance. It critiques the belief that clean code equals bad code, highlighting the balance needed in software development.
Ask HN: Business logic is slowing us down
Developers face challenges balancing internal stakeholder demands and external user needs, often spending time on code maintenance. Recognizing this work is crucial for enabling focus on new feature development.
Algorithms We Develop Software By
The article explores software development methodologies that improve coding efficiency, emphasizing daily feature work, code rewriting, the "gun to the head" heuristic, and effective navigation of problem spaces.
- Many commenters emphasize that refactoring should not be an isolated goal but should be tied to real work, such as fixing bugs or adding features.
- There is a consensus that understanding the existing code and its context is crucial before undertaking refactoring.
- Several commenters argue against the idea of enforcing consistency across a codebase, suggesting that flexibility and adaptation to changing requirements are more important.
- Some express skepticism about the necessity and effectiveness of certain refactoring examples provided in the article, questioning their practicality.
- Concerns are raised about the culture surrounding refactoring, with some suggesting that poor engineering practices and management pressures can lead to bad refactoring outcomes.
The example with the for-loop vs. map/filter in particular - it's such a micro-function that whichever the original author chose is probably fine. (And I would be suspicious of a developer who claimed that one is 'objectively' better than the other in a codebase that doesn't have an established style one way or the other).
Refactor when you need to when adding new features if you can reuse other work, and when doing so try to make minimal changes! Otherwise it kind of seems more like a matter of your taste at the time.
There's a limit of course, but it's usually when it's extremely obvious - e.g. looong functions and functions with too many parameters are obvious candidates. Even then I'd say only touch it when you're adding new features - it should have been caught in code review in the first place, and proactively refactoring seems like a potential waste of time if the code isn't touched again.
The (over) consolidation of duplicated code example was probably the most appealing refactor for me.
>Remember, consistency in your codebase is key. If you need to introduce a new pattern, consider refactoring the entire codebase to use this new pattern, rather than creating one-off inconsistencies.
It's often times not practical (or even allowed by management due to "time constraints") to refactor a pattern out of an entire codebase if it's large enough. New patterns can be applied to new features with large scopes. This can work especially in the cases of old code that's almost never changed.
> If you need to introduce a new pattern, consider refactoring the entire codebase to use this new pattern, rather than creating one-off inconsistencies.
Putting aside the mis-application of "pattern" (which _should_ be used with respect to a specific design problem, per the Gang of Four), this suggestion to "refactor the entire codebase" is impractical and calcifying.
Consistency increases legibility, but only to a certain point. If the problems that your software is trying to solve drift (as they always do with successful software), the solutions that your software employs must also shift accordingly. You can do this gradually, experimenting with possible new solutions and implementations and patterns, as you get a feel for the new problems you are solving, or you can insist on "consistency" and then find yourself having to perform a Big Rewrite under unrealistic pressure.
The easiest way to accomplish a real goal like fix a bug, or add a feature, may very well be to first refactor the code. And yes maybe you want to merge that in as its own commit because of the risk of conflicts over time. But just having the code look nice (to who exactly?) isn't valuable, and it encourages the addition of useless abstractions. It may even make later real-work harder. Never refactor outside the context of real-work.
The cartoon with the PM also gets at a ridiculous pattern: engineers negotiating when to do various parts of their job with non-technical people who have no idea how do any part of their job. The PM doesn't know what a refactor is, the EM probably doesn't either. It doesn't make the organization function any better to tell these people about something they don't understand, and then ask them when it should be done. Budget it as part of the estimate for real-work.
Bad refactoring is elitist, "you won't understand this" commented and the owner walks with nobody left behind who understands it.
That the examples deprecated FP and preferred an idiom natural to Java(script) only speaks to the principle. I can imagine a quant-shop in a bank re-factoring to pure Haskell, out of somthing else, and being entirely happy that its FP respecting.
So the surface "FP patterns are bad" is a bit light-on. The point was, nobody else in that specific group could really be expected to maintain them unless they were part of the culture.
"If you unroll loops a la duff's device, you should explain why you're doing it" would be another example.
And while many devs are resistant to try functional ways, this first example reads so much better than the original code that I find it impossible to believe that some prefer the imperative loop/conditional nesting approach.
These two metrics are interrelated, but as a general rule if the gzipped size of the codebase (ignoring comments) does not go down, it's probably not a good refactoring.
The title focuses on good and bad refactoring, but most of the content discusses good and bad design. This means that many of the bad examples are inherently bad, regardless of whether they were refactored from another version or written from scratch. The introductory comic and the conclusion mention how to perform refactoring, but the rest of the article drifts away from this and only discusses the resulting code. The first pitfall mentions changing the coding style, but the explanation actually addresses the problem of introducing external dependencies. The fifth point, "understand business context," should actually be "not understanding business context." If we perform refactoring incrementally, it's inevitable that there will be some inconsistencies during the process. Therefore, the third pitfall, "adding inconsistency," should include additional explanations.
In summary, I think the article would be more helpful if it focused more on how to perform refactoring rather than criticizing a specific piece of code.
Often code structure matters much less than data flow and how it is piped. Since most codebases use a mixture of:
- global state or singletons, - configurations provided externally (config file, env var, cmd option, feature flag, etc.), that are then chopped up and passed around, - wrappers and shims, - mix of push and pull to get input/outputs to functions, - no consistency or code representation of assumptions about handling mutable state,
It may be better to build around existing code using ideas listed here than to try to refactor code to improve its structure: - open/closed principle = compose new code for new functionality (instead of modifying), - building loosely coupled modules (that interface via simple types and a consistent way of passing them) - enforcing an import order dependency via CI (no surprise cyclic dependencies months after an unrelated feature added some import that doesn't "belong")
The code's structure will be simple if the dataflow (input, outputs, state, and configuration) flows consistently through the codebase.
Imagine working on a legacy codebase where the PM holds the dogma of refactoring being a bad thing and expecting you to do it wrong, even micro managing your PRs.
Most often than not, I do see projects suffering and coders actually resigning due to a lack of internal discussing about best practices, having space/time to test potential solutions, having Lead devs who resemble dictators quite well.
Let me guess, some PM wrote this article and they just want you to push the product asap by applying pressure and not allowing you ever to refactor. This is just a casual day in software development. I'm not surprised anymore when most web apps have silly bugs for years because it's gonna be a Jira ticket and a big discussion about..... one evil thing called refactor.
Several years ago I rewrote a full SaaS in about 3 months, it took another team 12 months with 5 devs. Guess which version made the investors happy, mine.
Bad refactoring is just a product of poor engineering culture.
refactoring is overrated plus refactor is all about: clarify the terms, then do the thing
I’ve found almost always that less is best.
That is, extracting the caching logic from the API call logic.
Caching could have been a more generic function that wraps the API call function. That way each function does exactly one thing, and the caching bit can get reused somewhere else.
Instead, this weird advice was given: changing behavior is bad refactoring. Which is weird because that's not even what we call refactoring.
Edit: removed unnecessary negativity.
Refactoring is about moving existing code around, not introducing new code. Replacing localStorage methods with cacheManager is a fix/feature. Updating one part of the codebase to work completely differently from the rest is a fix/feature. Changing processUsers to a whole useless class is not considered refactoring, it is a fix/feature. A single page app for a SEO-focused site is NOT a bad idea since 2018. Most examples of "refactors" in the article are actual fixes and features which brought (bad), or not brought (good) new regressions into the software.
> More than a few of them have come in with a strong belief that our code needed heavy refactoring.
The code might, but a blind spot for many developers is that just because they are not familiar with the code doesn't mean it is bad code. A lot of refactoring arguments I have seen over the years do boil down to "well, I just don't like the code" and are often made when someone just joins a team at a point where they haven't really had time to familiarize themselves with it.
The first point of the article sort of touches on this, but imho mainly misses the point. In a few teams I worked in we had a basic rule where you were not allowed to propose extensive refactoring in the months (3 or more) of being on the team. More specifically, you would be allowed to talk about it and brainstorm a bit but it would not be considered on the backlog or in sprints. After that, any proposal would be seriously considered. This was with various different types of applications, different languages and differently structured code. As it turned out, most of the time if they already did propose a refactor, it was severely scaled down from what they initially had in mind. Simply because they had worked with the code, gained a better understanding of why things were structured in certain ways and overall gotten more familiar with it. More importantly, the one time someone still proposed a more extensive refactoring of a certain code base it was much more tailored to the specific situation and environment as it otherwise would have been.
Edit: Looks like it is being touched on in the fourth point which I glossed over. I would have started with it rather than make this list of snippeted examples.
The better refactor to introduce OO concepts would have been to introduce an isAdult function on the user class and maybe a formatted function. This + the functional refactor probably would have made for the best code.
return users.filter(u => u.isAdult())
.map(u => format(u)); // maybe u.formatted()
[0] https://www.yegor256.com/2015/03/09/objects-end-with-er.htmlFor a dev that's a new hire, refactoring the code is also a way for them to feel ownership over it. The PM should be happy that they're thinking about the way the code works and the way the code should work. It's on the company to have review & qa processes that catch problems before they lead to downtime.
I don't disagree that some of the examples given are bad refactors, but in regards to adding inconsistency I see that happen a lot more when rushing out new features or bug fixes than when refactoring; usually the refactor is the effort trying to establish some kind of consistency. And example 5 isn't a refactor it's just removing functionality. If that was the intent, the person should be told not to do that. If it's an accidental side effect of some larger refactor effort, then just add the functionality back in a new PR. Accept that mistakes happen, adopt some QA controls to catch them, and build a culture that encourages your developers to care about your product.
Not a very qualitative article.
This article just reminds me why I hate JavaScript so much. I know you frontend engineers can’t avoid it, but I wish we could come up with something better.
Basically, make it simpler without breaking it or actually making it not simpler.
No need to loop twice over the array. Always use reduce if there are chained methods going through arrays
Related
The only way to fix a tech zoo is by focusing on stability first
Klaus Breyer stresses stability as key to addressing outdated tech in a step-by-step process. Enhance observability, assess tech objectively, define a vision, and modernize gradually through teamwork and continuous improvement.
The Transformation Priority Premise (2013)
The Clean Coder Blog discusses Transformations in code development, distinguishing them from Refactorings. It emphasizes maintaining a preferred order of Transformations to enhance the red/green/refactor cycle efficiency. Examples illustrate transitioning from specific to generic code.
Beyond Clean Code
The article explores software optimization and "clean code," emphasizing readability versus performance. It critiques the belief that clean code equals bad code, highlighting the balance needed in software development.
Ask HN: Business logic is slowing us down
Developers face challenges balancing internal stakeholder demands and external user needs, often spending time on code maintenance. Recognizing this work is crucial for enabling focus on new feature development.
Algorithms We Develop Software By
The article explores software development methodologies that improve coding efficiency, emphasizing daily feature work, code rewriting, the "gun to the head" heuristic, and effective navigation of problem spaces.