pdd-skills 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1478 -0
- package/bin/pdd.js +354 -0
- package/config/bpmn-rules.yaml +166 -0
- package/config/checkstyle.xml +105 -0
- package/config/eslint.config.js +48 -0
- package/config/pmd.xml +91 -0
- package/config/prd-rules.yaml +113 -0
- package/config/ruff.toml +45 -0
- package/config/sqlfluff.cfg +82 -0
- package/hooks/hook-executor.js +332 -0
- package/index.js +43 -0
- package/lib/api-routes.js +750 -0
- package/lib/api-server.js +408 -0
- package/lib/cache/cache-config.js +209 -0
- package/lib/cache/system-cache.js +852 -0
- package/lib/config-manager.js +373 -0
- package/lib/generate.js +528 -0
- package/lib/grpc/grpc-routes.js +1134 -0
- package/lib/grpc/grpc-server.js +912 -0
- package/lib/grpc/proto-definitions.js +1033 -0
- package/lib/init.js +172 -0
- package/lib/iteration/auto-fixer.js +1025 -0
- package/lib/iteration/auto-reviewer.js +923 -0
- package/lib/iteration/controller.js +577 -0
- package/lib/list.js +130 -0
- package/lib/mcp-server.js +548 -0
- package/lib/openclaw/api-integration.js +535 -0
- package/lib/openclaw/cli-integration.js +567 -0
- package/lib/openclaw/data-sync.js +845 -0
- package/lib/openclaw/openclaw-adapter.js +783 -0
- package/lib/plugin/example-plugins/code-stats/index.js +332 -0
- package/lib/plugin/example-plugins/code-stats/plugin.json +1 -0
- package/lib/plugin/example-plugins/custom-linter/index.js +472 -0
- package/lib/plugin/example-plugins/custom-linter/plugin.json +1 -0
- package/lib/plugin/example-plugins/hello-world/index.js +86 -0
- package/lib/plugin/example-plugins/hello-world/plugin.json +1 -0
- package/lib/plugin/plugin-manager.js +655 -0
- package/lib/plugin/plugin-sdk.js +565 -0
- package/lib/plugin/sandbox.js +627 -0
- package/lib/quality/rules/maintainability.js +418 -0
- package/lib/quality/rules/performance.js +498 -0
- package/lib/quality/rules/readability.js +441 -0
- package/lib/quality/rules/robustness.js +504 -0
- package/lib/quality/rules/security.js +444 -0
- package/lib/quality/scorer.js +576 -0
- package/lib/report.js +669 -0
- package/lib/sdk-base.js +301 -0
- package/lib/sdk-js.js +446 -0
- package/lib/sdk-python/README.md +546 -0
- package/lib/sdk-python/examples/basic_usage.py +450 -0
- package/lib/sdk-python/pdd_sdk/__init__.py +180 -0
- package/lib/sdk-python/pdd_sdk/client.py +1170 -0
- package/lib/sdk-python/pdd_sdk/events.py +423 -0
- package/lib/sdk-python/pdd_sdk/exceptions.py +158 -0
- package/lib/sdk-python/pdd_sdk/models.py +518 -0
- package/lib/sdk-python/pdd_sdk/utils.py +759 -0
- package/lib/token/budget-alert.js +367 -0
- package/lib/token/budget-manager.js +485 -0
- package/lib/update.js +54 -0
- package/lib/utils/logger.js +88 -0
- package/lib/verify.js +741 -0
- package/lib/version.js +52 -0
- package/lib/vm/README.md +102 -0
- package/lib/vm/dashboard/api-routes.js +669 -0
- package/lib/vm/dashboard/server.js +391 -0
- package/lib/vm/dashboard/sse.js +358 -0
- package/lib/vm/dashboard/static/css/dashboard.css +1378 -0
- package/lib/vm/dashboard/static/index.html +118 -0
- package/lib/vm/dashboard/static/js/app.js +949 -0
- package/lib/vm/dashboard/static/js/charts.js +913 -0
- package/lib/vm/dashboard/static/js/kanban-view.js +1053 -0
- package/lib/vm/dashboard/static/js/pipeline-view.js +463 -0
- package/lib/vm/dashboard/static/js/quality-view.js +598 -0
- package/lib/vm/dashboard/static/js/system-view.js +1021 -0
- package/lib/vm/data-provider.js +1191 -0
- package/lib/vm/event-bus.js +402 -0
- package/lib/vm/hooks/extract-hook.js +307 -0
- package/lib/vm/hooks/generate-hook.js +374 -0
- package/lib/vm/hooks/hook-interface.js +458 -0
- package/lib/vm/hooks/report-hook.js +331 -0
- package/lib/vm/hooks/verify-hook.js +454 -0
- package/lib/vm/models.js +1003 -0
- package/lib/vm/reconciler.js +855 -0
- package/lib/vm/scanner.js +988 -0
- package/lib/vm/state-schema.js +955 -0
- package/lib/vm/state-store.js +733 -0
- package/lib/vm/tui/components/card.js +339 -0
- package/lib/vm/tui/components/progress-bar.js +368 -0
- package/lib/vm/tui/components/sparkline.js +327 -0
- package/lib/vm/tui/components/status-light.js +294 -0
- package/lib/vm/tui/components/table.js +370 -0
- package/lib/vm/tui/input.js +335 -0
- package/lib/vm/tui/renderer.js +548 -0
- package/lib/vm/tui/screens/kanban-screen.js +397 -0
- package/lib/vm/tui/screens/overview-screen.js +357 -0
- package/lib/vm/tui/screens/quality-screen.js +336 -0
- package/lib/vm/tui/screens/system-screen.js +379 -0
- package/lib/vm/tui/tui.js +805 -0
- package/package.json +1 -0
- package/scripts/cso-analyzer.js +198 -0
- package/scripts/eval-runner.js +359 -0
- package/scripts/i18n-checker.js +109 -0
- package/scripts/linter/activiti-linter.js +272 -0
- package/scripts/linter/prd-linter.js +162 -0
- package/scripts/linter/report-generator.js +207 -0
- package/scripts/linter/run-linters.js +285 -0
- package/scripts/linter/sql-linter.js +166 -0
- package/scripts/token-analyzer.js +162 -0
- package/scripts/vm-test.js +180 -0
- package/skills/core/official-doc-writer/LICENSE +21 -0
- package/skills/core/official-doc-writer/README.md +232 -0
- package/skills/core/official-doc-writer/SKILL.md +475 -0
- package/skills/core/official-doc-writer/_meta.json +1 -0
- package/skills/core/official-doc-writer/document_generator.py +580 -0
- package/skills/core/official-doc-writer/evals/default-evals.json +1 -0
- package/skills/core/official-doc-writer/examples.md +150 -0
- package/skills/core/official-doc-writer/fonts/FONTS_LIST.md +45 -0
- package/skills/core/official-doc-writer/fonts/README.md +141 -0
- package/skills/core/official-doc-writer/fonts/SIMFANG.TTF +0 -0
- package/skills/core/official-doc-writer/fonts/SIMHEI.TTF +0 -0
- package/skills/core/official-doc-writer/fonts/SIMKAI.TTF +0 -0
- package/skills/core/official-doc-writer/fonts/SIMSUN.TTC +0 -0
- package/skills/core/official-doc-writer/fonts//346/226/271/346/255/243/345/260/217/346/240/207/345/256/213GBK.TTF +0 -0
- package/skills/core/official-doc-writer/references/GBT_9704-2012_/345/205/232/346/224/277/346/234/272/345/205/263/345/205/254/346/226/207/346/240/274/345/274/217.md +422 -0
- package/skills/core/official-doc-writer/scripts/__pycache__/generate_official_doc.cpython-313.pyc +0 -0
- package/skills/core/official-doc-writer/scripts/dialog_manager.py +564 -0
- package/skills/core/official-doc-writer/scripts/generate_official_doc.py +252 -0
- package/skills/core/official-doc-writer/scripts/install_fonts.py +390 -0
- package/skills/core/official-doc-writer/scripts/smart_prompts.py +363 -0
- package/skills/core/pdd-ba/SKILL.md +305 -0
- package/skills/core/pdd-ba/_meta.json +1 -0
- package/skills/core/pdd-ba/evals/default-evals.json +1 -0
- package/skills/core/pdd-code-reviewer/SKILL.md +378 -0
- package/skills/core/pdd-code-reviewer/_meta.json +1 -0
- package/skills/core/pdd-code-reviewer/evals/default-evals.json +1 -0
- package/skills/core/pdd-doc-change/SKILL.md +350 -0
- package/skills/core/pdd-doc-change/_meta.json +1 -0
- package/skills/core/pdd-doc-change/evals/default-evals.json +1 -0
- package/skills/core/pdd-doc-gardener/SKILL.md +248 -0
- package/skills/core/pdd-doc-gardener/_meta.json +1 -0
- package/skills/core/pdd-doc-gardener/evals/default-evals.json +1 -0
- package/skills/core/pdd-entropy-reduction/SKILL.md +360 -0
- package/skills/core/pdd-entropy-reduction/_meta.json +1 -0
- package/skills/core/pdd-entropy-reduction/evals/default-evals.json +1 -0
- package/skills/core/pdd-entropy-reduction/references/entropy-report-template.md +287 -0
- package/skills/core/pdd-entropy-reduction/references/golden-principles.md +573 -0
- package/skills/core/pdd-entropy-reduction/scripts/entropy_scan.py +712 -0
- package/skills/core/pdd-extract-features/SKILL.md +320 -0
- package/skills/core/pdd-extract-features/_meta.json +1 -0
- package/skills/core/pdd-extract-features/evals/default-evals.json +1 -0
- package/skills/core/pdd-generate-spec/SKILL.md +418 -0
- package/skills/core/pdd-generate-spec/_meta.json +1 -0
- package/skills/core/pdd-generate-spec/evals/default-evals.json +1 -0
- package/skills/core/pdd-implement-feature/SKILL.md +332 -0
- package/skills/core/pdd-implement-feature/_meta.json +1 -0
- package/skills/core/pdd-implement-feature/evals/default-evals.json +1 -0
- package/skills/core/pdd-main/SKILL.md +540 -0
- package/skills/core/pdd-main/_meta.json +1 -0
- package/skills/core/pdd-main/evals/default-evals.json +1 -0
- package/skills/core/pdd-main/evals/evals.json +215 -0
- package/skills/core/pdd-verify-feature/SKILL.md +474 -0
- package/skills/core/pdd-verify-feature/_meta.json +1 -0
- package/skills/core/pdd-verify-feature/evals/default-evals.json +1 -0
- package/skills/core/pdd-vm/evals/default-evals.json +1 -0
- package/skills/core/traffic-accident-assessor/LICENSE +29 -0
- package/skills/core/traffic-accident-assessor/SKILL.md +439 -0
- package/skills/core/traffic-accident-assessor/evals/evals.json +1 -0
- package/skills/core/traffic-accident-assessor/references/accident-types.md +369 -0
- package/skills/core/traffic-accident-assessor/references/liability-rules.md +287 -0
- package/skills/core/traffic-accident-assessor/references/traffic-laws.md +226 -0
- package/skills/core/traffic-accident-assessor/references//351/253/230/345/260/224/345/244/253/350/257/264/346/230/216/344/271/246.pdf +32576 -106
- package/skills/core/traffic-accident-assessor/scripts/generate_official_statement.py +588 -0
- package/skills/core/traffic-accident-assessor/scripts/generate_report.py +495 -0
- package/skills/core/traffic-accident-assessor/scripts/generate_statement.py +528 -0
- package/skills/core/traffic-accident-assessor.zip +0 -0
- package/skills/entropy/expert-arch-enforcer/SKILL.md +292 -0
- package/skills/entropy/expert-arch-enforcer/_meta.json +1 -0
- package/skills/entropy/expert-arch-enforcer/evals/default-evals.json +1 -0
- package/skills/entropy/expert-auto-refactor/SKILL.md +327 -0
- package/skills/entropy/expert-auto-refactor/_meta.json +1 -0
- package/skills/entropy/expert-auto-refactor/evals/default-evals.json +1 -0
- package/skills/entropy/expert-code-quality/SKILL.md +468 -0
- package/skills/entropy/expert-code-quality/_meta.json +1 -0
- package/skills/entropy/expert-code-quality/evals/default-evals.json +1 -0
- package/skills/entropy/expert-code-quality/evals/evals.json +109 -0
- package/skills/entropy/expert-code-quality/references/code-smells.md +605 -0
- package/skills/entropy/expert-code-quality/references/design-patterns.md +1111 -0
- package/skills/entropy/expert-code-quality/references/refactoring-catalog.md +1281 -0
- package/skills/entropy/expert-code-quality/references/solid-principles.md +524 -0
- package/skills/entropy/expert-entropy-auditor/SKILL.md +276 -0
- package/skills/entropy/expert-entropy-auditor/_meta.json +1 -0
- package/skills/entropy/expert-entropy-auditor/evals/default-evals.json +1 -0
- package/skills/expert/expert-activiti/SKILL.md +497 -0
- package/skills/expert/expert-activiti/_meta.json +1 -0
- package/skills/expert/expert-mysql/SKILL.md +832 -0
- package/skills/expert/expert-mysql/_meta.json +1 -0
- package/skills/expert/expert-performance/SKILL.md +379 -0
- package/skills/expert/expert-performance/_meta.json +1 -0
- package/skills/expert/expert-performance/evals/default-evals.json +1 -0
- package/skills/expert/expert-ruoyi/SKILL.md +472 -0
- package/skills/expert/expert-ruoyi/_meta.json +1 -0
- package/skills/expert/expert-security/SKILL.md +1341 -0
- package/skills/expert/expert-security/_meta.json +1 -0
- package/skills/expert/expert-security/evals/default-evals.json +1 -0
- package/skills/expert/software-architect/SKILL.md +350 -0
- package/skills/expert/software-architect/_meta.json +1 -0
- package/skills/expert/software-engineer/SKILL.md +437 -0
- package/skills/expert/software-engineer/_meta.json +1 -0
- package/skills/expert/software-engineer/architecture.md +130 -0
- package/skills/expert/software-engineer/patterns.md +151 -0
- package/skills/expert/software-engineer/testing.md +135 -0
- package/skills/expert/system-architect/SKILL.md +628 -0
- package/skills/expert/system-architect/_meta.json +1 -0
- package/skills/expert/system-architect/assets/templates/ARCHITECTURE.md +25 -0
- package/skills/expert/system-architect/assets/templates/README.md +44 -0
- package/skills/expert/system-architect/references/js-ts-standards.md +18 -0
- package/skills/expert/system-architect/references/python-standards.md +19 -0
- package/skills/expert/system-architect/references/scaffolding.md +61 -0
- package/skills/expert/system-architect/references/security-checklist.md +21 -0
- package/skills/openspec/openspec-apply-change/SKILL.md +156 -0
- package/skills/openspec/openspec-apply-change/_meta.json +1 -0
- package/skills/openspec/openspec-archive-change/SKILL.md +114 -0
- package/skills/openspec/openspec-archive-change/_meta.json +1 -0
- package/skills/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
- package/skills/openspec/openspec-bulk-archive-change/_meta.json +1 -0
- package/skills/openspec/openspec-continue-change/SKILL.md +118 -0
- package/skills/openspec/openspec-continue-change/_meta.json +1 -0
- package/skills/openspec/openspec-explore/SKILL.md +288 -0
- package/skills/openspec/openspec-explore/_meta.json +1 -0
- package/skills/openspec/openspec-ff-change/SKILL.md +101 -0
- package/skills/openspec/openspec-ff-change/_meta.json +1 -0
- package/skills/openspec/openspec-new-change/SKILL.md +74 -0
- package/skills/openspec/openspec-new-change/_meta.json +1 -0
- package/skills/openspec/openspec-onboard/SKILL.md +554 -0
- package/skills/openspec/openspec-onboard/_meta.json +1 -0
- package/skills/openspec/openspec-sync-specs/SKILL.md +138 -0
- package/skills/openspec/openspec-sync-specs/_meta.json +1 -0
- package/skills/openspec/openspec-verify-change/SKILL.md +168 -0
- package/skills/openspec/openspec-verify-change/_meta.json +1 -0
- package/skills/pr/pdd-multi-review/SKILL.md +534 -0
- package/skills/pr/pdd-multi-review/_meta.json +1 -0
- package/skills/pr/pdd-pr-batch/SKILL.md +303 -0
- package/skills/pr/pdd-pr-batch/_meta.json +1 -0
- package/skills/pr/pdd-pr-create/SKILL.md +344 -0
- package/skills/pr/pdd-pr-create/_meta.json +1 -0
- package/skills/pr/pdd-pr-merge/SKILL.md +286 -0
- package/skills/pr/pdd-pr-merge/_meta.json +1 -0
- package/skills/pr/pdd-pr-review/SKILL.md +217 -0
- package/skills/pr/pdd-pr-review/_meta.json +1 -0
- package/skills/pr/pdd-task-manager/SKILL.md +636 -0
- package/skills/pr/pdd-task-manager/_meta.json +1 -0
- package/skills/pr/pdd-template-engine/SKILL.md +306 -0
- package/skills/pr/pdd-template-engine/_meta.json +1 -0
- package/templates/behavior-shaping/iron-law-template.md +87 -0
- package/templates/behavior-shaping/rationalization-template.md +62 -0
- package/templates/behavior-shaping/red-flags-template.md +70 -0
- package/templates/bilingual-template.md +139 -0
- package/templates/config/default.yaml +47 -0
- package/templates/project/default/README.md +31 -0
- package/templates/project/frontend/README.md +46 -0
- package/templates/project/java/README.md +48 -0
|
@@ -0,0 +1,1281 @@
|
|
|
1
|
+
# Refactoring Catalog
|
|
2
|
+
|
|
3
|
+
A comprehensive catalog of refactoring techniques from Martin Fowler's "Refactoring: Improving the Design of Existing Code".
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Composing Methods](#1-composing-methods)
|
|
8
|
+
2. [Moving Features Between Objects](#2-moving-features-between-objects)
|
|
9
|
+
3. [Organizing Data](#3-organizing-data)
|
|
10
|
+
4. [Simplifying Conditional Expressions](#4-simplifying-conditional-expressions)
|
|
11
|
+
5. [Simplifying Method Calls](#5-simplifying-method-calls)
|
|
12
|
+
6. [Dealing with Generalization](#6-dealing-with-generalization)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Composing Methods
|
|
17
|
+
|
|
18
|
+
### Extract Method
|
|
19
|
+
|
|
20
|
+
**Problem**: You have a code fragment that can be grouped together.
|
|
21
|
+
|
|
22
|
+
**Solution**: Turn the fragment into a method whose name explains the purpose of the method.
|
|
23
|
+
|
|
24
|
+
```java
|
|
25
|
+
// Before
|
|
26
|
+
void printOwing() {
|
|
27
|
+
System.out.println("***********************");
|
|
28
|
+
System.out.println("*** Customer Owes ***");
|
|
29
|
+
System.out.println("***********************");
|
|
30
|
+
// ... more code
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// After
|
|
34
|
+
void printOwing() {
|
|
35
|
+
printBanner();
|
|
36
|
+
// ... more code
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void printBanner() {
|
|
40
|
+
System.out.println("***********************");
|
|
41
|
+
System.out.println("*** Customer Owes ***");
|
|
42
|
+
System.out.println("***********************");
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Mechanics**:
|
|
47
|
+
1. Create a new method, name it after the intention
|
|
48
|
+
2. Copy the extracted code to the new method
|
|
49
|
+
3. Replace the original code with a call to the method
|
|
50
|
+
4. Compile and test
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### Inline Method
|
|
55
|
+
|
|
56
|
+
**Problem**: A method's body is just as clear as its name.
|
|
57
|
+
|
|
58
|
+
**Solution**: Put the method's body into the body of its callers and remove the method.
|
|
59
|
+
|
|
60
|
+
```java
|
|
61
|
+
// Before
|
|
62
|
+
int getRating() {
|
|
63
|
+
return moreThanFiveLateDeliveries() ? 2 : 1;
|
|
64
|
+
}
|
|
65
|
+
boolean moreThanFiveLateDeliveries() {
|
|
66
|
+
return numberOfLateDeliveries > 5;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// After
|
|
70
|
+
int getRating() {
|
|
71
|
+
return numberOfLateDeliveries > 5 ? 2 : 1;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Extract Variable
|
|
78
|
+
|
|
79
|
+
**Problem**: You have a complex expression.
|
|
80
|
+
|
|
81
|
+
**Solution**: Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose.
|
|
82
|
+
|
|
83
|
+
```java
|
|
84
|
+
// Before
|
|
85
|
+
if (platform.toUpperCase().indexOf("MAC") > -1 &&
|
|
86
|
+
browser.toUpperCase().indexOf("IE") > -1 &&
|
|
87
|
+
wasInitialized() && resize > 0) {
|
|
88
|
+
// do something
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// After
|
|
92
|
+
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
|
|
93
|
+
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
|
|
94
|
+
final boolean wasResized = resize > 0;
|
|
95
|
+
|
|
96
|
+
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
|
|
97
|
+
// do something
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### Replace Temp with Query
|
|
104
|
+
|
|
105
|
+
**Problem**: You are using a temporary variable to hold the result of an expression.
|
|
106
|
+
|
|
107
|
+
**Solution**: Extract the expression into a method. Replace all references to the temp with the expression. The new method can then be used in other methods.
|
|
108
|
+
|
|
109
|
+
```java
|
|
110
|
+
// Before
|
|
111
|
+
double getPrice() {
|
|
112
|
+
int basePrice = quantity * itemPrice;
|
|
113
|
+
double discountFactor;
|
|
114
|
+
if (basePrice > 1000) discountFactor = 0.95;
|
|
115
|
+
else discountFactor = 0.98;
|
|
116
|
+
return basePrice * discountFactor;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// After
|
|
120
|
+
double getPrice() {
|
|
121
|
+
return basePrice() * discountFactor();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private int basePrice() {
|
|
125
|
+
return quantity * itemPrice;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private double discountFactor() {
|
|
129
|
+
if (basePrice() > 1000) return 0.95;
|
|
130
|
+
else return 0.98;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### Introduce Explaining Variable
|
|
137
|
+
|
|
138
|
+
**Problem**: You have a complicated expression.
|
|
139
|
+
|
|
140
|
+
**Solution**: Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose.
|
|
141
|
+
|
|
142
|
+
```java
|
|
143
|
+
// Before
|
|
144
|
+
if ((platform.toUpperCase().indexOf("MAC") > -1) &&
|
|
145
|
+
(browser.toUpperCase().indexOf("IE") > -1) &&
|
|
146
|
+
wasInitialized() && resize > 0) {
|
|
147
|
+
// do something
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// After
|
|
151
|
+
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
|
|
152
|
+
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
|
|
153
|
+
final boolean wasResized = resize > 0;
|
|
154
|
+
|
|
155
|
+
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
|
|
156
|
+
// do something
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### Split Temporary Variable
|
|
163
|
+
|
|
164
|
+
**Problem**: You have a temporary variable assigned to more than once, but is not a loop variable nor a collecting temporary variable.
|
|
165
|
+
|
|
166
|
+
**Solution**: Make a separate temporary variable for each assignment.
|
|
167
|
+
|
|
168
|
+
```java
|
|
169
|
+
// Before
|
|
170
|
+
double temp = 2 * (height + width);
|
|
171
|
+
System.out.println(temp);
|
|
172
|
+
temp = height * width;
|
|
173
|
+
System.out.println(temp);
|
|
174
|
+
|
|
175
|
+
// After
|
|
176
|
+
final double perimeter = 2 * (height + width);
|
|
177
|
+
System.out.println(perimeter);
|
|
178
|
+
final double area = height * width;
|
|
179
|
+
System.out.println(area);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Remove Assignments to Parameters
|
|
185
|
+
|
|
186
|
+
**Problem**: The code assigns to a parameter.
|
|
187
|
+
|
|
188
|
+
**Solution**: Use a temporary variable instead.
|
|
189
|
+
|
|
190
|
+
```java
|
|
191
|
+
// Before
|
|
192
|
+
int discount(int inputVal, int quantity, int yearToDate) {
|
|
193
|
+
if (inputVal > 50) inputVal -= 2;
|
|
194
|
+
// ...
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// After
|
|
198
|
+
int discount(int inputVal, int quantity, int yearToDate) {
|
|
199
|
+
int result = inputVal;
|
|
200
|
+
if (inputVal > 50) result -= 2;
|
|
201
|
+
// ...
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Replace Method with Method Object
|
|
208
|
+
|
|
209
|
+
**Problem**: You have a long method that uses local variables in such a way that you cannot apply Extract Method.
|
|
210
|
+
|
|
211
|
+
**Solution**: Turn the method into its own object so that all the local variables become fields on that object. You can then decompose the method into other methods on the same object.
|
|
212
|
+
|
|
213
|
+
```java
|
|
214
|
+
// Before
|
|
215
|
+
class Order {
|
|
216
|
+
double price() {
|
|
217
|
+
double primaryBasePrice;
|
|
218
|
+
double secondaryBasePrice;
|
|
219
|
+
double tertiaryBasePrice;
|
|
220
|
+
// long computation
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// After
|
|
225
|
+
class Order {
|
|
226
|
+
double price() {
|
|
227
|
+
return new PriceCalculator(this).compute();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
class PriceCalculator {
|
|
232
|
+
private double primaryBasePrice;
|
|
233
|
+
private double secondaryBasePrice;
|
|
234
|
+
private double tertiaryBasePrice;
|
|
235
|
+
|
|
236
|
+
PriceCalculator(Order order) {
|
|
237
|
+
// initialize fields
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
double compute() {
|
|
241
|
+
// computation
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### Substitute Algorithm
|
|
249
|
+
|
|
250
|
+
**Problem**: You want to replace an algorithm with one that is clearer.
|
|
251
|
+
|
|
252
|
+
**Solution**: Replace the body of the method that implements the algorithm with a new algorithm.
|
|
253
|
+
|
|
254
|
+
```java
|
|
255
|
+
// Before
|
|
256
|
+
String foundPerson(String[] people) {
|
|
257
|
+
for (int i = 0; i < people.length; i++) {
|
|
258
|
+
if (people[i].equals("Don")) {
|
|
259
|
+
return "Don";
|
|
260
|
+
}
|
|
261
|
+
if (people[i].equals("John")) {
|
|
262
|
+
return "John";
|
|
263
|
+
}
|
|
264
|
+
if (people[i].equals("Kent")) {
|
|
265
|
+
return "Kent";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return "";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// After
|
|
272
|
+
String foundPerson(String[] people) {
|
|
273
|
+
List<String> candidates = Arrays.asList("Don", "John", "Kent");
|
|
274
|
+
for (String person : people) {
|
|
275
|
+
if (candidates.contains(person)) {
|
|
276
|
+
return person;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 2. Moving Features Between Objects
|
|
286
|
+
|
|
287
|
+
### Move Method
|
|
288
|
+
|
|
289
|
+
**Problem**: A method is, or will be, using or used by more features of another class than the class on which it is defined.
|
|
290
|
+
|
|
291
|
+
**Solution**: Create a new method with a similar body in the class it uses most. Either turn the old method into a simple delegation, or remove it altogether.
|
|
292
|
+
|
|
293
|
+
```java
|
|
294
|
+
// Before
|
|
295
|
+
class Account {
|
|
296
|
+
double overdraftCharge() {
|
|
297
|
+
if (type.isPremium()) {
|
|
298
|
+
double result = 10;
|
|
299
|
+
if (daysOverdrawn > 7) {
|
|
300
|
+
result += (daysOverdrawn - 7) * 0.85;
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
return daysOverdrawn * 1.75;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// After
|
|
309
|
+
class AccountType {
|
|
310
|
+
double overdraftCharge(int daysOverdrawn) {
|
|
311
|
+
if (isPremium()) {
|
|
312
|
+
double result = 10;
|
|
313
|
+
if (daysOverdrawn > 7) {
|
|
314
|
+
result += (daysOverdrawn - 7) * 0.85;
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
return daysOverdrawn * 1.75;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
### Move Field
|
|
326
|
+
|
|
327
|
+
**Problem**: A field is, or will be, used by another class more than the class on which it is defined.
|
|
328
|
+
|
|
329
|
+
**Solution**: Create a new field in the target class, and change all its users.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
### Extract Class
|
|
334
|
+
|
|
335
|
+
**Problem**: One class does work that should be done by two.
|
|
336
|
+
|
|
337
|
+
**Solution**: Create a new class and move the relevant fields and methods from the old class into the new class.
|
|
338
|
+
|
|
339
|
+
```java
|
|
340
|
+
// Before
|
|
341
|
+
class Person {
|
|
342
|
+
private String name;
|
|
343
|
+
private String officeAreaCode;
|
|
344
|
+
private String officeNumber;
|
|
345
|
+
|
|
346
|
+
String getTelephoneNumber() {
|
|
347
|
+
return "(" + officeAreaCode + ") " + officeNumber;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// After
|
|
352
|
+
class Person {
|
|
353
|
+
private String name;
|
|
354
|
+
private TelephoneNumber officeTelephone = new TelephoneNumber();
|
|
355
|
+
|
|
356
|
+
String getTelephoneNumber() {
|
|
357
|
+
return officeTelephone.getTelephoneNumber();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
class TelephoneNumber {
|
|
362
|
+
private String areaCode;
|
|
363
|
+
private String number;
|
|
364
|
+
|
|
365
|
+
String getTelephoneNumber() {
|
|
366
|
+
return "(" + areaCode + ") " + number;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### Inline Class
|
|
374
|
+
|
|
375
|
+
**Problem**: A class isn't doing very much.
|
|
376
|
+
|
|
377
|
+
**Solution**: Move all its features into another class and delete it.
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### Hide Delegate
|
|
382
|
+
|
|
383
|
+
**Problem**: A client is calling a delegate class of an object through an accessor.
|
|
384
|
+
|
|
385
|
+
**Solution**: Create methods on the server to hide the delegate.
|
|
386
|
+
|
|
387
|
+
```java
|
|
388
|
+
// Before
|
|
389
|
+
class Person {
|
|
390
|
+
Department department;
|
|
391
|
+
|
|
392
|
+
Department getDepartment() {
|
|
393
|
+
return department;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Client code
|
|
398
|
+
manager = john.getDepartment().getManager();
|
|
399
|
+
|
|
400
|
+
// After
|
|
401
|
+
class Person {
|
|
402
|
+
Department department;
|
|
403
|
+
|
|
404
|
+
Person getManager() {
|
|
405
|
+
return department.getManager();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Client code
|
|
410
|
+
manager = john.getManager();
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### Remove Middle Man
|
|
416
|
+
|
|
417
|
+
**Problem**: A class is doing too much simple delegation.
|
|
418
|
+
|
|
419
|
+
**Solution**: Get the client to call the delegate directly.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
### Introduce Foreign Method
|
|
424
|
+
|
|
425
|
+
**Problem**: A server class you are using needs an additional method, but you can't modify the class.
|
|
426
|
+
|
|
427
|
+
**Solution**: Create a method in the client class with an instance of the server class as its first argument.
|
|
428
|
+
|
|
429
|
+
```java
|
|
430
|
+
// Before
|
|
431
|
+
Date newStart = new Date(previousEnd.getYear(),
|
|
432
|
+
previousEnd.getMonth(), previousEnd.getDate() + 1);
|
|
433
|
+
|
|
434
|
+
// After
|
|
435
|
+
Date newStart = nextDay(previousEnd);
|
|
436
|
+
|
|
437
|
+
private static Date nextDay(Date arg) {
|
|
438
|
+
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### Introduce Local Extension
|
|
445
|
+
|
|
446
|
+
**Problem**: A server class you are using needs several additional methods, but you can't modify the class.
|
|
447
|
+
|
|
448
|
+
**Solution**: Create a new class containing these extra methods. Make this extension class a subclass or wrapper of the original.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 3. Organizing Data
|
|
453
|
+
|
|
454
|
+
### Self Encapsulate Field
|
|
455
|
+
|
|
456
|
+
**Problem**: You are accessing a field directly, but the coupling to the field is becoming awkward.
|
|
457
|
+
|
|
458
|
+
**Solution**: Create getting and setting methods for the field and use only those to access the field.
|
|
459
|
+
|
|
460
|
+
```java
|
|
461
|
+
// Before
|
|
462
|
+
private int low, high;
|
|
463
|
+
boolean includes(int arg) {
|
|
464
|
+
return arg >= low && arg <= high;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// After
|
|
468
|
+
private int low, high;
|
|
469
|
+
boolean includes(int arg) {
|
|
470
|
+
return arg >= getLow() && arg <= getHigh();
|
|
471
|
+
}
|
|
472
|
+
int getLow() { return low; }
|
|
473
|
+
int getHigh() { return high; }
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### Replace Data Value with Object
|
|
479
|
+
|
|
480
|
+
**Problem**: You have a data item that needs additional data or behavior.
|
|
481
|
+
|
|
482
|
+
**Solution**: Turn the data item into an object.
|
|
483
|
+
|
|
484
|
+
```java
|
|
485
|
+
// Before
|
|
486
|
+
class Order {
|
|
487
|
+
private String customer;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// After
|
|
491
|
+
class Order {
|
|
492
|
+
private Customer customer;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
class Customer {
|
|
496
|
+
private String name;
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### Change Value to Reference
|
|
503
|
+
|
|
504
|
+
**Problem**: You have a class with many equal instances that you want to replace with a single object.
|
|
505
|
+
|
|
506
|
+
**Solution**: Turn the object into a reference object.
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
### Change Reference to Value
|
|
511
|
+
|
|
512
|
+
**Problem**: You have a reference object that is small, immutable, and awkward to manage.
|
|
513
|
+
|
|
514
|
+
**Solution**: Turn the reference object into a value object.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
### Replace Array with Object
|
|
519
|
+
|
|
520
|
+
**Problem**: You have an array in which certain elements mean different things.
|
|
521
|
+
|
|
522
|
+
**Solution**: Replace the array with an object that has a field for each element.
|
|
523
|
+
|
|
524
|
+
```java
|
|
525
|
+
// Before
|
|
526
|
+
String[] row = new String[3];
|
|
527
|
+
row[0] = "Liverpool";
|
|
528
|
+
row[1] = "15";
|
|
529
|
+
|
|
530
|
+
// After
|
|
531
|
+
Performance row = new Performance();
|
|
532
|
+
row.setName("Liverpool");
|
|
533
|
+
row.setWins("15");
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
### Duplicate Observed Data
|
|
539
|
+
|
|
540
|
+
**Problem**: You have domain data that only needs to be in a GUI control, but domain methods need access to it.
|
|
541
|
+
|
|
542
|
+
**Solution**: Copy the data to a domain object and set up an observer to synchronize the two.
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### Change Unidirectional Association to Bidirectional
|
|
547
|
+
|
|
548
|
+
**Problem**: You have two classes that need to use each other's features, but there is only a one-way link.
|
|
549
|
+
|
|
550
|
+
**Solution**: Add back pointers, and change modifiers to update both sets.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
### Change Bidirectional Association to Unidirectional
|
|
555
|
+
|
|
556
|
+
**Problem**: You have a bidirectional association between two classes, but one of the classes no longer needs features from the other.
|
|
557
|
+
|
|
558
|
+
**Solution**: Drop the unnecessary end of the association.
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
### Replace Magic Number with Symbolic Constant
|
|
563
|
+
|
|
564
|
+
**Problem**: You have a literal number with a particular meaning.
|
|
565
|
+
|
|
566
|
+
**Solution**: Create a constant, name it after the meaning, and replace the number with it.
|
|
567
|
+
|
|
568
|
+
```java
|
|
569
|
+
// Before
|
|
570
|
+
double potentialEnergy(double mass, double height) {
|
|
571
|
+
return mass * height * 9.81;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// After
|
|
575
|
+
static final double GRAVITATIONAL_CONSTANT = 9.81;
|
|
576
|
+
|
|
577
|
+
double potentialEnergy(double mass, double height) {
|
|
578
|
+
return mass * height * GRAVITATIONAL_CONSTANT;
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
### Encapsulate Field
|
|
585
|
+
|
|
586
|
+
**Problem**: There is a public field.
|
|
587
|
+
|
|
588
|
+
**Solution**: Make it private and provide accessors.
|
|
589
|
+
|
|
590
|
+
```java
|
|
591
|
+
// Before
|
|
592
|
+
public String name;
|
|
593
|
+
|
|
594
|
+
// After
|
|
595
|
+
private String name;
|
|
596
|
+
public String getName() { return name; }
|
|
597
|
+
public void setName(String name) { this.name = name; }
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
### Encapsulate Collection
|
|
603
|
+
|
|
604
|
+
**Problem**: A method returns a collection that is a member of the class.
|
|
605
|
+
|
|
606
|
+
**Solution**: Have it return a read-only view and provide add/remove methods.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## 4. Simplifying Conditional Expressions
|
|
611
|
+
|
|
612
|
+
### Decompose Conditional
|
|
613
|
+
|
|
614
|
+
**Problem**: You have a complicated conditional (if-then-else) statement.
|
|
615
|
+
|
|
616
|
+
**Solution**: Extract methods from the condition, then part, and else part.
|
|
617
|
+
|
|
618
|
+
```java
|
|
619
|
+
// Before
|
|
620
|
+
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
|
|
621
|
+
charge = quantity * winterRate + winterServiceCharge;
|
|
622
|
+
} else {
|
|
623
|
+
charge = quantity * summerRate;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// After
|
|
627
|
+
if (isSummer(date)) {
|
|
628
|
+
charge = summerCharge(quantity);
|
|
629
|
+
} else {
|
|
630
|
+
charge = winterCharge(quantity);
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
### Consolidate Conditional Expression
|
|
637
|
+
|
|
638
|
+
**Problem**: You have a sequence of conditional tests with the same result.
|
|
639
|
+
|
|
640
|
+
**Solution**: Combine them into a single conditional expression and extract it.
|
|
641
|
+
|
|
642
|
+
```java
|
|
643
|
+
// Before
|
|
644
|
+
double disabilityAmount() {
|
|
645
|
+
if (seniority < 2) return 0;
|
|
646
|
+
if (monthsDisabled > 12) return 0;
|
|
647
|
+
if (isPartTime) return 0;
|
|
648
|
+
// compute the disability amount
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// After
|
|
652
|
+
double disabilityAmount() {
|
|
653
|
+
if (isNotEligibleForDisability()) return 0;
|
|
654
|
+
// compute the disability amount
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
boolean isNotEligibleForDisability() {
|
|
658
|
+
return seniority < 2 || monthsDisabled > 12 || isPartTime;
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
### Consolidate Duplicate Conditional Fragments
|
|
665
|
+
|
|
666
|
+
**Problem**: The same fragment of code is in all branches of a conditional expression.
|
|
667
|
+
|
|
668
|
+
**Solution**: Move it outside of the expression.
|
|
669
|
+
|
|
670
|
+
```java
|
|
671
|
+
// Before
|
|
672
|
+
if (isSpecialDeal()) {
|
|
673
|
+
total = price * 0.95;
|
|
674
|
+
send();
|
|
675
|
+
} else {
|
|
676
|
+
total = price * 0.98;
|
|
677
|
+
send();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// After
|
|
681
|
+
if (isSpecialDeal()) {
|
|
682
|
+
total = price * 0.95;
|
|
683
|
+
} else {
|
|
684
|
+
total = price * 0.98;
|
|
685
|
+
}
|
|
686
|
+
send();
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
### Remove Control Flag
|
|
692
|
+
|
|
693
|
+
**Problem**: You have a variable that is acting as a control flag for a series of boolean expressions.
|
|
694
|
+
|
|
695
|
+
**Solution**: Use a break or return instead.
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
### Replace Nested Conditional with Guard Clauses
|
|
700
|
+
|
|
701
|
+
**Problem**: A method has conditional behavior that does not make clear the normal path of execution.
|
|
702
|
+
|
|
703
|
+
**Solution**: Use guard clauses for all the special cases.
|
|
704
|
+
|
|
705
|
+
```java
|
|
706
|
+
// Before
|
|
707
|
+
double getPayAmount() {
|
|
708
|
+
double result;
|
|
709
|
+
if (isDead) {
|
|
710
|
+
result = deadAmount();
|
|
711
|
+
} else {
|
|
712
|
+
if (isSeparated) {
|
|
713
|
+
result = separatedAmount();
|
|
714
|
+
} else {
|
|
715
|
+
if (isRetired) {
|
|
716
|
+
result = retiredAmount();
|
|
717
|
+
} else {
|
|
718
|
+
result = normalPayAmount();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// After
|
|
726
|
+
double getPayAmount() {
|
|
727
|
+
if (isDead) return deadAmount();
|
|
728
|
+
if (isSeparated) return separatedAmount();
|
|
729
|
+
if (isRetired) return retiredAmount();
|
|
730
|
+
return normalPayAmount();
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
### Replace Conditional with Polymorphism
|
|
737
|
+
|
|
738
|
+
**Problem**: You have a conditional that chooses different behavior depending on the type of an object.
|
|
739
|
+
|
|
740
|
+
**Solution**: Move each leg of the conditional to an overriding method in a subclass. Make the original method abstract.
|
|
741
|
+
|
|
742
|
+
```java
|
|
743
|
+
// Before
|
|
744
|
+
double getSpeed() {
|
|
745
|
+
switch (type) {
|
|
746
|
+
case EUROPEAN:
|
|
747
|
+
return getBaseSpeed();
|
|
748
|
+
case AFRICAN:
|
|
749
|
+
return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
|
|
750
|
+
case NORWEGIAN_BLUE:
|
|
751
|
+
return isNailed ? 0 : getBaseSpeed(voltage);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// After
|
|
756
|
+
abstract class Bird {
|
|
757
|
+
abstract double getSpeed();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
class EuropeanBird extends Bird {
|
|
761
|
+
double getSpeed() { return getBaseSpeed(); }
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
class AfricanBird extends Bird {
|
|
765
|
+
double getSpeed() { return getBaseSpeed() - getLoadFactor() * numberOfCoconuts; }
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
### Introduce Null Object
|
|
772
|
+
|
|
773
|
+
**Problem**: You have repeated checks for a null value.
|
|
774
|
+
|
|
775
|
+
**Solution**: Replace the null value with a null object.
|
|
776
|
+
|
|
777
|
+
```java
|
|
778
|
+
// Before
|
|
779
|
+
if (customer != null) {
|
|
780
|
+
plan = customer.getPlan();
|
|
781
|
+
} else {
|
|
782
|
+
plan = Plan.NULL;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// After
|
|
786
|
+
interface Customer {
|
|
787
|
+
Plan getPlan();
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
class NullCustomer implements Customer {
|
|
791
|
+
Plan getPlan() { return Plan.NULL; }
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Usage
|
|
795
|
+
plan = customer.getPlan(); // No null check needed
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
### Introduce Assertion
|
|
801
|
+
|
|
802
|
+
**Problem**: A section of code assumes something about the state of the program.
|
|
803
|
+
|
|
804
|
+
**Solution**: Make the assumption explicit with an assertion.
|
|
805
|
+
|
|
806
|
+
```java
|
|
807
|
+
// Before
|
|
808
|
+
double getExpenseLimit() {
|
|
809
|
+
// should have either expense limit or a primary project
|
|
810
|
+
return (expenseLimit != NULL_EXPENSE) ?
|
|
811
|
+
expenseLimit : primaryProject.getMemberExpenseLimit();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// After
|
|
815
|
+
double getExpenseLimit() {
|
|
816
|
+
Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);
|
|
817
|
+
return (expenseLimit != NULL_EXPENSE) ?
|
|
818
|
+
expenseLimit : primaryProject.getMemberExpenseLimit();
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
---
|
|
823
|
+
|
|
824
|
+
## 5. Simplifying Method Calls
|
|
825
|
+
|
|
826
|
+
### Rename Method
|
|
827
|
+
|
|
828
|
+
**Problem**: The name of a method does not reveal its purpose.
|
|
829
|
+
|
|
830
|
+
**Solution**: Rename the method.
|
|
831
|
+
|
|
832
|
+
```java
|
|
833
|
+
// Before
|
|
834
|
+
String getNm() { return name; }
|
|
835
|
+
|
|
836
|
+
// After
|
|
837
|
+
String getName() { return name; }
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
### Add Parameter
|
|
843
|
+
|
|
844
|
+
**Problem**: A method needs more information from its caller.
|
|
845
|
+
|
|
846
|
+
**Solution**: Add a parameter for an object that can pass on this information.
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
### Remove Parameter
|
|
851
|
+
|
|
852
|
+
**Problem**: A parameter is no longer used by the method body.
|
|
853
|
+
|
|
854
|
+
**Solution**: Remove it.
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
### Separate Query from Modifier
|
|
859
|
+
|
|
860
|
+
**Problem**: You have a method that returns a value but also changes the state of an object.
|
|
861
|
+
|
|
862
|
+
**Solution**: Create two methods, one for the query and one for the modification.
|
|
863
|
+
|
|
864
|
+
```java
|
|
865
|
+
// Before
|
|
866
|
+
String getNewMissions() {
|
|
867
|
+
String result = "";
|
|
868
|
+
for (Mission mission : missions) {
|
|
869
|
+
if (mission.isNew()) {
|
|
870
|
+
result += mission.getName() + "\n";
|
|
871
|
+
mission.markAsRead();
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return result;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// After
|
|
878
|
+
String getNewMissions() {
|
|
879
|
+
return missions.stream()
|
|
880
|
+
.filter(Mission::isNew)
|
|
881
|
+
.map(Mission::getName)
|
|
882
|
+
.collect(Collectors.joining("\n"));
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
void markNewMissionsAsRead() {
|
|
886
|
+
missions.stream()
|
|
887
|
+
.filter(Mission::isNew)
|
|
888
|
+
.forEach(Mission::markAsRead);
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
---
|
|
893
|
+
|
|
894
|
+
### Parameterize Method
|
|
895
|
+
|
|
896
|
+
**Problem**: Several methods do similar things but with different values contained in the method body.
|
|
897
|
+
|
|
898
|
+
**Solution**: Create a single method that uses a parameter for the different values.
|
|
899
|
+
|
|
900
|
+
```java
|
|
901
|
+
// Before
|
|
902
|
+
void tenPercentRaise() { salary *= 1.1; }
|
|
903
|
+
void fivePercentRaise() { salary *= 1.05; }
|
|
904
|
+
|
|
905
|
+
// After
|
|
906
|
+
void raise(double factor) { salary *= (1 + factor); }
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
---
|
|
910
|
+
|
|
911
|
+
### Replace Parameter with Explicit Methods
|
|
912
|
+
|
|
913
|
+
**Problem**: You have a method that runs different code depending on the values of an enumerated parameter.
|
|
914
|
+
|
|
915
|
+
**Solution**: Create a separate method for each value of the parameter.
|
|
916
|
+
|
|
917
|
+
```java
|
|
918
|
+
// Before
|
|
919
|
+
void setValue(String name, int value) {
|
|
920
|
+
if (name.equals("height")) {
|
|
921
|
+
height = value;
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (name.equals("width")) {
|
|
925
|
+
width = value;
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// After
|
|
931
|
+
void setHeight(int arg) { height = arg; }
|
|
932
|
+
void setWidth(int arg) { width = arg; }
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
---
|
|
936
|
+
|
|
937
|
+
### Preserve Whole Object
|
|
938
|
+
|
|
939
|
+
**Problem**: You are getting several values from an object and passing these values as parameters in a method call.
|
|
940
|
+
|
|
941
|
+
**Solution**: Instead, pass the whole object.
|
|
942
|
+
|
|
943
|
+
```java
|
|
944
|
+
// Before
|
|
945
|
+
int low = daysTempRange().getLow();
|
|
946
|
+
int high = daysTempRange().getHigh();
|
|
947
|
+
boolean withinPlan = plan.withinRange(low, high);
|
|
948
|
+
|
|
949
|
+
// After
|
|
950
|
+
boolean withinPlan = plan.withinRange(daysTempRange());
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
|
|
955
|
+
### Replace Parameter with Method
|
|
956
|
+
|
|
957
|
+
**Problem**: An object invokes a method, then passes the result as a parameter for a method. The receiver can also invoke this method.
|
|
958
|
+
|
|
959
|
+
**Solution**: Remove the parameter and let the receiver invoke the method.
|
|
960
|
+
|
|
961
|
+
```java
|
|
962
|
+
// Before
|
|
963
|
+
int basePrice = quantity * itemPrice;
|
|
964
|
+
discountLevel = getDiscountLevel();
|
|
965
|
+
double finalPrice = discountedPrice(basePrice, discountLevel);
|
|
966
|
+
|
|
967
|
+
// After
|
|
968
|
+
double finalPrice = discountedPrice();
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
---
|
|
972
|
+
|
|
973
|
+
### Introduce Parameter Object
|
|
974
|
+
|
|
975
|
+
**Problem**: You have a group of parameters that naturally go together.
|
|
976
|
+
|
|
977
|
+
**Solution**: Replace them with an object.
|
|
978
|
+
|
|
979
|
+
```java
|
|
980
|
+
// Before
|
|
981
|
+
void printReport(Date start, Date end, String title, String author);
|
|
982
|
+
|
|
983
|
+
// After
|
|
984
|
+
class ReportParameters {
|
|
985
|
+
Date start;
|
|
986
|
+
Date end;
|
|
987
|
+
String title;
|
|
988
|
+
String author;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
void printReport(ReportParameters params);
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
### Remove Setting Method
|
|
997
|
+
|
|
998
|
+
**Problem**: The value of a field should be set only when it is initialized, and not changed at any other time.
|
|
999
|
+
|
|
1000
|
+
**Solution**: Remove any setting method for that field.
|
|
1001
|
+
|
|
1002
|
+
---
|
|
1003
|
+
|
|
1004
|
+
### Hide Method
|
|
1005
|
+
|
|
1006
|
+
**Problem**: A method is not used by any other class.
|
|
1007
|
+
|
|
1008
|
+
**Solution**: Make the method private.
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
### Replace Constructor with Factory Method
|
|
1013
|
+
|
|
1014
|
+
**Problem**: You want to do more than simple construction when you create an object.
|
|
1015
|
+
|
|
1016
|
+
**Solution**: Replace the constructor with a factory method.
|
|
1017
|
+
|
|
1018
|
+
```java
|
|
1019
|
+
// Before
|
|
1020
|
+
Employee(int type) {
|
|
1021
|
+
this.type = type;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// After
|
|
1025
|
+
static Employee create(int type) {
|
|
1026
|
+
switch (type) {
|
|
1027
|
+
case ENGINEER: return new Engineer();
|
|
1028
|
+
case SALESMAN: return new Salesman();
|
|
1029
|
+
case MANAGER: return new Manager();
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
---
|
|
1035
|
+
|
|
1036
|
+
### Encapsulate Downcast
|
|
1037
|
+
|
|
1038
|
+
**Problem**: A method returns an object that needs to be downcasted by its callers.
|
|
1039
|
+
|
|
1040
|
+
**Solution**: Move the downcast to within the method.
|
|
1041
|
+
|
|
1042
|
+
```java
|
|
1043
|
+
// Before
|
|
1044
|
+
Object lastReading() {
|
|
1045
|
+
return readings.lastElement();
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// After
|
|
1049
|
+
Reading lastReading() {
|
|
1050
|
+
return (Reading) readings.lastElement();
|
|
1051
|
+
}
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
---
|
|
1055
|
+
|
|
1056
|
+
### Replace Error Code with Exception
|
|
1057
|
+
|
|
1058
|
+
**Problem**: A method returns a special code to indicate an error.
|
|
1059
|
+
|
|
1060
|
+
**Solution**: Throw an exception instead.
|
|
1061
|
+
|
|
1062
|
+
```java
|
|
1063
|
+
// Before
|
|
1064
|
+
int withdraw(int amount) {
|
|
1065
|
+
if (amount > balance) {
|
|
1066
|
+
return -1;
|
|
1067
|
+
}
|
|
1068
|
+
balance -= amount;
|
|
1069
|
+
return 0;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// After
|
|
1073
|
+
void withdraw(int amount) throws BalanceException {
|
|
1074
|
+
if (amount > balance) {
|
|
1075
|
+
throw new BalanceException();
|
|
1076
|
+
}
|
|
1077
|
+
balance -= amount;
|
|
1078
|
+
}
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
---
|
|
1082
|
+
|
|
1083
|
+
### Replace Exception with Test
|
|
1084
|
+
|
|
1085
|
+
**Problem**: You are throwing an exception on a condition the caller could have checked first.
|
|
1086
|
+
|
|
1087
|
+
**Solution**: Change the caller to make the test first.
|
|
1088
|
+
|
|
1089
|
+
```java
|
|
1090
|
+
// Before
|
|
1091
|
+
double getValueForPeriod(int periodNumber) {
|
|
1092
|
+
try {
|
|
1093
|
+
return values[periodNumber];
|
|
1094
|
+
} catch (ArrayIndexOutOfBoundsException e) {
|
|
1095
|
+
return 0;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// After
|
|
1100
|
+
double getValueForPeriod(int periodNumber) {
|
|
1101
|
+
if (periodNumber >= values.length) {
|
|
1102
|
+
return 0;
|
|
1103
|
+
}
|
|
1104
|
+
return values[periodNumber];
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
---
|
|
1109
|
+
|
|
1110
|
+
## 6. Dealing with Generalization
|
|
1111
|
+
|
|
1112
|
+
### Pull Up Field
|
|
1113
|
+
|
|
1114
|
+
**Problem**: Two subclasses have the same field.
|
|
1115
|
+
|
|
1116
|
+
**Solution**: Move the field to the superclass.
|
|
1117
|
+
|
|
1118
|
+
---
|
|
1119
|
+
|
|
1120
|
+
### Pull Up Method
|
|
1121
|
+
|
|
1122
|
+
**Problem**: You have methods with identical results on subclasses.
|
|
1123
|
+
|
|
1124
|
+
**Solution**: Move them to the superclass.
|
|
1125
|
+
|
|
1126
|
+
---
|
|
1127
|
+
|
|
1128
|
+
### Pull Up Constructor Body
|
|
1129
|
+
|
|
1130
|
+
**Problem**: You have constructors on subclasses with mostly identical bodies.
|
|
1131
|
+
|
|
1132
|
+
**Solution**: Create a superclass constructor; call this from the subclass methods.
|
|
1133
|
+
|
|
1134
|
+
---
|
|
1135
|
+
|
|
1136
|
+
### Push Down Method
|
|
1137
|
+
|
|
1138
|
+
**Problem**: Behavior on a superclass is relevant only for some of its subclasses.
|
|
1139
|
+
|
|
1140
|
+
**Solution**: Move it to those subclasses.
|
|
1141
|
+
|
|
1142
|
+
---
|
|
1143
|
+
|
|
1144
|
+
### Push Down Field
|
|
1145
|
+
|
|
1146
|
+
**Problem**: A field is used only by some subclasses.
|
|
1147
|
+
|
|
1148
|
+
**Solution**: Move it to those subclasses.
|
|
1149
|
+
|
|
1150
|
+
---
|
|
1151
|
+
|
|
1152
|
+
### Extract Subclass
|
|
1153
|
+
|
|
1154
|
+
**Problem**: A class has features that are used only in some instances.
|
|
1155
|
+
|
|
1156
|
+
**Solution**: Create a subclass for that subset of features.
|
|
1157
|
+
|
|
1158
|
+
```java
|
|
1159
|
+
// Before
|
|
1160
|
+
class JobItem {
|
|
1161
|
+
private int unitPrice;
|
|
1162
|
+
private boolean isLabor;
|
|
1163
|
+
|
|
1164
|
+
int getTotalPrice() {
|
|
1165
|
+
return isLabor ? getQuantity() * getEmployee().getRate()
|
|
1166
|
+
: unitPrice * getQuantity();
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// After
|
|
1171
|
+
abstract class JobItem {
|
|
1172
|
+
abstract int getTotalPrice();
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
class LaborItem extends JobItem {
|
|
1176
|
+
int getTotalPrice() {
|
|
1177
|
+
return getQuantity() * getEmployee().getRate();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
class PartsItem extends JobItem {
|
|
1182
|
+
int getTotalPrice() {
|
|
1183
|
+
return unitPrice * getQuantity();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
---
|
|
1189
|
+
|
|
1190
|
+
### Extract Superclass
|
|
1191
|
+
|
|
1192
|
+
**Problem**: You have two classes with similar features.
|
|
1193
|
+
|
|
1194
|
+
**Solution**: Create a superclass and move the common features to the superclass.
|
|
1195
|
+
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1198
|
+
### Extract Interface
|
|
1199
|
+
|
|
1200
|
+
**Problem**: Several clients use the same subset of a class interface, or two classes have part of their interfaces in common.
|
|
1201
|
+
|
|
1202
|
+
**Solution**: Extract the subset into an interface.
|
|
1203
|
+
|
|
1204
|
+
```java
|
|
1205
|
+
// Before
|
|
1206
|
+
class Employee {
|
|
1207
|
+
double getRate();
|
|
1208
|
+
boolean hasSpecialSkill();
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// After
|
|
1212
|
+
interface Billable {
|
|
1213
|
+
double getRate();
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
class Employee implements Billable {
|
|
1217
|
+
public double getRate() { ... }
|
|
1218
|
+
boolean hasSpecialSkill() { ... }
|
|
1219
|
+
}
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
---
|
|
1223
|
+
|
|
1224
|
+
### Collapse Hierarchy
|
|
1225
|
+
|
|
1226
|
+
**Problem**: A superclass and subclass are not very different.
|
|
1227
|
+
|
|
1228
|
+
**Solution**: Merge them together.
|
|
1229
|
+
|
|
1230
|
+
---
|
|
1231
|
+
|
|
1232
|
+
### Form Template Method
|
|
1233
|
+
|
|
1234
|
+
**Problem**: You have two methods in subclasses that carry out similar steps in the same order, but the steps are different.
|
|
1235
|
+
|
|
1236
|
+
**Solution**: Get the steps into methods with the same signature, so that the original methods become the same. Then you can pull them up.
|
|
1237
|
+
|
|
1238
|
+
---
|
|
1239
|
+
|
|
1240
|
+
### Replace Inheritance with Delegation
|
|
1241
|
+
|
|
1242
|
+
**Problem**: A subclass uses only part of a superclass interface or does not want to inherit data.
|
|
1243
|
+
|
|
1244
|
+
**Solution**: Create a field for the superclass, adjust methods to delegate to the superclass, and remove the inheritance.
|
|
1245
|
+
|
|
1246
|
+
---
|
|
1247
|
+
|
|
1248
|
+
### Replace Delegation with Inheritance
|
|
1249
|
+
|
|
1250
|
+
**Problem**: You're using delegation and are often writing many simple delegations for the entire interface.
|
|
1251
|
+
|
|
1252
|
+
**Solution**: Make the delegating class a subclass of the delegate.
|
|
1253
|
+
|
|
1254
|
+
---
|
|
1255
|
+
|
|
1256
|
+
## Summary: Refactoring Quick Reference Card
|
|
1257
|
+
|
|
1258
|
+
| Smell | Primary Refactoring |
|
|
1259
|
+
|-------|---------------------|
|
|
1260
|
+
| Duplicated Code | Extract Method |
|
|
1261
|
+
| Long Method | Extract Method |
|
|
1262
|
+
| Large Class | Extract Class |
|
|
1263
|
+
| Long Parameter List | Introduce Parameter Object |
|
|
1264
|
+
| Divergent Change | Extract Class |
|
|
1265
|
+
| Shotgun Surgery | Move Method |
|
|
1266
|
+
| Feature Envy | Move Method |
|
|
1267
|
+
| Data Clumps | Extract Class |
|
|
1268
|
+
| Primitive Obsession | Replace Data Value with Object |
|
|
1269
|
+
| Switch Statements | Replace Conditional with Polymorphism |
|
|
1270
|
+
| Parallel Inheritance | Move Method |
|
|
1271
|
+
| Lazy Class | Inline Class |
|
|
1272
|
+
| Speculative Generality | Inline Class |
|
|
1273
|
+
| Temporary Field | Extract Class |
|
|
1274
|
+
| Message Chains | Hide Delegate |
|
|
1275
|
+
| Middle Man | Remove Middle Man |
|
|
1276
|
+
| Inappropriate Intimacy | Move Method |
|
|
1277
|
+
| Alternative Classes | Rename Method |
|
|
1278
|
+
| Incomplete Library Class | Introduce Foreign Method |
|
|
1279
|
+
| Data Class | Encapsulate Collection |
|
|
1280
|
+
| Refused Bequest | Push Down Method |
|
|
1281
|
+
| Comments | Extract Method |
|