DEV Community

Fahad Ali Khan
Fahad Ali Khan

Posted on

Refactoring a C++ Project: A Journey of Improvement and Optimization

Refactoring code is a crucial part of software development. It helps to clean up the code, improve readability, and make it more maintainable. In my recent project, I went through a thorough refactoring process that included organizing global variables, removing dead code, creating a test function, configuring a namespace, and introducing an ApiClient class to handle API interactions. Here’s a breakdown of my journey, the challenges I faced, and the lessons I learned.

Step 1: Organizing Global Variables with Namespaces

One of my first goals was to manage a growing number of global variables more effectively. Initially, variables like model, output_name, and API credentials were scattered across multiple files, making it difficult to track their usage. To address this, I moved all global variables into a dedicated Config namespace. This not only reduced clutter but also improved the structure by grouping configuration-related variables.

What I Focused On:

  • I aimed to encapsulate configuration variables to avoid naming conflicts and enhance readability.
  • Using a namespace allowed me to centralize global variables, making it easier to modify them as the project grows.

Step 2: Removing Dead Code

As I went through the code, I noticed several sections that were either unused or redundant. Removing this dead code helped to reduce the overall complexity and made the remaining code more readable.

Improvements Made:

  • I carefully evaluated each piece of code to identify unused functions and variables.
  • By cleaning up these areas, I reduced potential confusion for future maintainers and made the codebase leaner.

Step 3: Creating a Test Function

To verify that my changes didn't break any functionality, I created a simple test function. This helped to ensure that the key features were still working as expected. Having this test function also provided a foundation for writing more comprehensive tests in the future.

Why It Was Important:

  • Testing while refactoring is essential to catch any inadvertent errors introduced during changes.
  • This initial test function allowed me to validate my refactoring efforts quickly.

Step 4: Refactoring API Interaction with ApiClient

One of the most significant changes I made was creating an ApiClient class to handle all API interactions. Initially, these interactions were embedded within the eng_format class, which violated the single responsibility principle. By separating the API logic into its own class, I improved modularity and made the code more manageable.

How I Improved the Code:

  • The new ApiClient class encapsulates all API-related logic, such as making requests and parsing responses.
  • This change made eng_format more focused, with clearer responsibilities, and improved overall code structure.

Step 5: Interactive Rebase with Git

After making several commits, I decided to clean up my commit history with an interactive rebase. This allowed me to combine related commits, remove unnecessary ones, and create a more logical commit history.

How the Rebase Went:

  • I used Git’s interactive rebase feature to squash minor commits into larger, meaningful ones.
  • It was a smooth process, and I found it satisfying to see a cleaner commit history. It was a bit intimidating at first, but I was careful with the process, and Git's interface made it straightforward.

Challenges and Bugs Discovered

While refactoring, I discovered a few issues and bugs:

  • API Key Issues: During testing, I found that the api_key was not being loaded correctly in some cases. I added error handling in the ApiClient class to catch and log this.
  • Breaking Changes: At one point, moving too many functions into the ApiClient class broke the code due to missing dependencies. I fixed this by ensuring that eng_format only called ApiClient for API-specific tasks, while retaining its own responsibilities.
  • Environment Variable Bugs: While refactoring, I found that some environment variables were not being set as expected. I resolved this by adding a dedicated function in the Config namespace to handle environment variables properly.

Lessons Learned

Refactoring can sometimes feel risky, especially when breaking up existing functionality. However, using Git effectively for history management and testing at every step made the process manageable. Here are some key takeaways:

  • Modularity Matters: By separating concerns with classes like ApiClient, I found that the code became easier to understand and maintain.
  • Namespaces Are Powerful: Grouping related global variables in a namespace was a small change that had a big impact on code clarity.
  • Testing Is Key: Writing even a basic test function saved me from potential bugs during refactoring.

In conclusion, this refactoring process allowed me to streamline my code, improve its structure, and make it more maintainable. Using Git for an interactive rebase helped to polish the commit history, creating a more readable project timeline. This experience reaffirmed the importance of refactoring as a practice and showed me how thoughtful improvements can make a big difference in the overall quality of code.

Top comments (0)