Post

🚫 5 Parameter-Related Code Smells and How to Refactor Them

In software development, the way you design and pass parameters can significantly impact code readability, maintainability, and robustness. Poor parameter design often leads to code smellsβ€”subtle indicators of deeper issues that can make your code harder to understand, test, and modify.

In this article, we’ll cover five common parameter-related code smells, analyze why they’re problematic, and demonstrate how to refactor them using best practices.


βœ… 1. Flag Parameters (Boolean Trap)

Smell:
A function has a boolean or flag parameter that controls branching logic, making the function do two different things. This reduces readability and violates the Single Responsibility Principle (SRP).

Example (Before):

1
2
3
4
def calculate_price(books, special_edition=False):
    if special_edition:
        return len(books) * 20  # Special edition price
    return len(books) * 10  # Regular price

🚫 Why it’s problematic:

  • The function does two things: handles both regular and special editions.
  • Harder to read and test.
  • Branching behavior is hidden inside the function.

Refactored Version:

1
2
3
4
5
def calculate_regular_price(books):
    return len(books) * 10

def calculate_special_edition_price(books):
    return len(books) * 20

βœ… Best practice:

  • Split into separate functions for each behavior.
  • Makes the code more modular and readable.
  • Easier to test and extend.

πŸ”₯ 2. Null-Driven Behavior

Smell:
A function’s behavior varies drastically depending on whether a parameter is null or not. This introduces hidden branching and makes the logic harder to follow.

Example (Before):

1
2
3
4
5
6
7
def process_order(order_id, user=None):
    if user:
        if not user.is_active:
            raise Exception("Inactive user.")
        print(f"Processing order {order_id} for {user.name}")
    else:
        raise Exception("User is required.")

🚫 Why it’s problematic:

  • The function performs two different things depending on whether user is null or not.
  • Hidden complexity inside the function.
  • Harder to maintain and debug.

Refactored Version (Separate Functions):

1
2
3
4
5
6
7
def process_order_with_user(order_id, user):
    if not user.is_active:
        raise Exception("Inactive user.")
    print(f"Processing order {order_id} for {user.name}")

def process_order_without_user(order_id):
    raise Exception("User is required.")

βœ… Best practice:

  • Separate functions for the two cases.
  • Clearer responsibilities.
  • Easier to test and maintain.

πŸš€ 3. Duplicate Information in Parameters

Smell:
When a new structured parameter is introduced (e.g., an object or DTO), but the function still accepts the old individual parameters, creating duplication and inconsistency.

Example (Before):

1
2
3
4
5
6
7
8
9
def calculate_price(copies=None, discount=None, purchases=None):
    if purchases:
        copies = purchases.copies
        discount = purchases.discount
    
    if copies is None or discount is None:
        raise ValueError("Missing required parameters.")
    
    return copies * (100 - discount) / 100

🚫 Why it’s problematic:

  • Duplicate information: copies and discount are passed both individually and via purchases.
  • Inconsistent and harder to maintain.
  • Unclear precedence when both are provided.

Refactored Version (Single Source of Truth):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import warnings

def calculate_price(purchases=None, copies=None, discount=None):
    if purchases:
        copies = purchases.copies
        discount = purchases.discount
    else:
        warnings.warn(
            "Passing copies and discount separately is deprecated. Use purchases instead.",
            DeprecationWarning
        )

    if copies is None or discount is None:
        raise ValueError("Missing required parameters.")

    return copies * (100 - discount) / 100

βœ… Best practice:

  • Prefer the new object (purchases) and use old parameters as a fallback.
  • Deprecate the old parameters gradually.
  • Eventually, remove the old version for cleaner code.

πŸ”₯ 4. Overloaded Parameters (Inconsistent Behavior)

Smell:
A function takes a parameter that accepts multiple types or formats, making its behavior inconsistent and unpredictable.

Example (Before):

1
2
3
4
5
6
7
def get_discount_rate(customer):
    if isinstance(customer, int):  # Customer ID
        return fetch_discount_by_id(customer)
    elif isinstance(customer, str):  # Customer name
        return fetch_discount_by_name(customer)
    else:
        raise ValueError("Invalid customer format.")

🚫 Why it’s problematic:

  • The function handles different types in one parameter.
  • Difficult to maintain and extend.
  • Error-prone and less predictable.

Refactored Version (Separate Functions):

1
2
3
4
5
def get_discount_by_id(customer_id):
    return fetch_discount_by_id(customer_id)

def get_discount_by_name(customer_name):
    return fetch_discount_by_name(customer_name)

βœ… Best practice:

  • Split into separate functions based on input type.
  • Clearer and easier to understand.
  • Reduces type-checking complexity.

πŸš€ 5. Long Parameter Lists

Smell:
Functions with many parameters (typically more than 3-4) become difficult to read, call, and maintain.

Example (Before):

1
2
3
4
5
6
7
8
9
10
def create_user(first_name, last_name, email, phone, age, city, country):
    return {
        "first_name": first_name,
        "last_name": last_name,
        "email": email,
        "phone": phone,
        "age": age,
        "city": city,
        "country": country
    }

🚫 Why it’s problematic:

  • Hard to read and understand.
  • Prone to errors when calling.
  • Difficult to extend without breaking the function signature.

Refactored Version (Use a DTO or Named Tuple):

1
2
3
4
5
6
from collections import namedtuple

User = namedtuple('User', ['first_name', 'last_name', 'email', 'phone', 'age', 'city', 'country'])

def create_user(user):
    return user._asdict()

βœ… Best practice:

  • Use a DTO or named tuple to group related parameters.
  • Clearer function signature.
  • Easier to extend and maintain.

🎯 Key Takeaways

  • Flag parameters β†’ Split into separate functions.
  • Null-driven behavior β†’ Use early returns or separate functions.
  • Duplicate parameters β†’ Prefer the new structured parameter and deprecate the old one.
  • Overloaded parameters β†’ Use distinct functions for different types.
  • Long parameter lists β†’ Refactor into DTOs or objects.

πŸ”₯ Final Thoughts

These parameter-related code smells may seem harmless initially, but they often lead to hard-to-maintain and error-prone code. By applying clean code principles and refactoring strategies, you can make your code more modular, readable, and extensible.

βœ… Refactoring these smells not only improves code quality but also makes it easier for your future self and teammates to understand and modify the code.


πŸ’¬ Do you have any specific parameter-related code smells in your project? Let me knowβ€”I can help you refactor them! πŸš€

This post is licensed under CC BY 4.0 by the author.