Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 49

Foundational Refactoring Techniques

Recommended resources

Hello! In our last lesson, we learned to identify 'code smells'โ€”the symptoms of deeper design problems like Long Method and Large Class. Recognizing a problem is the first step, but a true architect knows how to fix it.

This lesson is about the cure: refactoring. Refactoring is the disciplined process of restructuring existing computer codeโ€”changing the factoringโ€”without changing its external behavior. It's how you transform a smelly, brittle codebase into one that is clean, maintainable, and a pleasure to work with.

Your goal is to apply foundational refactoring techniques (e.g., Extract Method) to improve code quality. We will focus on a small set of powerful techniques that provide the most leverage for cleaning up common smells.

1. The Golden Rule of Refactoring

Before you change a single line of code, there is one absolute, non-negotiable rule: make sure you have a solid set of automated tests.

Refactoring without tests is not refactoring; it's just "changing stuff." Tests are your safety net. They are the only way to verify that you have preserved the code's behavior after restructuring it. The process often follows the "Red-Green-Refactor" cycle you may have heard of from Test-Driven Development (TDD):

  1. Red: Write a failing test.
  2. Green: Write the simplest code to make the test pass.
  3. Refactor: Clean up the code you just wrote, relying on the test to ensure you don't break anything.

Our focus today is on the "Refactor" step, assuming the tests are already in place.


2. The Workhorse: Extract Method

The single most common and useful refactoring technique is Extract Method. It is the primary tool for fixing the Long Method smell we discussed previously.

The idea is simple: if you can find a group of lines in a method that can be explained with a single, clear purpose, you should move that code into a new method with a name that explains that purpose.

12 Essential Code Refactoring Techniques for Clean Code

The article '12 Essential Code Refactoring Techniques' provides a concise guide for this technique. Let's start here for a formal definition and step-by-step process.

Read the section '1. Extract Method: Break Down Complex Functions'. Focus on the 'Why it works' and the 'Steps'. Notice how the 'After' example is self-documenting; the method names describe the workflow.

Now, let's see this in action. Theory is one thing, but watching a skilled developer apply it is another.

5 Awesome Refactoring Tips To Clean Up Your Code

This video by Milan Jovanoviฤ‡ provides an excellent, practical demonstration of refactoring a messy method. We'll watch the first part where he applies 'Extract Method' to the validation logic.

Watch from 00:58 to 03:41. First, the presenter walks through the very long AddCustomer method, pointing out the different logical blocks (validation, calculation, etc.). Then, he applies the 'Extract Method' refactoring to pull the validation logic into its own IsValid method.

As you saw, the AddCustomer method immediately becomes easier to read. Instead of a large block of if statements, there is a single call: if (!IsValid(...)). The main method now describes what it does at a higher level, delegating the how to the new, specialized method.

This image provides a great visual summary of Extract Method and its inverse, Inline Method.

Refactoring Techniques: Composing Methods
This image visualizes four common techniques for composing methods. 'Extract Method' (top left) is the process of taking a block of code and moving it into a new, descriptively named method, which we just discussed.

3. A Foundational Refactoring Toolkit

Beyond Extract Method, a few other techniques form the core of a developer's refactoring toolkit. We'll use the "12 Essential Code Refactoring Techniques" article as our guide.

A. Inline Method

This is the opposite of Extract Method. You use it when a method's body is just as clear as its name. Unnecessary indirection can sometimes make code harder to follow, and inlining the method can remove that cognitive overhead. Look at the Inline Method example in the image above to see how it works.

B. Move Method

This technique is the primary cure for the Feature Envy code smellโ€”when a method in one class seems more interested in the data of another class than its own. The goal is to improve cohesion by placing behavior close to the data it operates on.

12 Essential Code Refactoring Techniques for Clean Code

Let's return to our guide to see an example.

Read section '4. Move Method: Relocate Behavior to Its Natural Home'. The example shows a CalculateShippingCost method on an Invoice class that really belongs on the Customer class because it primarily uses Customer data. Moving it makes the design more logical.

C. Replace Temp with Query

Temporary variables within a method can often make it longer and harder to understand. This technique replaces a temp variable with a method call that calculates the value. This makes the logic reusable and isolates the calculation.

12 Essential Code Refactoring Techniques for Clean Code

The guide has another clear example for this.

Read section '3. Replace Temp with Query: Eliminate Unnecessary State'. Notice how the loyaltyMultiplier temporary variable is extracted into a getLoyaltyMultiplier() method. This not only cleans up the calculateDiscount method but also makes the loyalty logic available to other parts of the class.

D. Replace Magic Number with Symbolic Constant

This is a simple but incredibly effective refactoring for improving readability and maintainability. Any hard-coded number that has a specific business meaning (a "magic number") should be replaced with a named constant.

12 Essential Code Refactoring Techniques for Clean Code

One last technique from our guide.

Read section '6. Replace Magic Number with Symbolic Constant'. The example of replacing 1800000 with SESSION_TIMEOUT_MS is a perfect illustration. The intent of the code becomes immediately clear.


