Simplifying Conditional Expressions
As I’ve been reading through Refactoring by Martin Fowler, I’ve found it helpful to rewrite some of the examples from the book in PHP in order to cement the concepts into my mind.
While Martin’s examples are primarily in Java, I’ve found an overwhelming majority of the concepts apply to PHP, which is where I spend most of my programming time.
In today’s article, I will attempt to rework the Simplifying Conditional Expressions (pp. 237-270) section into a handful of PHP-based examples.
As with any large block of code, you can make your intention clearer by decomposing it and replacing chunks of code with a method call named after the intention of that block of code.
- Martin Fowler, Refactoring - p. 238
Decompose conditional
public function calculateProjectRate(Project $project, $urgency, $type) {
if($type == 'complex' || $urgency == 'rush') {
$rate = ($project->getBaseRate() * $project->getSize()) * 1.5;
} else {
$rate = $project->getBaseRate() * $project->getSize();
}
return $rate;
}
One of the resounding concepts in Refactoring is that whenever you can break apart a section of code into smaller, more informative units, you should. Essentially, the method above takes a Project
object along with a couple of parameters and determines what rate should be charged.
The two rate calculations are dependent on the outcome of the conditional. Since the conditional is made up of two items, it may make more sense to refactor it into its own method with a descriptive name.
While we are at it, we can move the two rate calculations into their own methods.
public function calculateProjectRate(Project $project, $urgency, $type)
{
if( $this->isNotSimple($urgency, $type) ) {
$rate = $this->calculateComplexRate($project);
} else {
$rate = $this->calculateDefaultRate($project);
}
return $rate;
}
private function isNotSimple($urgency, $type)
{
return $type == 'complex' || $urgency == 'rush';
}
private function calculateComplexRate($project)
{
return ($project->getBaseRate() * $project->getSize()) * 1.5;
}
private function calculateDefaultRate($project)
{
return $project->getBaseRate() * $project->getSize();
}
It's worth pointing out that while we have added several methods and increased the number of code lines, we have more clearly communicated the intention behind our conditional by using a descriptive method name, instead of simply comparing strings.
Consolidate conditional expression
Say you have a handful of conditionals that all return the same result if true. By consolidating them into a single method, you can improve the implied logic of your code.
public function calculateProjectRate($request, $language, $timeline)
{
if ($request == 'interpretive dancing') {
return false;
}
if ($language == 'Fortran 1') {
return false;
}
if ($timeline == '1 day') {
return false;
}
// continue rate processing
}
Basically, we are checking a handful of things to weed out projects we aren't interested in. All of those return a boolean value indicating we don't want, or aren't capable of doing the requested work. Since each conditional returns the same value, we can refactor it into the following:
public function calculateProjectRate($request, $language, $timeline)
{
if($this->isNotOurKindOfWork($request, $language, $timeline)) {
return false;
}
// continue rate processing
}
private function isNotOurKindOfWork($request, $language, $timeline)
{
return $request == 'interpretive dancing' || $language == 'Fortran 1' || $timeline == '1 day';
}
Consolidate duplicate conditional fragment
Occasionally, you will find sections of code that are duplicated across multiple legs of a conditional.
public function calculateProjectRate()
{
if ($this->isComplexWork()) {
$rate = $this->calculateComplexRate();
$this->verifyRateWithTheBoss($rate);
} else {
$rate = $this->calculateBaseRate();
$this->verifyRateWithTheBoss($rate);
}
}
Fortunately, this is a pretty easy fix. Just move the duplicated code outside the conditional so that it is processed after the conditional is evaluated.
public function calculateProjectRate()
{
if ($this->isComplexWork()) {
$rate = $this->calculateComplexRate();
} else {
$rate = $this->calculateBaseRate();
}
$this->verifyRateWithTheBoss($rate);
}
Replace nested conditional with guard clause
If you find yourself nesting a conditional block within another set of conditionals, you may find that replacing the outermost conditional with a guard clause is more clear.
public function filterWorkRequests()
{
if ($this->workIsInappropriate()) {
$response = $this->absolutelyNot();
} else {
if ($this->workIsNeutral()) {
$response = $this->possibly();
} else {
$response = $this->weWouldLoveTo();
}
}
return $response;
}
In this example, we know right off the bat that we don't want work we deem to be inappropriate. If that's the case, perhaps a guard clause would be a better choice than nesting the conditionals.
If you are using an if-then-else construct you are giving equal weight to the if leg and the else leg. This communicates to the reader that the legs are equally likely and important. Instead, the guard clause says, "This is rare, and if it happens, do something and get out."
- Martin Fowler, Refactoring - p.251
As you can see below, we are using a guard clause to exit the method as soon as we deem the work inappropriate. We know already we don't want it, so there's no sense in continuing the method.
public function filterWorkRequests()
{
if ($this->workIsInappropriate()) {
return $this->absolutelyNot();
}
if ($this->workIsNeutral()) {
return $this->possibly();
}
return $this->weWouldLoveTo();
}
Conclusion
Computers excel at taking sets of instructions and stepping through them systematically. They don’t need code to have informative method or variable names. They don’t even need the code to be formatted in a specific pattern (aside for the syntactical requirements).
Our goal in simplifying conditional expressions should be to make the code read easier for humans, not for computers. Ideally, you should be able to glance at a conditional, see which is the happy path through the method, and quickly identify when and where deviations should occur.
Good luck!
Want to read more tips and insights on working with a website development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.
By Jesse Schutt
Director of Engineering
Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.