4 Smart Ways to Avoid Pitfall of Too Many Method Parameters


Developers often underestimate methods.

Why? Because they are an integral part of C#, and you can reuse them.

However, sometimes, they require variables/values passed as parameters to perform the dedicated task. As a C# programmer, you must have faced at least one instance where creating a long list of parameters was inevitable.

A long parameter list leads to confusion and unnecessary complexities when extending the method or modifying the code’s functionality. It also leads to code smells, which indicate a deeper problem.

This article will explain how to avoid the pitfall of too many method parameters, with code examples so you can create clean code.

What are the disadvantages of having too many method parameters?

A long parameter list is never advantageous because it leads to contradictions and code duplication. To better explain what I mean, let’s see this in action:

This is a class facilitating gift purchases, so it requires the information of both the sender and receiver. As a result, it takes an unnaturally long parameters list.

class Gifts
{
    static void GiftPurchase(string Name,
                             string Email,
                             string Address,
                             string CardType,
                             int AccountNumber,
                             string ReceipientAddress,
                             string ReceipientName,
                             string Message)
    {

    }
}

Apart from being an eye sore, it also has other problems. These are:

Typing in a long parameter list at each method call

You need to enter eight different parameters repeatedly every time you decide to call this method. It is time inefficient and will also add long lists throughout your code, making it a mess.

Adding a new parameter becomes too difficult

With such a long list of parameters, adding a new parameter becomes a demanding task if your code is lengthy. Why?

Because once you add a new parameter, you will need to update it everywhere in the calls.

This means skimming through the code, finding the method calls, and making the updates. You may miss an instance or two, which will immediately cause errors. So, now, you will spend some more time correcting those errors. In short, it’s a highly inefficient process.

Adding a new parameter may cause conflicts and confusion

Suppose another person comes in and decides to add state information by creating the parameter string State.

This new parameter can easily confuse other programmers. Does he mean State = Texas?

If yes, is it the State of the sender or the receiver of the gift?

Or is it State = Damaged, the state the gift is in?

Since both are string variables, the other programmers can pass incorrect information, and the compiler will raise no alarm.

May lead to merge conflicts

If you work in a team setup, merge conflicts can quickly occur due to long parameter lists. For instance, Person A adds a new parameter, bool GiftPriority, and at the same time, Person B introduces bool CouponAvailed.

Now they merge their branches into the main branch. For the code to work correctly, the order in which they appear in the main branch is critical. For example, Person B may be passing 1 for their variable CouponAvailed but ends up setting GiftPriority to 1 and vice versa.

Therefore, it is crucial to have concise parameter lists which can be dealt with quickly.

How many method parameters are too many?

To reduce the number of method parameters, you first need to identify when your list is problematic.

According to Clean Code by Robert C. Martin,

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification – and then shouldn’t be used anyway.”

On the other hand, Steve McConnell’s Code Complete says to “limit the number of a routine’s parameters to about seven because seven is a magic number for people’s comprehension.”

Although using too many method parameters will never cause an error, a smaller number of parameters should be preferred. Combining the advice by Martin and McConnell, you should consider refactoring if you have four parameters and definitely refactor if you have five or more method parameters.

How to reduce the number of method parameters?

To reduce the number of method parameters, you can use one of the four different ways to make your code look structured and readable:

  1. Introduce Parameter Object refactoring
  2. Preserve Whole Object refactoring
  3. Replace Parameter with Method refactoring
  4. Use Extract Method refactoring to make the method smaller

Let’s look at these techniques in detail.

1. Introduce Parameter Object refactoring

When you want a smaller method signature without fewer parameters, use Introduce Parameter Object refactoring. With this technique, you can also make your code flexible as the method signature stays the same even if you add a new parameter or decide to expand the code.

This refactoring technique involves encapsulating the long list of method parameters into a single class, which you can then pass to your method. This way, the code immediately looks cleaner, and you can easily update the parameter list without making lengthy updates throughout your code.

Let’s see how we can implement this refactoring. We have a method called SaveHomeAddress() which saves a person’s information into a file.

