ℹ️ This repository is part of my "refactoring" catalog based on Fowler's book with the same title. Please see kaiosilveira/refactoring for more details.
Formerly: Extract Method
Before | After |
---|---|
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
// print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
} |
function printOwing(invoice) {
printBanner();
let outstanding = calculateOutstanding();
printDetails(outstanding);
function printDetails(outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
}
} |
Inverse of: Inline Function
Extracting a block of code into a function is one of the most common refactorings out there. It helps give more meaning to a given piece of code, as you'll be effectively naming a code block after its intent.
The working example for this refactoring is pretty simple: a function that prints the outstanding amount for a given invoice.
This is the code block that we will be refactoring:
function printOwing(invoice) {
let outstanding = 0;
console.log('***********************');
console.log('**** Customer owes ****');
console.log('***********************');
// calculate outstanding
for (const o of invoice.orders) {
outstanding += o.amount;
}
// record due date
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
// print details
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
This is the end result after all the refactoring steps being made:
function printOwing(invoice) {
printBanner();
const outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
A simple test suite with four tests was added. These tests cover the main behaviors of the printOwing
function:
- printing a banner
- printing the customer's name
- printing the outstanding amount
- printing the due date
For the implementation details, see index.test.js.
No variables out of scope
It's easy to extract a function for the upper part of printOwing
, as it's just a sequence of console.log
statements. The steps are:
- introduce a
printBanner
function:
diff --git a/index.js b/index.js
@@ -22,4 +22,10 @@ function printOwing(invoice) {
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
+function printBanner() {
+ console.log('***********************');
+ console.log('**** Customer owes ****');
+ console.log('***********************');
+}
+
module.exports = { printOwing };
- replace banner code block with the new function
diff --git a/index.js b/index.js
@@ -3,9 +3,7 @@ const Clock = { today: new Date() };
function printOwing(invoice) {
let outstanding = 0;
- console.log('***********************');
- console.log('**** Customer owes ****');
- console.log('***********************');
+ printBanner();
// calculate outstanding
for (const o of invoice.orders) {
Using local variables
As for the bottom part, the details, it's a little bit more involved: we can create a printDetails
function and encapsulate the console.log
statements, but we can't yet move it to the top level, as it is using variables declared in the parent scope:
- Introduce the
printDetails
function
diff --git a/index.js b/index.js
@@ -18,6 +18,12 @@ function printOwing(invoice) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
+
+ function printDetails() {
+ console.log(`name: ${invoice.customer}`);
+ console.log(`amount: ${outstanding}`);
+ console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
+ }
}
function printBanner() {
- Replace the details code block with the new function:
diff --git a/index.js b/index.js
@@ -14,10 +14,7 @@ function printOwing(invoice) {
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()
+ 30);
- // print details
- console.log(`name: ${invoice.customer}`);
- console.log(`amount: ${outstanding}`);
- console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
+ printDetails();
function printDetails() {
console.log(`name: ${invoice.customer}`);
Then, to move the function to the top level, we need to pass down the variables it depends on as parameters, in this case, these variables are invoice
and outstanding
:
diff --git a/index.js b/index.js
@@ -14,9 +14,9 @@ function printOwing(invoice) {
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()
+ 30);
- printDetails();
+ printDetails(invoice, outstanding);
- function printDetails() {
+ function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
The same mechanics apply to the code that records the due date:
- introduce the
recordDueDate
function:
diff --git a/index.js b/index.js
@@ -16,6 +16,11 @@ function printOwing(invoice) {
printDetails(invoice, outstanding);
+ function recordDueDate(invoice) {
+ const today = Clock.today;
+ invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
+ }
+
function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
- replace due date recording with the new function:
diff --git a/index.js b/index.js
@@ -10,10 +10,7 @@ function printOwing(invoice) {
outstanding += o.amount;
}
- // record due date
- const today = Clock.today;
- invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
-
+ recordDueDate(invoice);
printDetails(invoice, outstanding);
function recordDueDate(invoice) {
Reassigning a local variable
Things get trickier when it comes to extracting a function for the calculation of the outstanding
amount, as it's being declared and then incremented later on.
We start with a tiny variable sliding:
diff --git a/index.js b/index.js
@@ -1,11 +1,10 @@
const Clock = { today: new Date() };
function printOwing(invoice) {
- let outstanding = 0;
-
printBanner();
// calculate outstanding
+ let outstanding = 0;
for (const o of invoice.orders) {
outstanding += o.amount;
}
Then, we can copy this whole code block and put it into its own function:
diff --git a/index.js b/index.js
@@ -30,4 +30,14 @@ function printBanner() {
console.log('***********************');
}
+function calculateOutstanding(invoice) {
+ let outstanding = 0;
+
+ for (const o of invoice.orders) {
+ outstanding += o.amount;
+ }
+
+ return outstanding;
+}
+
module.exports = { printOwing };
Finally, we can replace the first assignment with outstanding
as being directly the result of a function call:
diff --git a/index.js b/index.js
@@ -3,12 +3,7 @@ const Clock = { today: new Date() };
function printOwing(invoice) {
printBanner();
- // calculate outstanding
- let outstanding = 0;
- for (const o of invoice.orders) {
- outstanding += o.amount;
- }
-
+ let outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
This looks way better now, but there are still some tidy up we can do:
- make the
outstanding
variable constant:
diff --git a/index.js b/index.js
@@ -3,7 +3,7 @@ const Clock = { today: new Date() };
function printOwing(invoice) {
printBanner();
- let outstanding = calculateOutstanding(invoice);
+ const outstanding = calculateOutstanding(invoice);
recordDueDate();
printDetails(invoice, outstanding);
- rename the temp variable inside
calculateOutstanding
asresult
:
diff --git a/index.js b/index.js
index 6fd76d3..e9baa55 100644
@@ -26,13 +26,13 @@ function printBanner() {
}
function calculateOutstanding(invoice) {
- let outstanding = 0;
+ let result = 0;
for (const o of invoice.orders) {
- outstanding += o.amount;
+ result += o.amount;
}
- return outstanding;
+ return result;
}
module.exports = { printOwing };
- move
printDetails
out ofprintOwing
:
diff --git a/index.js b/index.js
@@ -11,12 +11,12 @@ function printOwing(invoice) {
const today = Clock.today;
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}
+}
- function printDetails(invoice, outstanding) {
- console.log(`name: ${invoice.customer}`);
- console.log(`amount: ${outstanding}`);
- console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
- }
+function printDetails(invoice, outstanding) {
+ console.log(`name: ${invoice.customer}`);
+ console.log(`amount: ${outstanding}`);
+ console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
function printBanner() {
- move
recordDueDate
out ofprintOwing
:
diff --git a/index.js b/index.js
@@ -6,11 +6,11 @@ function printOwing(invoice) {
const outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
+}
- function recordDueDate(invoice) {
- const today = Clock.today;
- invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
- }
+function recordDueDate(invoice) {
+ const today = Clock.today;
+ invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}
function printDetails(invoice, outstanding) {
And that's it for this refactoring!
See below a chronology (from top to bottom) of all the refactoring steps:
Commit SHA | Message |
---|---|
f721ce9 | introduce printBanner function |
e894f28 | replace banner code block by the printBanner function |
74ec05e | introduce printDetails function |
609736f | replace details code block with function |
8c3be6c | pass down printDetails dependencies as parameters |
8106bbc | introduce recordDueDate function |
7bdf838 | replace due date recording code block by recordDueDate function |
13df230 | slide the declaration of the outstanding variable near to its usage |
ac1952e | introduce calculateOutstanding function |
de65877 | replace outstanding calculation by function call |
daef722 | make the outstanding variable a const |
aa465d9 | rename outstanding to result in calculateOutstanding |
1b662cd | move printDetails out of printOwing |
1b662cd | move recordDueDate out of printOwing |
The full commit history can be seen in the Commit history tab.