I have noticed a pattern when I refactor my code for readability. When I refactor a method into several smaller methods, I often have to introduce parameters which sometimes (too often) makes the code less readable than before. I am wondering if this indicates that I should refactor to a different class, instead of just smaller methods.
I would think this is a very common situation, but I don't recall reading anywhere how to handle it.
Her is a simple example of this from a made-up scenario.
Before refactoring
public class ATM
{
private double cashBalance = 5000;
public double WithDraw(CreditCard card, string pin, double amount)
{
if(card.Pin != pin)
throw new Exception("Wrong pin");
if(balance < amount)
throw new Exception("ATM out of money");
if(card.Balance < amount)
throw new Exception("Credit card out of money");
cashBalance -= amount;
return amount;
}
}
Right off the bat, this isn't too bad, but I want to remove the logic from my conditionals and give it proper names.
First refactoring
public class ATM
{
private double cashBalance = 5000;
public double WithDraw(CreditCard card, string pin, double amount)
{
Validate(card, pin, amount);
cashBalance -= amount;
return amount;
}
private void Validate(CreditCard card, string pin, double amount)
{
if(!VerifyPin(card, pin))
throw new Exception("Wrong pin");
if(!HasEnoughCashBalance(amount))
throw new Exception("ATM out of money");
if(!CardHasEnoughMoney(card, amount))
throw new Exception("Credit card out of money");
}
private bool VerifyPin(CreditCard card, string pin)
{
return card.Pin == pin;
}
private bool HasEnoughCashBalance(double amount)
{
return cashBalance >= amount;
}
private bool CardHasEnoughMoney(CreditCard card, double amount)
{
return card.Balance >= amount;
}
}
I separate validation to its own method, it then further refactors each conditional to its own method given it a more descriptive name. (note I havn't spend the time to come up with actually good names) This however, has introduced a lot of parameters, and while the WithDraw
method is more readable, the validation part isn't really.
This is the situation a notice again and again while refactoring methods into smaller methods.
Second refactoring
public class ATM
{
private double cashBalance = 5000;
private CreditCard card;
private string pin;
private double amount;
public double WithDraw(CreditCard card, string pin, double amount)
{
this.card = card;
this.pin = pin;
this.amount = amount;
Validate();
cashBalance -= amount;
return amount;
}
private void Validate()
{
if(!VerifyPin())
throw new Exception("Wrong pin");
if(!HasEnoughCashBalance())
throw new Exception("ATM out of money");
if(!CardHasEnoughMoney())
throw new Exception("Credit card out of money");
}
private bool VerifyPin()
{
return card.Pin == pin;
}
private bool HasEnoughCashBalance()
{
return cashBalance >= amount;
}
private bool CardHasEnoughMoney()
{
return card.Balance >= amount;
}
}
I sometimes end up with this fix, but I'm not a big fan of it. It feels borderline risky, or at the least doesn't feel cohesive.
Third refactoring
public class ATM
{
private double cashBalance = 5000;
public double WithDraw(CreditCard card, string pin, double amount)
{
var validator = new ATMValidator(cashBalance, card, pin, amount);
validator.Validate();
cashBalance -= amount;
return amount;
}
}
public class ATMValidator
{
private double amount;
private CreditCard card;
private string pin;
private double cashBalance;
public ATMValidator(double cashBalance, CreditCard card, string pin, double amount)
{
this.cashBalance = cashBalance;
this.card = card;
this.pin = pin;
this.amount = amount;
}
public void Validate()
{
if(!VerifyPin())
throw new Exception("Wrong pin");
if(!HasEnoughCashBalance())
throw new Exception("ATM out of money");
if(!CardHasEnoughMoney())
throw new Exception("Credit card out of money");
}
private bool VerifyPin()
{
return card.Pin == pin;
}
private bool HasEnoughCashBalance()
{
return cashBalance >= amount;
}
private bool CardHasEnoughMoney()
{
return card.Balance >= amount;
}
}
And this is where I end up, having a separate class. In this example it seems quite reasonable, and I would make an argument that withdrawal and validation are two separate responsibilities.
What I really want to know though, is if this pattern of sending parameters around inside a class is indicative of separate responsibilities. And am I handling it correctly. Is this perhaps just pure coincidence?
Thank you for taking the time to read all this.
Sidenote: Without refactoring I might've never noticed the separate responsibility.