public void SaveHomeAddress(string name,
                            string homeAddress,
                            string country,
                            string email,
                            string fileLocation)
{

    if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(homeAddress)
        || string.IsNullOrEmpty(fileLocation))
    {
        Console.WriteLine("Input parameters are empty");
    }
    else
    {
        using FileStream fileStream = new FileStream(fileLocation, FileMode.Append);
        using StreamWriter writer = new StreamWriter(fileStream);
        List<string> aPersonRecord = new List<string>
    {
        name,
        homeAddress,
        country,
        email
    };
        writer.WriteLine(aPersonRecord);
    }
}

As you can see, the method has an unacceptable number of parameters, but reducing them would mean that the logic isn’t implementable. This is where Introduce Parameter Object refactoring comes in handy.

You can begin by creating a new class AddressDetails and pass this as a parameter to the SaveHomeAddress method:

public class AddressDetails
{
    public string Name { get; set; }
    public string HomeAddress { get; set; }
    public string Country { get; set; }
    public string Email { get; set; }
    public string FileLocation { get; set; }
}

Then replace the list with AddressDetails and the parameters inside the method one by one. For instance, HomeAddress becomes AddressDetails.HomeAddress.

The final look of the method is as follows:

public void SaveHomeAddress(AddressDetails addressDetails)
        {

    if (string.IsNullOrEmpty(addressDetails.Name) || string.IsNullOrEmpty(addressDetails.HomeAddress)
        || string.IsNullOrEmpty(addressDetails.FileLocation))
    {
        Console.WriteLine("Input parameters are empty");
    }
    else
    {
        using FileStream fileStream = new FileStream(addressDetails.FileLocation, FileMode.Append);
        using StreamWriter writer = new StreamWriter(fileStream);
        List<string> aPersonRecord = new List<string>
        {
            addressDetails.Name,
            addressDetails.HomeAddress,
            addressDetails.Country,
            addressDetails.Email
        };
        writer.WriteLine(aPersonRecord);
    }
}

For more details about Introduce Parameter Object refactoring and a step-by-step tutorial on this example, click here.

2. Preserve Whole Object refactoring

While working with subroutines, you pass the data from the calling method to the subroutine as variables. The concept behind Preserve Whole Object Refactoring is that instead of passing information from an object as a parameter, you can pass the object itself.

This way, the method signature becomes smaller, and you can extract any information from the subroutine you require. For instance, in a method that calculates the distance traveled by a vehicle, you might need information about the engine apart from “speed” and “time.”

If you have passed only speed and time as parameters, you will need to update the parameter list, and the method calls everywhere in your code for “engine size.” However, if you have passed the entire object “TravelData” as a parameter, you can extract whatever information you require.

Suppose a company gives a bonus to its employees based on the Sales they have made during the month. There are several thresholds of sales to identify which bonus percentage should be given. The implementation would look something like this:

public double CalculateBonus(Sales sales, EmployeeSalary salary)
{
    int percentage = CalculatePercentageOnSales(sales.SalesMade, sales.EmployeeRecord);
    return salary.Salary * percentage;
}

public int CalculatePercentageOnSales(int salesMade, string employeeRecord)
{
    //some logic
}

Here, the method CalculatePercentageOnSales decide which bonus percentage the employee should get based on his current sales and previous record.

This object requires two parameters, salesMade and employeeRecord. However, if you pass the Sales object, we can get any information present in the object.

It would look something like this:

public double CalculateBonus(Sales sales, EmployeeSalary salary)
{
    int percentage = CalculatePercentageOnSales(sales);
    return salary.Salary * percentage;
}

public int CalculatePercentageOnSales(Sales sales)
{

    //some logic
}

3. Replace Parameter with Method Refactoring

Sometimes, a method requires values from another method. In such cases, you call the method, and the results from that method are given as parameters to the second method. This often creates a long loop of calling methods and passing them, which are hard to follow and track.

The Replace Parameter with Method refactoring resolves this problem by allowing you to shift the method calls inside the calling method. It argues that instead of calling a parameter obtained from another object, you can call that method inside your calling method.

Consider a code that creates a new user account by passing information in different modules, such as Billing Info, Personal Info, and Previous Account information, as parameters:

string CreateAccount = this.CreateAccount();
string UpdatePersonalInfo = this.GetPersonalInfo();
string UpdateBillingInfo = this.GetBillingInfo();
bool PreviousAccountExists = this.GetPreviousAccounts();

