Skip to content

Commit ebbd2a5

Browse files
committed
add docs
1 parent 28a43a9 commit ebbd2a5

File tree

1 file changed

+273
-52
lines changed

1 file changed

+273
-52
lines changed

README.md

Lines changed: 273 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
11
[![Continuous Integration](https://github.com/kaiosilveira/replace-function-with-command-refactoring/actions/workflows/ci.yml/badge.svg)](https://github.com/kaiosilveira/replace-function-with-command-refactoring/actions/workflows/ci.yml)
22

3-
# Refactoring catalog repository template
4-
5-
## Useful commands
6-
7-
- Generate markdown containing a diff with patch information based on a range of commits:
8-
9-
```bash
10-
yarn tools:cli generate-diff -f <first_commit_sha> -l <last_commit_sha>
11-
```
12-
13-
- To generate the commit history table for the last section, including the correct links:
14-
15-
```bash
16-
yarn tools:cli generate-cmt-table -r replace-function-with-command-refactoring
17-
```
18-
19-
---
20-
213
ℹ️ _This repository is part of my Refactoring catalog based on Fowler's book with the same title. Please see [kaiosilveira/refactoring](https://github.com/kaiosilveira/refactoring) for more details._
224

235
---
246

257
# Replace Function With Command
268

27-
**Formerly: Old name**
9+
**Formerly: Replace Method with Method Object**
2810

2911
<table>
3012
<thead>
@@ -36,18 +18,30 @@ yarn tools:cli generate-cmt-table -r replace-function-with-command-refactoring
3618
<td>
3719

3820
```javascript
39-
result = initial.code;
21+
function score(candidate, medicalExam, scoringGuide) {
22+
let result = 0;
23+
let healthLevel = 0;
24+
// long body code
25+
}
4026
```
4127

4228
</td>
4329

4430
<td>
4531

4632
```javascript
47-
result = newCode();
48-
49-
function newCode() {
50-
return 'new code';
33+
class Scorer {
34+
constructor(candidate, medicalExam, scoringGuide) {
35+
this._candidate = candidate;
36+
this._medicalExam = medicalExam;
37+
this._scoringGuide = scoringGuide;
38+
}
39+
40+
execute() {
41+
this._result = 0;
42+
this._healthLevel = 0;
43+
// long body code
44+
}
5145
}
5246
```
5347

@@ -56,58 +50,285 @@ function newCode() {
5650
</tbody>
5751
</table>
5852

59-
**Inverse of: [Another refactoring](https://github.com/kaiosilveira/refactoring)**
53+
**Inverse of: [Replace Command with Function](https://github.com/kaiosilveira/replace-command-with-function-refactoring)**
6054

61-
**Refactoring introduction and motivation** dolore sunt deserunt proident enim excepteur et cillum duis velit dolor. Aute proident laborum officia velit culpa enim occaecat officia sunt aute labore id anim minim. Eu minim esse eiusmod enim nulla Lorem. Enim velit in minim anim anim ad duis aute ipsum voluptate do nulla. Ad tempor sint dolore et ullamco aute nulla irure sunt commodo nulla aliquip.
55+
Nested functions are a useful tool in most cases but, sometimes, we need extra control over variables and readability. That's when this refactoring comes in handy.
6256

6357
## Working example
6458

65-
**Working example general explanation** proident reprehenderit mollit non voluptate ea aliquip ad ipsum anim veniam non nostrud. Cupidatat labore occaecat labore veniam incididunt pariatur elit officia. Aute nisi in nulla non dolor ullamco ut dolore do irure sit nulla incididunt enim. Cupidatat aliquip minim culpa enim. Fugiat occaecat qui nostrud nostrud eu exercitation Lorem pariatur fugiat ea consectetur pariatur irure. Officia dolore veniam duis duis eu eiusmod cupidatat laboris duis ad proident adipisicing. Minim veniam consectetur ut deserunt fugiat id incididunt reprehenderit.
59+
Our working example consists of a program that calculates a health score for an insurance company. It goes like this:
60+
61+
```javascript
62+
export function score(candidate, medicalExam, scoringGuide) {
63+
let result = 0;
64+
let healthLevel = 0;
65+
let highMedicalRiskFlag = false;
66+
if (medicalExam.isSmoker) {
67+
healthLevel += 10;
68+
highMedicalRiskFlag = true;
69+
}
70+
let certificationGrade = 'regular';
71+
if (scoringGuide.stateWithLowCertification(candidate.originState)) {
72+
certificationGrade = 'low';
73+
result -= 5;
74+
}
75+
// lots more code like this
76+
result -= Math.max(healthLevel - 5, 0);
77+
return result;
78+
}
79+
```
80+
81+
Our goal here is to make the `score` function into a [command](https://github.com/kaiosilveira/design-patterns/tree/main/command), so we can isolate the processing into chunks, making the function more legible and probably more testable as well.
6682

6783
### Test suite
6884

69-
Occaecat et incididunt aliquip ex id dolore. Et excepteur et ea aute culpa fugiat consectetur veniam aliqua. Adipisicing amet reprehenderit elit qui.
85+
The test suite covers all possible bifurcations of the aforementioned function, and it is somewhat extensive. To see the full implementation, please refer to [src/index.test.js](./src/index.test.js).
7086

71-
```javascript
72-
describe('functionBeingRefactored', () => {
73-
it('should work', () => {
74-
expect(0).toEqual(1);
75-
});
76-
});
87+
### Steps
88+
89+
We start by introducing a `Scorer` class:
90+
91+
```diff
92+
+export class Scorer {
93+
+ execute(candidate, medicalExam, scoringGuide) {
94+
+ let result = 0;
95+
+ let healthLevel = 0;
96+
+ let highMedicalRiskFlag = false;
97+
+
98+
+ if (medicalExam.isSmoker) {
99+
+ healthLevel += 10;
100+
+ highMedicalRiskFlag = true;
101+
+ }
102+
+
103+
+ let certificationGrade = 'regular';
104+
+ if (scoringGuide.stateWithLowCertification(candidate.originState)) {
105+
+ certificationGrade = 'low';
106+
+ result -= 5;
107+
+ }
108+
+
109+
+ // lots more code like this
110+
+ result -= Math.max(healthLevel - 5, 0);
111+
+
112+
+ return result;
113+
+ }
114+
+}
77115
```
78116

79-
Magna ut tempor et ut elit culpa id minim Lorem aliqua laboris aliqua dolor. Irure mollit ad in et enim consequat cillum voluptate et amet esse. Fugiat incididunt ea nulla cupidatat magna enim adipisicing consequat aliquip commodo elit et. Mollit aute irure consequat sunt. Dolor consequat elit voluptate aute duis qui eu do veniam laborum elit quis.
117+
Then, we [inline](https://github.com/kaiosilveira/inline-function-refactoring) `Scorer.execute` at the body of `score`:
80118

81-
### Steps
119+
```diff
120+
export function score(candidate, medicalExam, scoringGuide) {
121+
- let result = 0;
122+
- let healthLevel = 0;
123+
- let highMedicalRiskFlag = false;
124+
-
125+
- if (medicalExam.isSmoker) {
126+
- healthLevel += 10;
127+
- highMedicalRiskFlag = true;
128+
- }
129+
-
130+
- let certificationGrade = 'regular';
131+
- if (scoringGuide.stateWithLowCertification(candidate.originState)) {
132+
- certificationGrade = 'low';
133+
- result -= 5;
134+
- }
135+
-
136+
- // lots more code like this
137+
- result -= Math.max(healthLevel - 5, 0);
138+
-
139+
- return result;
140+
+ return new Scorer().execute(candidate, medicalExam, scoringGuide);
141+
}
142+
```
143+
144+
Then, since the command's only raison d'être is to execute the scoring logic, it's somewhat more semantic to have the function's arguments as part of its constructor. We start by moving `candidate`:
145+
146+
```diff
147+
+++ b/src/index.js
148+
@@ -1,9 +1,13 @@
149+
export function score(candidate, medicalExam, scoringGuide) {
150+
- return new Scorer().execute(candidate, medicalExam, scoringGuide);
151+
+ return new Scorer(candidate).execute(medicalExam, scoringGuide);
152+
}
153+
154+
export class Scorer {
155+
- execute(candidate, medicalExam, scoringGuide) {
156+
+ constructor(candidate) {
157+
+ this._candidate = candidate;
158+
+ }
159+
+
160+
+ execute(medicalExam, scoringGuide) {
161+
let result = 0;
162+
let healthLevel = 0;
163+
let highMedicalRiskFlag = false;
164+
let certificationGrade = 'regular';
165+
- if (scoringGuide.stateWithLowCertification(candidate.originState)) {
166+
+ if (scoringGuide.stateWithLowCertification(this._candidate.originState)) {
167+
certificationGrade = 'low';
168+
result -= 5;
169+
}
170+
```
171+
172+
Then, we do the same for `medicalExam`:
173+
174+
```diff
175+
export function score(candidate, medicalExam, scoringGuide) {
176+
- return new Scorer(candidate).execute(medicalExam, scoringGuide);
177+
+ return new Scorer(candidate, medicalExam).execute(scoringGuide);
178+
}
179+
export class Scorer {
180+
- constructor(candidate) {
181+
+ constructor(candidate, medicalExam) {
182+
this._candidate = candidate;
183+
+ this._medicalExam = medicalExam;
184+
}
185+
- execute(medicalExam, scoringGuide) {
186+
+ execute(scoringGuide) {
187+
let result = 0;
188+
let healthLevel = 0;
189+
let highMedicalRiskFlag = false;
190+
- if (medicalExam.isSmoker) {
191+
+ if (this._medicalExam.isSmoker) {
192+
healthLevel += 10;
193+
highMedicalRiskFlag = true;
194+
}
195+
```
196+
197+
And the last one is `scoringGuide`:
198+
199+
```diff
200+
export function score(candidate, medicalExam, scoringGuide) {
201+
- return new Scorer(candidate, medicalExam).execute(scoringGuide);
202+
+ return new Scorer(candidate, medicalExam, scoringGuide).execute();
203+
}
204+
export class Scorer {
205+
- constructor(candidate, medicalExam) {
206+
+ constructor(candidate, medicalExam, scoringGuide) {
207+
this._candidate = candidate;
208+
this._medicalExam = medicalExam;
209+
+ this._scoringGuide = scoringGuide;
210+
}
211+
- execute(scoringGuide) {
212+
+ execute() {
213+
let result = 0;
214+
let healthLevel = 0;
215+
let highMedicalRiskFlag = false;
216+
let certificationGrade = 'regular';
217+
- if (scoringGuide.stateWithLowCertification(this._candidate.originState)) {
218+
+ if (this._scoringGuide.stateWithLowCertification(this._candidate.originState)) {
219+
certificationGrade = 'low';
220+
result -= 5;
221+
}
222+
```
223+
224+
Now, on to the inner refactorings. Since our goal is to break the processing down into smaller chunks, we need to make the internal variables widely accessible, and we can accomplish this by turning them into class members. We start with `result`:
225+
226+
```diff
227+
execute() {
228+
- let result = 0;
229+
+ this._result = 0;
230+
let healthLevel = 0;
231+
let highMedicalRiskFlag = false;
232+
let certificationGrade = 'regular';
233+
if (this._scoringGuide.stateWithLowCertification(this._candidate.originState)) {
234+
certificationGrade = 'low';
235+
- result -= 5;
236+
+ this._result -= 5;
237+
}
238+
// lots more code like this
239+
- result -= Math.max(healthLevel - 5, 0);
240+
+ this._result -= Math.max(healthLevel - 5, 0);
241+
- return result;
242+
+ return this._result;
243+
}
244+
}
245+
```
246+
247+
Then `healthLevel`:
248+
249+
```diff
250+
execute() {
251+
this._result = 0;
252+
- let healthLevel = 0;
253+
+ this._healthLevel = 0;
254+
let highMedicalRiskFlag = false;
255+
if (this._medicalExam.isSmoker) {
256+
- healthLevel += 10;
257+
+ this._healthLevel += 10;
258+
highMedicalRiskFlag = true;
259+
}
260+
// lots more code like this
261+
- this._result -= Math.max(healthLevel - 5, 0);
262+
+ this._result -= Math.max(this._healthLevel - 5, 0);
263+
return this._result;
264+
}
265+
```
266+
267+
And then `highMedicalRiskFlag`:
268+
269+
```diff
270+
execute() {
271+
this._result = 0;
272+
this._healthLevel = 0;
273+
- let highMedicalRiskFlag = false;
274+
+ this._highMedicalRiskFlag = false;
275+
if (this._medicalExam.isSmoker) {
276+
this._healthLevel += 10;
277+
- highMedicalRiskFlag = true;
278+
+ this._highMedicalRiskFlag = true;
279+
}
280+
let certificationGrade = 'regular';
281+
```
82282

83-
**Step 1 description** mollit eu nulla mollit irure sint proident sint ipsum deserunt ad consectetur laborum incididunt aliqua. Officia occaecat deserunt in aute veniam sunt ad fugiat culpa sunt velit nulla. Pariatur anim sit minim sit duis mollit.
283+
And, finally, `certificationGrade`:
84284

85285
```diff
86-
diff --git a/src/price-order/index.js b/src/price-order/index.js
87-
@@ -3,6 +3,11 @@
88-
-module.exports = old;
89-
+module.exports = new;
286+
- let certificationGrade = 'regular';
287+
+ this._certificationGrade = 'regular';
288+
if (this._scoringGuide.stateWithLowCertification(this._candidate.originState)) {
289+
- certificationGrade = 'low';
290+
+ this._certificationGrade = 'low';
291+
this._result -= 5;
292+
}
90293
```
91294

92-
**Step n description** mollit eu nulla mollit irure sint proident sint ipsum deserunt ad consectetur laborum incididunt aliqua. Officia occaecat deserunt in aute veniam sunt ad fugiat culpa sunt velit nulla. Pariatur anim sit minim sit duis mollit.
295+
Now we're able to start the chunking. Our example is `scoreSmoking`:
93296

94297
```diff
95-
diff --git a/src/price-order/index.js b/src/price-order/index.js
96-
@@ -3,6 +3,11 @@
97-
-module.exports = old;
98-
+module.exports = new;
298+
execute() {
299+
- if (this._medicalExam.isSmoker) {
300+
- this._healthLevel += 10;
301+
- this._highMedicalRiskFlag = true;
302+
- }
303+
+ this.scoreSmoking();
304+
}
305+
306+
+ scoreSmoking() {
307+
+ if (this._medicalExam.isSmoker) {
308+
+ this._healthLevel += 10;
309+
+ this._highMedicalRiskFlag = true;
310+
+ }
311+
+ }
312+
}
99313
```
100314

101-
And that's it!
315+
And that's it for this one! The function is now more ligible and can become more readable as well.
102316

103317
### Commit history
104318

105319
Below there's the commit history for the steps detailed above.
106320

107-
| Commit SHA | Message |
108-
| --------------------------------------------------------------------------- | ------------------------ |
109-
| [cmt-sha-1](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit-SHA-1) | description of commit #1 |
110-
| [cmt-sha-2](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit-SHA-2) | description of commit #2 |
111-
| [cmt-sha-n](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit-SHA-n) | description of commit #n |
321+
| Commit SHA | Message |
322+
| ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
323+
| [3310680](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/3310680db031412cf0b87160b335976ea32b48db) | introduce `Scorer` class |
324+
| [c982d35](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/c982d35f07364977eb0704b2679f721f191f2796) | call `Scorer.execute` at the body of `score` |
325+
| [f8408b4](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/f8408b4690f690e568e5bfdc5ceafed9bab0e78e) | move `candidate` argument to `Score`'s constructor |
326+
| [091533c](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/091533c15dc03a53c98bf1434cb1a4ee84b3fce4) | move `medicalExam` to `Score`'s constructor |
327+
| [20b234e](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/20b234e39640ed5469eab550e329e783f635d2e2) | move `scoringGuide` to `Score`'s constructor |
328+
| [10aa4f7](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/10aa4f7792523199c33d9aea33d8d991b722564e) | make `result` an instance variable at `Score` |
329+
| [eda5940](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/eda594099ce1b4e4687189f3289822929e2a27c2) | make `healthLevel` an instance variable at `Score` |
330+
| [d172cde](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/d172cde23d41c832739bfbe645d0c87e5b9a62f5) | make `highMedicalRiskFlag` an instance variable at `Score` |
331+
| [b979164](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/b979164d07f04ae53012b3ca1157cc78c189c9c1) | make `certificationGrade` an instance variable at `Score` |
332+
| [28a43a9](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commit/28a43a9654631562873fce636094c1b0d1f485a3) | extract `scoreSmoking` function at `Score` |
112333

113334
For the full commit history for this project, check the [Commit History tab](https://github.com/kaiosilveira/replace-function-with-command-refactoring/commits/main).

0 commit comments

Comments
 (0)