Whether conditional branching uses early returns or is safely written in conditional coverage-15 [C # refactoring sample]

How to write a conditional branch

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.

Sample code

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;
                }
            }
        }
java
    //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"));
            }
        }
    }
# Refactoring policy In this example, the processing flow is not good before the early return. Since I am trying to write everything in one place, it is difficult to divide the process. Error judgment => Consumption tax rate fixed => Discount rate fixed => Payment price calculation The dependence of each process is reduced by dividing by the process. Since the error judgment is a process before entering the calculation logic, an early return is made.

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.

After refactoring


       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;
        }
java
    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);
    }

Consideration

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.

Consumption tax confirmation method (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;
        }

Previous article (ignoring exceptions)

Table of Contents

Recommended Posts

Whether conditional branching uses early returns or is safely written in conditional coverage-15 [C # refactoring sample]
When the processing after conditional branching is redundant-12 [C # refactoring sample]