Embrace guard statements and stop using nested conditional expressions!

Get into the habit of writing together! This is the sixth day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

motivation

Conditional expressions generally come in two flavors:

  • Both conditional branches are normal behavior
  • Only one conditional branch is normal behavior, the other branch is abnormal

These two types of conditional expressions serve different purposes, which should be shown in the code:

  • If both branches are normal behavior, a conditional expression of the form if...else... should be used

  • If a condition is extremely rare, it should be checked separately and returned from the function as soon as the condition is true

    Such separate checks are often referred to as "guard clauses"

The essence of replacing nested conditional expressions with guards is to give special attention to a branch. If you use an if-then-else construct, you place equal weight on the if branch and the else branch. The message to the reader of such a code structure is that each branch is equally important. The guard statement is telling the reader: "This situation is not what the core logic of this function cares about. If it does happen, please do some necessary cleanup and exit."

The notion that "every function can only have one entry and one exit" is deeply ingrained in some programmers. When I work with the code they write, I often need to use guard statements instead of nested conditional expressions. Today's programming languages ​​enforce only one entry per function, and the "single exit" rule is not that useful. Keeping the code clear is the key: if a single exit makes the function more readable, use a single exit; otherwise, it doesn't.

practice

Select the outermost conditional logic that needs to be replaced and replace it with a guard statement.

test.

If necessary, repeat the above steps.

If all guard statements produce the same result, you can combine them using [Merge Conditional Expressions].

case

Calculate wages to be paid to employees. Only employees who are still working in the company need to be paid, so this letter

The number needs to check for two "employee no longer at work" situations.

    public Long payAmount(Employee employee) {
        long result;
        if (employee.isSeparated) {
            result = 0;
        } else {
            if (employee.isRetired) {
                result = 0;
            } else {
                // logic to compute amount
                lorem.ipsum(dolor.sitAmet);
                consectetur(adipiscing).elit();
                sed.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
                ut.enim.ad(minim.veniam);
                result = someFinalComputation();
            }
        } return result;
    }
复制代码

Nested conditional logic makes it difficult to see what the code really means. Only when neither of the current two conditional expressions is true does this code actually start its main work. Therefore, guard statements allow the code to express its meaning more clearly

picture. As always, I like to take small steps, so I deal with the top conditional logic first.

    public Long payAmount(Employee employee) {
        long result;
        if (employee.isSeparated) {
            result = 0;
        }
        if (employee.isRetired) {
            result = 0;
        } else { // logic to compute amount
            lorem.ipsum(dolor.sitAmet);
            consectetur(adipiscing).elit();
            sed.do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
            ut.enim.ad(minim.veniam);
            result = someFinalComputation();
        } return result;
    }
复制代码

After making this modification, I execute the test and move on to the next step.

    public Long payAmount(Employee employee) {
        long result;
        if (employee.isSeparated) {
            return 0l;
        }
        if (employee.isRetired) {
            return 0l;
        }

        lorem.ipsum(dolor.sitAmet);
        consectetur(adipiscing).elit();
        sed. do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
        ut.enim.ad(minim.veniam);
        result = someFinalComputation();
        return result;
    }
复制代码

At this point, the result variable is useless, so I delete it:

    public Long payAmount(Employee employee) {
        if (employee.isSeparated) {
            return 0l;
        }
        if (employee.isRetired) {
            return 0l;
        }
        lorem.ipsum(dolor.sitAmet);
        consectetur(adipiscing).elit();
        sed. do.eiusmod = tempor.incididunt.ut(labore) && dolore(magna.aliqua);
        ut.enim.ad(minim.veniam);
        return someFinalComputation();
    }
复制代码

It's always good to be able to reduce one mutable variable.

reverse the condition

We can often reverse the conditional expression to replace nested conditional expressions with guards.

public int adjustedCapital(Instrument anInstrument) {
  int result = 0;
  if (anInstrument.capital > 0) {
    if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
      result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
    }
  }
  return result;
}
复制代码

Replace one by one. But this time when inserting the guard statement, I need to reverse the corresponding condition:

public int adjustedCapital(Instrument anInstrument) {
  int result = 0;
  if (anInstrument.capital <= 0) {
    return result;
  }
  if (anInstrument.interestRate > 0 && anInstrument.duration > 0) {
    result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
  }
  return result;
}
复制代码

The next condition is a little more complicated, so I do the inversion in two steps. First add a logical NOT operation:

public int adjustedCapital(Instrument anInstrument) {
  int result = 0;
  if (anInstrument.capital <= 0) {
    return result;
  }
  if (!(anInstrument.interestRate > 0 && anInstrument.duration > 0)) {
    return result;
  }
  result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
  return result;
}
复制代码

But leaving a logical NOT in a conditional expression like this would screw my head up, so I simplified it to:

public int adjustedCapital(Instrument anInstrument) {
  int result = 0;
  if (anInstrument.capital <= 0) {
    return result;
  }
  if (anInstrument.interestRate <= 0 || anInstrument.duration <= 0) {
    return result;
  }
  result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
  return result;
}
复制代码

These two lines of logic produce the same result, so I can combine them with [Merge Conditional Expressions]:

public int adjustedCapital(Instrument anInstrument) {
  int result = 0;
  if (anInstrument.capital <= 0 || anInstrument.interestRate <= 0 || anInstrument.duration <= 0) {
    return result;
  }
  result = (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
  return result;
}
复制代码

At this point, the result variable does two things: at first I set it to 0, which represents the return value when the guard statement is triggered; and then I assign it with the result of the final calculation. I can remove this variable completely, avoid double responsibility with one variable, and reduce one more variable variable.

public int adjustedCapital(Instrument anInstrument) {
  if (anInstrument.capital <= 0 || anInstrument.interestRate <= 0 || anInstrument.duration <= 0) {
    return 0;
  }
  return (anInstrument.income / anInstrument.duration) * anInstrument.adjustmentFactor;
}
复制代码

refer to

  • "Reconstruction"
  • "Clean Architecture"

Guess you like

Origin juejin.im/post/7083509863132692494