Refactoring: Part 2 (Techniques for Cleanup the Smells)

Refactoring: Part 2 (Techniques for Cleanup the Smells)


In our previous part (Part 1), we talked about what is refactoring and what is not, motivation behind this and identify the common code smells we generally found our codebase. In this part we are going to explore some well known techniques which can help us to cleanup the code smells. So lets start...


Techniques for Cleanup Code Smells:

Extract Method: Move a block of code into a new method with a meaningful name.

Before:

def process_order(order):
    print("Validating...")
    print("Calculating total...")        

After:

def process_order(order):
    validate_order()
    calculate_total()

def validate_order():
    print("Validating...")

def calculate_total():
    print("Calculating total...")        

Inline Method: Replace a simple method call with its contents directly in the caller.

Before:

def get_discount(price):
    return calculate_discount(price)

def calculate_discount(price):
    return price * 0.1        

After:

def get_discount(price):
    return price * 0.1        

Extract Variable: Create a variable for an expression to make it clearer.

Before:

if order.total_price() > 1000:
    print("High-value order")        

After:

total = order.total_price()
if total > 1000:
    print("High-value order")        

Inline Variable: Replace a variable with its value directly in the code.

Before:

discount = order.get_discount()
final_price = price - discount        

After:

final_price = price - order.get_discount()        

Split Variable: Use separate variables for different purposes instead of reusing one.

Before:

temp = 5
temp = temp + 10        

After:

base = 5
result = base + 10        

Rename Variable/Method/Class: Change the name to make its purpose clearer.

Before:

def calc(x, y):
    return x + y        

After:

def calculate_sum(first_number, second_number):
    return first_number + second_number        

Replace Nested Conditionals: Replace complex if-else or switch with simpler methods or polymorphism.

Before:

if user:
    if user.is_active:
        print("Welcome!")        

After:

if user and user.is_active:
    print("Welcome!")        

Decompose Conditional: Break down long conditionals into separate methods.

Before:

if user.age > 18 and user.has_license:
    print("Eligible to drive")        

After:

def is_eligible(user):
    return user.age > 18 and user.has_license

if is_eligible(user):
    print("Eligible to drive")        

Consolidate Duplicate Conditional Fragments: Combine duplicate logic within conditionals into one place.

Before:

if user.is_admin:
    print("Access granted")
if user.is_manager:
    print("Access granted")        

After:

if user.is_admin or user.is_manager:
    print("Access granted")        

Replace Magic Numbers: Replace hardcoded values with constants.

Before:

if account.balance > 100000:
    print("VIP customer")        

After:

VIP_THRESHOLD = 100000
if account.balance > VIP_THRESHOLD:
    print("VIP customer")        

Simplify Boolean Expressions: Refactor complex boolean logic into smaller, meaningful expressions.

Before:

if not (user.age < 18):
    print("Adult")        

After:

if user.age >= 18:
    print("Adult")        

Move Method: Move a method to a more relevant class.

Before:

class Order:
    def get_customer_email(self, customer):
        return customer.email        

After:

class Customer:
    def get_email(self):
        return self.email        

Move Field: Shift a field to a class where it makes more sense.

Before:

class Order:
    def __init__(self):
        self.customer_name = ""        

After:

class Customer:
    def __init__(self):
        self.name = ""        

Extract Class: Split a class into two when it does too much.

Before:

class Person:
    def __init__(self, name, street, city):
        self.name = name
        self.street = street
        self.city = city        

After:

class Person:
    def __init__(self, name, address):
        self.name = name
        self.address = address

class Address:
    def __init__(self, street, city):
        self.street = street
        self.city = city        

Inline Class: Merge a small, unnecessary class into another.

Before:

class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()        

After:

class Car:
    def start_engine(self):
        print("Engine started")        

Replace Type Code with Class/Enum: Replace a numeric or string type with a class or enumeration.

Before:

status = "active"        

After:

from enum import Enum

class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"        

Replace Delegation with Inheritance: Use inheritance instead of delegating behavior.

Before:

class Printer:
    def print(self):
        print("Printing...")

class AdvancedPrinter:
    def __init__(self):
        self.printer = Printer()

    def print(self):
        self.printer.print()        

After:

class AdvancedPrinter(Printer):
    pass        

Parameterize Method: Replace similar methods with one that uses parameters.

Before:

def add_tax(price):
    return price * 1.1

def add_discount(price):
    return price * 0.9        

After:

def adjust_price(price, factor):
    return price * factor        

Remove Parameter: Eliminate unnecessary parameters in a method.

Before:

def greet_user(user, time_of_day):
    if time_of_day:
        print(f"Good {time_of_day}, {user.name}")
    else:
        print(f"Hello, {user.name}")        

After:

def greet_user(user):
    print(f"Hello, {user.name}")        

Introduce Parameter Object: Group related parameters into a new object.

Before:

def create_order(customer_name, customer_address):
    pass        

After:

class Customer:
    def __init__(self, name, address):
        self.name = name
        self.address = address

def create_order(customer):
    pass        

Preserve Whole Object: Pass an object instead of its individual fields as parameters.

Before:

def display_customer(name, address):
    print(f"Name: {name}, Address: {address}")        

After:

def display_customer(customer):
    print(f"Name: {customer.name}, Address: {customer.address}")        

Replace Method with Method Object: Turn a complex method into an object for better organization.

Before:

def calculate():
    pass        

After:

class Calculator:
    def calculate(self):
        pass        

Encapsulate Field: Use getter and setter methods for direct field access.

Before:

class User:
    age = 25        

After:

class User:
    def __init__(self, age):
        self._age = age

    def get_age(self):
        return self._age        

Replace Array with Object: Use an object to make array data more meaningful.

Before:

student = ["John", 25]        

After:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age        

Duplicate Observed Data: Keep copies of dependent data to reduce dependencies.

Before:

class Product:
    def __init__(self, price):
        self.price = price
    def update_price(self, price):
        self.price = price

class Display:
    def show_price(self, product):
        print(f"Price: {product.price}")        

After:

class Product:
    def __init__(self, price):
        self._price = price
        self.observers = []

    def update_price(self, price):
        self._price = price
        self.notify_observers()

    def notify_observers(self):
        for observer in self.observers:
            observer.update(self)

class Display:
    def update(self, product):
        self.price = product._price
        print(f"Updated Price: {self.price}")        

Change Bidirectional Association to Unidirectional: Simplify relationships between classes.

Before:

class Student:
    def __init__(self, name):
        self.name = name
        self.courses = []

class Course:
    def __init__(self, title):
        self.title = title
        self.students = []        

After:

class Student:
    def __init__(self, name):
        self.name = name
        self.courses = []

class Course:
    def __init__(self, title):
        self.title = title
# Remove bidirectional link; only student knows their courses        

Introduce Null Object: Replace null with a default object to avoid checks.

Before:

if user is None:
    user = "Guest"        

After:

class GuestUser:
    name = "Guest"
user = user or GuestUser()        

Replace Conditional with Polymorphism: Replace conditionals with subclasses or methods for specific behaviors.

Before:

if shape == "circle":
    area = 3.14 * radius ** 2
elif shape == "rectangle":
    area = length * width        

After:

class Shape:
    def get_area(self):
        pass

class Circle(Shape):
    def get_area(self):
        return 3.14 * radius ** 2

class Rectangle(Shape):
    def get_area(self):
        return length * width        

Replace Constructor with Factory Method: Use a factory method for creating objects.

Before:

class Car:
    def __init__(self, model, engine_type):
        self.model = model
        self.engine_type = engine_type        

After:

class Car:
    def __init__(self, model, engine_type):
        self.model = model
        self.engine_type = engine_type

    @staticmethod
    def create_electric_car(model):
        return Car(model, "electric")

    @staticmethod
    def create_gas_car(model):
        return Car(model, "gas")        

Introduce Explaining Variable: Use variables with descriptive names to clarify logic.

Before:

if (order.total_price() > 1000 and order.customer_age() > 60):
    print("Eligible for senior discount")        

After:

is_high_value_order = order.total_price() > 1000
is_senior_customer = order.customer_age() > 60

if is_high_value_order and is_senior_customer:
    print("Eligible for senior discount")        

Remove Dead Code: Delete unused methods or fields.

Before:

def unused_function():
    pass        

After:

# totally remove the unused code        

Consolidate Duplicate Code: Merge repeated code into one method.

Before:

if user.is_admin:
    print("Welcome, Admin!")
    log_access("Admin")

if user.is_manager:
    print("Welcome, Manager!")
    log_access("Manager")        

After:

if user.is_admin or user.is_manager:
    role = "Admin" if user.is_admin else "Manager"
    print(f"Welcome, {role}!")
    log_access(role)        

Replace Inline Code with Function: Extract repetitive code into reusable functions.

Before:

price_with_tax = price + (price * 0.1)
discounted_price = price - (price * 0.2)        

After:

def add_tax(price):
    return price + (price * 0.1)

def apply_discount(price):
    return price - (price * 0.2)

price_with_tax = add_tax(price)
discounted_price = apply_discount(price)        

Replace Loop with Pipeline: Use functional methods like map, filter, or reduce instead of loops.

Before:

squared = []
for num in numbers:
    if num % 2 == 0:
        squared.append(num ** 2)        

After:

squared = [num ** 2 for num in numbers if num % 2 == 0]        

Split Large Classes: Break a class into smaller ones for single responsibility.

Before:

class Library:
    def __init__(self):
        self.books = []
        self.members = []

    def add_book(self, book):
        self.books.append(book)

    def add_member(self, member):
        self.members.append(member)

    def issue_book(self, member, book):
        pass

    def calculate_fine(self, member):
        pass        

After:

class BookCollection:
    def __init__(self):
        self.books = []

    def add_book(self, book):
        self.books.append(book)

class Membership:
    def __init__(self):
        self.members = []

    def add_member(self, member):
        self.members.append(member)        

Previous Part:

Refactoring: Part 1 (Principle, What It Is/Not and Identify the Smells First)


References:

  1. Book - Refactoring by Martin Fowler
  2. Blog - Refactoring.guru

To view or add a comment, sign in

Explore topics