4. Refactoring in Practice: Combining Techniques

Refactoring is rarely a single step. You often apply a series of small, safe transformations to gradually improve the code's design. Let's go back to the video and see how multiple techniques are combined to clean up the AddCustomer method further.

5 Awesome Refactoring Tips To Clean Up Your Code

We'll now watch a longer segment where the presenter tackles the credit limit logic. He applies 'Extract Method', then 'Extract Class' (a larger refactoring that greatly improves Single Responsibility), and finally replaces magic strings with a safer enum type.

Watch from 07:52 to 15:41. Pay attention to the thought process: He identifies that credit limit calculation is a separate responsibility (07:52). He first uses Extract Method to isolate the logic (08:20). He then realizes this logic doesn't belong in the CustomerService at all and uses Extract Class to create a new CreditLimitCalculator (10:45). This is a direct application of the Single Responsibility Principle. Finally, he improves the new class by replacing hard-coded strings like "ImportantClient" with an enum (14:50), which is a form of 'Replace Magic Number/String with Symbolic Constant'.

After all these changes, notice that the final AddCustomer method is short, clear, and easy to understand. It orchestrates calls to other, more specialized objects. This is the "architectural mindset" in action: identifying responsibilities and structuring code to reflect them clearly. The final check, of course, is running the unit tests to ensure no behavior was broken during this extensive refactoring.

Test your understanding!

You are reviewing the following method in a Python-based e-commerce system:

class ReportGenerator:
    def generate_sales_report(self, orders, start_date, end_date):
        # Filter orders by date range
        filtered_orders = []
        for order in orders:
            if start_date <= order.date <= end_date:
                filtered_orders.append(order)

        # Calculate total revenue
        total_revenue = 0
        for order in filtered_orders:
            base_price = order.subtotal
            # Apply 15% discount for members
            if order.customer.is_member:
                base_price = base_price * 0.85
            total_revenue += base_price

        # Format the report
        header = f"Sales Report: {start_date} to {end_date}\n"
        revenue_line = f"Total Revenue: ${total_revenue:.2f}\n"
        
        return header + revenue_line

This method suffers from the Long Method smell. Describe at least two foundational refactoring techniques you would apply to improve its quality, and explain what each refactoring would accomplish.

Show answer

Here are the most effective refactoring techniques to apply:

  1. Extract Method for filtering the orders. The first for loop, responsible for selecting orders within a date range, can be extracted into a new private method like _filter_orders_by_date(self, orders, start_date, end_date). This would make the main method's intent clearer by replacing the loop with a single, descriptive method call.

  2. Extract Method for calculating revenue. The second for loop, which calculates the total revenue and applies discounts, can be extracted into a new method like _calculate_total_revenue(self, orders). This separates the concern of calculation from filtering and formatting.

  3. Replace Magic Number with Symbolic Constant. The number 0.85 is a "magic number." It represents a 15% discount, but this isn't immediately obvious. It should be replaced with a named constant, like MEMBER_DISCOUNT_MULTIPLIER = 0.85. This makes the code more readable and easier to change if the discount rate ever needs to be updated.

By applying these refactorings, the generate_sales_report method would be transformed from a long, procedural script into a clean, high-level coordinator:

class ReportGenerator:
    MEMBER_DISCOUNT_MULTIPLIER = 0.85

    def _filter_orders_by_date(self, orders, start_date, end_date):
        # ... logic to filter orders ...

    def _calculate_total_revenue(self, orders):
        # ... logic to calculate revenue with discounts ...

    def generate_sales_report(self, orders, start_date, end_date):
        filtered_orders = self._filter_orders_by_date(orders, start_date, end_date)
        total_revenue = self._calculate_total_revenue(filtered_orders)
        
        # Format the report
        header = f"Sales Report: {start_date} to {end_date}\n"
        revenue_line = f"Total Revenue: ${total_revenue:.2f}\n"
        
        return header + revenue_line

(Further refactoring could even extract the formatting logic.)


Conclusion

In this lesson, you've learned how to move from identifying code smells to actively fixing them using foundational refactoring techniques. This is a skill that distinguishes senior engineers and architects: the ability to safely and systematically improve the design of an existing system.

Key Takeaways:

  • Refactoring requires a safety net. Automated tests are essential to ensure you don't introduce bugs.
  • Extract Method is your primary tool for breaking down long methods and adhering to the Single Responsibility Principle.
  • A core toolkit including Inline Method, Move Method, and Replace Temp with Query helps address other common design flaws like feature envy and unnecessary complexity.
  • Refactoring is an iterative process. Small, deliberate changes accumulate to create a significant improvement in code quality.

Preview of the Next Lesson:
So far in this module, we've covered the principles of good design (SOLID), how to spot bad design (code smells), and how to fix it (refactoring). Now you're ready to start designing clean systems from the ground up. In the next module, we will dive into Foundational Design Patterns, starting with creational patterns like Factory Method and Abstract Factory, which give you powerful strategies for creating objects in a flexible and decoupled way.