As a way to write a conditional branch (if), you can see various early return techniques that do not deepen the nesting. Personally, I don't think that everything should be written with early returns, and should be used depending on the situation. In addition, it was this article that inspired me to write an article. Especially, the comment section was a learning experience.
I have a policy that early returns should only be used for error handling, and business logic should be safely written in a conditional manner. This point is explained in the sample code below.
As a simple example, consider the calculation logic of the payment price considering the consumption tax and the discount rate. If certain conditions are met, the consumption tax will be the price excluding consumption tax. If the discount rate also meets certain conditions, it will be discounted by 1 to the price after considering the consumption tax. For the sake of simplicity, we will not go into detail about the existence of consumption tax and the conditions for applying the discount, but implement it with the flag of whether it is applicable.
//Calculate the amount paid by the customer
public decimal CalculatePaymentPrice(decimal price, bool canRemoveTax, bool canDiscount)
{
if (price < 0)
{
throw new ArgumentOutOfRangeException("Please enter a value of 0 or more for the product price.");
}
else if (canRemoveTax)
{
//Discount applied without sales tax
if (canDiscount)
{
return price * 0.9m;
}
else
{
return price;
}
}
else
{
//Discount applied with consumption tax
if (canDiscount)
{
return price * 1.08m * 0.9m;
}
else
{
return price * 1.08m;
}
}
}
//Calculate the amount paid by the customer
public BigDecimal CalculatePaymentPrice(BigDecimal price, boolean canRemoveTax, boolean canDiscount) {
if (price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Please enter a value of 0 or more for the product price.");
} else if (canRemoveTax) {
//Discount applied without sales tax
if (canDiscount) {
return price.multiply(new BigDecimal("0.9"));
} else {
return price;
}
} else {
//Discount applied with consumption tax
if (canDiscount) {
return price.multiply(new BigDecimal("1.08")).multiply(new BigDecimal("0.9"));
} else {
return price.multiply(new BigDecimal("1.08"));
}
}
}
2018/05/01 postscript As you can see in the comment section, an exception is thrown even though it is a business error. Normally, business errors should not throw exceptions. This is an adverse effect due to the simplification of the sample code, and is not subject to refactoring. What I wanted to say in this example is that I don't want to put business errors in the logic, so it's okay to get rid of them early.
In reality, one refactoring plan is to perform the price check process in advance and call the CalculatePaymentPrice method after passing the check.
public decimal CalculatePaymentPriceAfterRefactoring(decimal price, bool canRemoveTax, bool canDiscount)
{
//Error avoidance
if (price < 0)
{
throw new ArgumentOutOfRangeException("Please enter a value of 0 or more for the product price.");
}
//Consumption tax rate fixed
decimal taxRate;
if (canRemoveTax)
{
taxRate = 0m;
}
else
{
taxRate = 0.08m;
}
//Discount rate confirmed
decimal discountRate;
if (canDiscount)
{
discountRate = 0.9m;
}
else
{
discountRate = 1.0m;
}
//Calculate payment price
return price * (1m + taxRate) * discountRate;
}
public BigDecimal CalculatePaymentPriceAfterRefactoring(BigDecimal price, boolean canRemoveTax,
boolean canDiscount) {
//Error avoidance
if (price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Please enter a value of 0 or more for the product price.");
}
//Acquisition of consumption tax rate
BigDecimal taxRate;
if (canRemoveTax) {
taxRate = BigDecimal.ZERO;
} else {
taxRate = new BigDecimal("0.08");
}
//Get discount rate
BigDecimal discountRate;
if (canDiscount) {
discountRate = new BigDecimal("0.9");
} else {
discountRate = BigDecimal.ONE;
}
//Calculate payment price
return price.multiply(taxRate.add(BigDecimal.ONE)).multiply(discountRate);
}
In the payment price calculation part, each process can be separated by changing the process flow. If you are concerned about the temporary variables of taxRate and discountRate, you can make the part of consumption tax rate determination and discount rate determination into a method. Regarding error handling, if you apply early return and write the other business logic safely with comprehensive conditions, isn't there any problem as an implementation policy? Finally, I dared to write the place where the consumption tax is fixed with an early return.
This code is concise, but if you write it in if-else, you can check the condition coverage and feel at ease. On the other hand, many people say that the code is too short and either one is fine. .. ..
private decimal GetTaxRate(bool canRemoveTax)
{
if (canRemoveTax)
{
return 0m;
}
return 0.08m;
}