dev-resources.site
for different kinds of informations.
Refactoring: The Art of Polishing Code
Refactoring is the process of improving the internal structure of code without changing its external behavior. This article explores how this practice is fundamental to creating high-quality solutions.
âRefactoring is the process of changing the source code in a way that does not alter its external behavior while improving its internal structure. It is a disciplined technique for cleaning and organizing code, and as a result, minimizing the chances of introducing new bugs.â
â Martin Fowler
From the dictionary, âto polishâ means to perfect something rudimentary, rough, or crude; to refine. This definition captures what we seek in refactoring: making changes in the code in pursuit of perfection and beauty.
Developer: A Digital Craftsman
What is a software developer if not a digital craftsman, driven by the quest to create inspiring solutions? Weâre not just interested in creating solutions that work, but in doing so with quality. The results donât always meet expectations, but unlike traditional craftsmen, we can revisit a completed piece and make adjustments at any time.
As stated at the beginning of the article, we seek improvements that bring beauty to our code, and we must remember that beauty resides in simplicity. Therefore, we should strive to simplify our code, as we may not always realize the value this adds.
A simple codebase is easy to understand, whether by another craftsman or by ourselves in a few weeks or months. As Kent Beck says, code should âreveal its intentions.â
Code that is easy to understand can just as easily be modified and evolved, with fewer chances of side effects. Yes, Iâm talking about bugs â and I know that caught your attention. Gotcha!
Itâs essential to acknowledge the uncomfortable truth that we donât always get the design right on the first attempt, regardless of our seniority level in software development. The initial version of code might not be the cleanest; depending on circumstances, weâre often more focused on getting it to work and plan to take a more critical look later, evaluating whether weâve violated any SOLID principles or ignored a design pattern.
There are times when you might recognize the need to modify existing software before adding new features to facilitate the integration of new code.
âMake the change easy, then make the easy change.â
â Kent Beck
Why Tweak Whatâs Already Working?
If youâre a software developer who has never asked this question, I must congratulate youâyou love what you do and care about doing things well. However, the chance of someone asking this question is high, and I donât blame them. Understanding the âwhyâ should guide our decisions.
If I had to convince my boss of the importance of refactoring, Iâd present the following case study, drawn from the book Clean Architecture, where we can analyze real data from a real company.
The first graph shows the growth of the engineering team, followed by a graph on productivity measured in lines of code. In the second graph, we see that while each release includes a larger team, productivity begins to stagnate.
Graph 1 â Engineering Team Growth
Graph 2 â Productivity Over Time
More lines of code were expected with the addition of new developers, right? You might think other factors influenced the outcome, such as a lack of motivation or team dedication, but what was observed was that overtime hours didnât decrease during this period.
Looking at the cost per line of code graph, we see that costs increased by 40 times between releases one and eight.
Graph 3 â Cost Per Line of Code Over Time
If you really want to startle the company board, present the next graph, which shows the monthly payroll for development over the same period. What started at several hundred thousand dollars reached around $20 million by the eighth release, with no forecast for change.
Graph 4 â Monthly Development Payroll Per Release
This is the picture of a system where meeting deadlines was always the priority. Developers were added in hopes of producing more code in less time, with little regard for code quality. Over time, adding new features became harder, as most efforts shifted to fixing issues, leaving little room for significant new functionality.
Agile Methodologies
The concept of agile is often misunderstood, with many people equating agility with speed. Setting tight deadlines to appear âagileâ is far from the reality. When applied correctly, the agile method can create the impression of rapid progress, as if developers are accelerating to meet delivery targets.
Where agility truly makes a difference is in responsiveness to changes, decision-making, and project course corrections when needed.
But as not everything is rosy, working with agile methods can bring the following challenges:
- Requirements can change, bringing new perspectives to the solution.
- Starting a sprint without clearly defined requirements â which is often part of the frameworkâs essence â allows for earlier failures, enabling adjustments to be made sooner.
In these cases, refactoring is a useful and necessary tool, given the agile methodologyâs characteristic of seeking small, incremental deliveries and admitting mistakes that must be promptly corrected.
Want to learn more about the agile world? Itâs worth reading some excellent articles on applying the agile manifesto to developing an agile mindset and on the functioning of a Scrum team.
When to Refactor?
Ideally, refactoring should happen within a reasonable timeframe, not too distant from the code creation, also known as timely refactoring. However, before starting to refactor, certain prerequisites must be met.
If youâre a careful reader, you may have noticed that the word âbehaviorâ appears multiple times throughout the article and might have guessed that itâs a critical element of refactoring. But how can we ensure that code behavior remains unchanged when dealing with projects with a large volume of code, workflows, and scenarios? Thatâs where testing comes in.
The first requirement before considering refactoring is the presence of unit tests, which aligns with Kent Beckâs first rule of Simple Design. Unit tests are essential for ensuring that the softwareâs behavior remains consistent, functioning as a contract and providing developers with the confidence to make necessary changes.
Of course, having tests isnât enough; good test coverage is essential. Coverage rates of 2%, 5%, or 8% are better than nothing but are very low numbers that wonât provide sufficient assurance that everything is working as it should, or that bugs havenât been introduced.
To ensure the success of the refactoring, all unit tests must continue to pass without modification.
When Not to Refactor
In theory, all poor code should be refactored, but there are exceptions where we can assess whether the effort is worth it. When dealing with legacy software, for instance, we must determine whether itâs worth refactoring or deprecating it.
For example, you could evaluate whether the language versions and libraries are outdated. The absence of unit tests may indicate poor code quality and a high likelihood of bugs.
In some cases, the effort required to refactor software with significant problems might be equivalent to that of writing new software.
Measuring your Code
Cyclomatic Complexity and Cognitive Complexity are metrics used to determine code quality. Performing this analysis manually is time-consuming, so you should use tools that automate this process. The goal is to provide a basic understanding of these metrics to clarify what they represent.
Cyclomatic Complexity
Cyclomatic Complexity measures how difficult it is to test a unit of code. Itâs useful for evaluating the complexity of code segments, such as methods and routines, rather than the codebase as a whole. In short, it measures the maximum number of independent paths within the code.
This value has a direct relationship with the number of test scenarios required to achieve 100% coverage for a method. A high number of test scenarios indicates that the method is complex and challenging to read.
Cognitive Complexity
Cognitive Complexity measures how difficult it is to understand a unit of code. It assesses the number of breaks in the linear reading flow, weighted by the level of nesting of these breaks.
It focuses on Kent Beckâs second rule of Simple Design: revealing the codeâs intent, scoring low when the code is more expressive.
Code Analysis Tools
Beyond our ability to spot problems in code, we have tools that make our lives easier, such as SonarQube, JArchitect, ESLint, and others.
Using SonarQube as an example, it provides an overview of a projectâs code status with various indicators.
One interesting metric in SonarQube is technical debt, which estimates the approximate time required to refactor your code.
Refactoring: The Art of Polishing Code â Final Considerations
Imagine a wise man on top of a hill defining refactoring. He might say, âTo refactor is to change without changing,â typical of those who hide knowledge in cryptic phrases and donât care if you understand them. But I hope this article has helped you enough for the message to make sense: refactoring is about changing the code without altering its essenceâits behaviorâin pursuit of simplicity for maintainability.
And maybe this concept isnât entirely unfamiliar. If youâve ever replaced a magic number with a constant or renamed a variable to something more meaningful, guess what? Youâve refactored your code. Cheers!
Let's connect on social media, follow us on LinkedIn!
Article written by Rodrigo Prata, and originally published at https://kwan.com/blog/refactoring-the-art-of-polishing-code/ on October 18, 2024.
See you in the next article!
Featured ones: