Fixing Long Functions With The Extract Function Refactoring
One of our core tenets of development is that code is read more than it’s written. To that end, we must make our code as easy to read and understand as possible. No one writes perfect code on the first try so it’s important that we continually refine our code so it’s easy for the next developer to read even if we’re the next developer.
In this article, we’re going to discuss what the extract function refactor is, what code smells are an indication to use it, and then work through an example in PHP.
Code Smell
We should use the extract function refactoring when we have code that’s duplicated in multiple places or when we can isolate a portion of a larger method to make multiple methods.
Duplicate code is less than ideal in our codebase because this duplication causes us to maintain the same logic twice and it’s easy for us to not update all the duplicate code which can cause bugs to enter our codebase.
“Large methods” can be hard to define. Some people have a hard limit for their teams while others say it needs to fit on a single screen. The logic of what can fit inside a single screen is a fun concept now that some people use their monitors in a vertical orientation.
A good rule to use is that a function should be as small as possible while still being easy to read. Small functions are also easier to test and debug as they generally only have a few paths.
What Is the Extract Function Code Refactoring?
In this technique, we take a section of code and make it a new function.
To perform this refactoring we’ll:
- Copy the section of code we’re extracting and then paste it into a new method
- Look for any local variables and add them as parameters to the method
- Replace the extracted code with a call to the newly created method
- Run our tests and make sure they all pass
- Look for places where we can use the newly extracted method
Example
Let’s work through an example. Our codebase contains the following function.
public function rebuildEstimatesBasedOnIncompleteTasks(): void
{
// get the incomplete tasks assigned to this project
$tasks = Task::where('project_id', $this->id)
->whereNull('end_date')
->get();
$total = 0;
foreach ($tasks as $task) {
if ($task->time_estimate > 1) {
$total += $task->time_estimate;
}
if ($task->time_estimate === null) {
$total += 2;
}
}
$this->estimated_date = now()->modify("+{$total} days");
$this->save();
}
Because we used an intention revealing name of rebuildEstimatesBasedOnIncompleteTasks
we can tell that we’re going to be rebuilding estimates based on our incomplete tasks but it’s hard to quickly parse exactly which part of the function does what.
Let’s extract a new function that calculates the total of a set of tasks.
- Copy the section of code we’re extracting and then paste it into a new method
public function rebuildEstimatesBasedOnIncompleteTasks(): void
{
// get the incomplete tasks assigned to this project
$tasks = Task::where('project_id', $this->id)
->whereNull('end_date')
->get();
$total = 0;
foreach ($tasks as $task) {
if ($task->time_estimate > 1) {
$total += $task->time_estimate;
}
if ($task->time_estimate === null) {
$total += 2;
}
}
$this->estimated_date = now()->modify("+{$total} days");
$this->save();
}
public function calculateEstimatedHoursForTasks(): int
{
$total = 0;
foreach ($tasks as $task) {
if ($task->time_estimate > 1) {
$total += $task->time_estimate;
}
if ($task->time_estimate === null) {
$total += 2;
}
}
return $total;
}
- Look for any local variables and add them as parameters to the method
public function calculateEstimatedHoursForTasks(Collection $tasks): int
{
$total = 0;
foreach ($tasks as $task) {
if ($task->time_estimate > 1) {
$total += $task->time_estimate;
}
if ($task->time_estimate === null) {
$total += 2;
}
}
return $total;
}
- Replace the extracted code with a call to the newly created method
public function rebuildEstimatesBasedOnIncompleteTasks(): void
{
// get the incomplete tasks assigned to this project
$tasks = Task::where('project_id', $this->id)
->whereNull('end_date')
->get();
$total = $this->calculateEstimatedHoursForTasks();
$this->estimated_date = now()->modify("+{$total} days");
$this->save();
}
- Run our tests and make sure they all pass
We’ll run our total test suite.
- Look for places where we can use the newly extracted method
In this case, we don’t have anywhere that we can reuse this new method but it could come in handy in the future.
What You Need To Know
- Extract Function Refactor allows us to extra code from one function into a new one
- This reduces duplication and improves readability
Scott Keck-Warren
Scott is the Director of Technology at WeCare Connect where he strives to provide solutions for his customers needs. He's the father of two and can be found most weekends working on projects around the house with his loving partner.
Top Posts
- Working With Soft Deletes in Laravel (By Example)
- Fixing CMake was unable to find a build program corresponding to "Unix Makefiles"
- Upgrading to Laravel 8.x
- Get The Count of the Number of Users in an AD Group
- Multiple Vagrant VMs in One Vagrantfile
- Fixing the "this is larger than GitHub's recommended maximum file size of 50.00 MB" error
- Changing the Directory Vagrant Stores the VMs In
- Accepting Android SDK Licenses From The OSX Command Line
- Fixing the 'Target class [config] does not exist' Error
- Using Rectangle to Manage MacOS Windows