string User = AccountCreation(CreateAccount, UpdatePersonalInfo, UpdateBillingInfo, PreviousAccountExists);

It uses the information of previous accounts to connect both accounts and then creates a new account. However, instead of passing these results as parameters, you can call them inside the method AccountCreation while implementing their logic.

This will make your method signature smaller as fewer or no parameters will be used.

string User = AccountCreation(CreateAccount);

Here only CreateAccount goes in as a parameter, while others are called while implementing the logic inside the method.

4. Use the Extract Method to make the method smaller

Extract Method refactoring refers to the technique where a smaller method is extracted from a larger method and called from the original method. The primary goal of this technique isn’t to reduce the number of method parameters.

It is to make the method itself smaller.

However, the benefit of doing that is that you end up with small methods that don’t need as many method parameters as the giant methods need.

The Extract Method breaks your code into smaller modules, making it more readable and editable.

Consider a method that processes an order by a customer.

public void ProcessOrder(Order order)
{
    // Validate customer information
    if (order.Customer.IsValid)
    {
        // Calculate order total
        decimal orderTotal = 0;
        foreach (var item in order.Items)
        {
            orderTotal += item.Price;
        }

        // Check inventory and reduce stock levels
        foreach (var item in order.Items)
        {
            var inventoryItem = GetInventory(item.ProductId);
            if (inventoryItem.StockLevel >= item.Quantity)
            {
                inventoryItem.StockLevel -= item.Quantity;
            }
            else
            {
                throw new Exception("Not enough stock for product: " + item.ProductId);
            }
        }

        // Save order to database
        SaveOrder(order);

        // Send confirmation email
        SendConfirmationEmail(order.Customer.Email, orderTotal);
    }
    else
    {
        throw new Exception("Invalid customer information");
    }
}

Using the Extract Method refactoring several times, you end up with the following code.

public void ProcessOrder(Order order)
{
    ValidateCustomer(order);

    decimal orderTotal = CalculateOrderTotal(order);

    CheckInventoryAndReduceStock(order);
    SaveOrder(order);
    SendConfirmationEmail(order.Customer.Email, orderTotal);
}

private static void ValidateCustomer(Order order)
{
    if (!order.Customer.IsValid)
    {
        throw new Exception("Invalid customer information");
    }
}

private static decimal CalculateOrderTotal(Order order)
{
    decimal orderTotal = 0;
    foreach (var item in order.Items)
    {
        orderTotal += item.Price;
    }

    return orderTotal;
}

private void CheckInventoryAndReduceStock(Order order)
{
    foreach (var item in order.Items)
    {
        var inventoryItem = GetInventory(item.ProductId);
        if (inventoryItem.StockLevel >= item.Quantity)
        {
            inventoryItem.StockLevel -= item.Quantity;
        }
        else
        {
            throw new Exception("Not enough stock for product: " + item.ProductId);
        }
    }
}

n this example, you refactor the original ProcessOrder method by extracting code blocks to separate methods. As a result, the code is easier to read, understand and maintain. That’s because each method is focused on doing a single task.

Finally, you can reuse some of the new methods. Or you can move those methods to more appropriate classes.

If you need to learn and use one refactoring, use Extract Method. This article provides more information about it.

How to reduce the number of constructor parameters

Constructors are specialized methods that initialize objects. They are created automatically whenever you call an object and have no return type.

So, while talking about methods, constructors are highly relevant. There are instances where you may face the issue of too many parameters in a constructor too. Here’s how you can resolve them:

  1. Split the class into multiple classes
  2. Use the Builder design pattern
  3. Introduce Parameter Object
  4. Use the Facade design pattern

Read these 4 Strategies to deal with too many constructor parameters for more details.

Conclusion

Having too many method parameters makes the code unreadable, difficult to edit, and creates confusion and contradictions. Therefore, it is necessary to make the method signature smaller.

However, it is not always possible to cut down the parameter list. In such cases, you can use the following refactoring techniques:

  1. Group the parameters into a class and then pass that class as a method parameter.
  2. Instead of passing values of an object, pass the entire object to the calling method for flexibility and to make future modifications easier.
  3. Instead of calling the methods as parameters, move them inside the calling method while implementing their logic.
  4. Extract relevant logic to make smaller methods from a larger method, and pass them as parameters.

Recent Posts