"Improving the design of existing code" this is how Martin Fowler defined refactoring in a single sentence in his 1999 book Refactoring: Improving the Design of Existing Code. While the idea of improving existing code had been known in the software development world long before the book was published, Fowler’s work helped formalize the concept. Since then, refactoring has become a well-understood and widely accepted practice among developers.
So how does refactoring support the development and maintenance of the Higson project?
Let’s take a closer look.
A Closer Look at the Core Concept
"Code refactoring is the process of improving the internal structure of existing source code without changing its external behavior."
Let’s put on our linguistic expert hats for a moment and break this definition down, piece by piece:
- "Refactoring is a process..." meaning a sequence of logically connected, purpose-driven changes.
- "...of improving..." to make the code easier to read, faster to modify, safer to work with, or simply cleaner.
- "...the internal structure of existing source code..." referring to how the code is organized, how its components interact, and how responsibilities are distributed.
- "...without changing its external behavior." ensuring the functionality remains identical before and after the changes.
The Role of Refactoring - Higson BRE Project
Higson, the business rules engine, is a mature project with over a decade of development behind it a considerable span of time in the world of IT. Over the years, Java has evolved significantly, libraries have been updated, containerization has become widespread, security awareness has increased, and UI/UX technologies have shifted.
Throughout this period, Higson has continued to adapt. It has been successfully deployed for clients across the globe, continually enhanced with new features, improved for stability, made more resilient to errors, optimized for performance, and hardened for security. Processes have been streamlined along the way.
And amid all these exciting changes, there has always been a quiet, steady presence: refactoring.
Why We Refactor in Higson: Goals and Principles
Refactoring in the Higson serves clearly defined goals that contribute to long-term success and sustainable development.
Here are some of the guiding principles we follow throughout this process:
1. Improving Code Readability
Complex code fragments can be difficult to understand — even for experienced developers. Refactoring helps simplify logic, eliminate duplication by extracting common parts, assigning more descriptive names to variables and methods, and break down large functions into smaller, more manageable pieces.
Readable code means easier maintenance, faster onboarding, simpler debugging, and a lower risk of introducing new bugs.
2. Improving Structure and Reducing Project Complexity
Over time, a project’s architecture tends to evolve under the weight of newly added features. Refactoring is an opportunity to realign the structure with established design patterns and architectural principles.
Reorganizing modules, relocating classes, or redefining dependencies between components can result in greater flexibility, easier testing, and reduced risk of cascading changes.
3. Preparing for New Feature Development
Sometimes existing code, while technically correct, creates obstacles when implementing new functionality. In such cases, refactoring helps pave the way for upcoming changes by making the codebase more accommodating and reducing the risk of introducing regressions during integration.
4. Updating External Dependencies
Keeping external dependencies up to date is essential for security, performance, and access to new features. However, newer library versions often introduce changes that require refactoring the existing codebase to maintain compatibility and take full advantage of the improvements they offer.
5. Enhancing Performance
Performance is a critical aspect of the Higson engine, so one of the top priorities during refactoring is improving execution speed or at the very least, ensuring that it doesn’t degrade.
When Do We Refactor in Higson?
Refactoring isn’t a one-time event. It’s a continuous process woven into the project’s lifecycle.
- During Code Reviews: Every code review presents a valuable opportunity to identify areas for improvement. Catching unclear logic, repeated code blocks, or structural issues at this stage allows for early corrections. Time spent on code reviews is never wasted. It’s an investment that pays off quickly.
- As a regular Part of Sprint Scope: In a mature project like Higson, allocating time for refactoring is a standard part of sprint planning. This approach enables continuous improvement of code quality without postponing these tasks for a “better time” - which may never come.
- Before Adding New Functionality: Before starting the implementation of a new feature, it's worth taking a closer look at the existing code. Refactoring can simplify the current implementation and make it more extensible, which in turn makes the development of new functionality easier and faster.
- When a Bug Occurs...: While fixing a bug, we often come across code that either caused the issue or made it harder to locate. That’s the perfect time not only to resolve the defect, but also to consider how refactoring that part of the code could help prevent similar problems in the future. Even if immediate refactoring isn't possible, it's worth marking the area for future improvement.
Refactoring Done Right: Practical Guidelines
Successful refactoring in the Higson project is guided by a few time-tested principles:
- Small steps, frequent tests:
Refactor in small, manageable increments. After each change, run the tests to ensure that the external behavior of the code remains unchanged. Naturally, this only works if you have a solid test suite in place 😉.
- Planned Actions:
It’s important to identify which parts of the code need improvement and define which specific refactoring techniques will be applied. A structured plan helps ensure the work is purposeful and efficient.
- Discuss Major Changes as a Team:
When planning larger refactoring efforts, it's essential to involve the team in the discussion. Architecture meetings during each sprint provide a space to exchange ideas, consider different perspectives, and agree on the best approach forward.
- Know When to Stop:
Refactoring is not a goal in itself. It’s important to have clear criteria for when a piece of code is “good enough” and avoid falling into the trap of perfectionism which can lead to endless tweaking and unnecessary changes.
- Don’t Leave Commented-Out Code Behind
Commented-out code clutters the codebase and makes it harder to understand. Version control systems track the full history, so there’s no need to preserve unused code in the main repository.
- Use Dedicated Commits for Refactoring
It’s a good practice to separate refactoring changes from commits that introduce new features or fix bugs. This makes it easier to track changes and roll back specific operations if needed.
- Refactor When It Makes Sense
Not every part of the code needs constant refactoring. Focus on areas that are frequently modified, problematic, or blocking further development. There will be times when more effort is needed, for example, before a major architectural change or a dependency upgrade.
- Code Is Never Perfect, It Evolves
Accept that code will never be perfect. It will need to evolve as the project grows and requirements change. The only constant is change.
- Dependency Updates Often Require Refactoring
Upgrading libraries isn’t just about getting access to new features it’s often a security requirement. New versions may fix vulnerabilities, but they can also introduce breaking changes. In such cases, refactoring becomes a necessary part of the upgrade process.
- Refactor Tests Too
Just like production code, test code can become complex and difficult to maintain. Refactoring tests is just important, it improves clarity, reliability, and maintainability.
When Not to Refactor in - Higson Case
There are also situations where refactoring may be unnecessary or even harmful:
- Right Before a Release Deadline:
When a release deadline is approaching, performing large-scale refactoring can introduce unnecessary risk and potentially delay delivery. In such cases, it’s better to focus on shipping a working product and revisit refactoring once things have stabilized.
- When You Don’t Fully Understand the Code You’re Changing:
Refactoring code without understanding how it works is risky and can easily introduce new bugs. Before making any changes, make sure you have a solid grasp of the logic and behavior of the code in question.
- When Fixing a Bug, Don’t Add New Features at the Same Time:
Bug fixes should be handled separately from refactoring or adding new functionality. Mixing these tasks makes it harder to track changes and increases the risk of introducing new issues.
- Code Without Tests:
Refactoring code that isn’t covered by automated tests is like walking a tightrope without a safety net. Without tests, there’s no reliable way to verify that your changes haven’t broken existing functionality.
- Stable, Critical Code That Rarely Changes:
If a piece of code is stable, critical to the system's operation, and rarely modified, refactoring it may introduce unnecessary risk. In such cases, it’s often best to leave it alone unless there’s a compelling reason to act, such as severe readability issues or performance bottlenecks.
- In Newly Formed Teams with Diverse Coding Habits:
In newly formed teams, where each developer may bring different coding styles and practices, enforcing strict refactoring rules too early can lead to friction. It’s better to first establish shared coding standards then use refactoring as a tool to reinforce and apply those standards.
Refactoring Beyond Production Code
It’s important to remember that refactoring isn’t limited to production code alone.
In the Higson, it also applies to other key areas:
- Documentation:
Outdated, incomplete, or unclear documentation can be just as problematic as poorly written code. Refactoring documentation means updating it, improving its structure, simplifying the language, and ensuring it’s accessible and easy to understand.
- Pipelines and Processes:
Build, test, and deployment processes can also become inefficient over time. Refactoring CI/CD pipelines can speed up these workflows, improve reliability, and make monitoring easier. Similarly, refactoring other processes such as development workflows or project management routines can enhance the overall efficiency of the team.
- Tests:
As mentioned earlier, test code also deserves attention. Refactoring tests improves their readability, maintainability, and effectiveness in catching bugs.
- UI/UX: The user interface also evolves over time. Refactoring UI/UX can improve usability, accessibility, and visual appeal all of which contribute to a better user experience.
- Infrastructure and Environments:
The configuration of development, testing, and production environments may also require refactoring to improve stability, scalability, and security. Automating infrastructure provisioning through Infrastructure as Code (IaC) is a prime example of refactoring in this area.
Just like with code, refactoring in other areas should be done gradually in small, controlled steps, with caution and the ability to easily revert changes if problems arise.
In each of these cases, the core idea remains the same: improving the internal structure and organization of a given component without altering its external behavior or functionality. The goal is to make it more understandable, easier to maintain, and more efficient.
Conclusion
In summary, refactoring is an essential part of maintaining the health and longevity of the Higson. Regular, well-considered refactoring efforts help preserve code quality, support ongoing development, and minimize technical debt.
By keeping the goals and best practices of refactoring in mind, the Higson team can continue to grow and evolve a mature, market-proven product and sleep soundly at night, without fearing unexpected support incidents.