opencode-metis 0.1.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 +140 -0
- package/dist/cli.cjs +63 -0
- package/dist/mcp-server.cjs +51 -0
- package/dist/plugin.cjs +4 -0
- package/dist/worker.cjs +224 -0
- package/opencode/agent/the-analyst/feature-prioritization.md +66 -0
- package/opencode/agent/the-analyst/market-research.md +77 -0
- package/opencode/agent/the-analyst/project-coordination.md +81 -0
- package/opencode/agent/the-analyst/requirements-analysis.md +77 -0
- package/opencode/agent/the-architect/compatibility-review.md +138 -0
- package/opencode/agent/the-architect/complexity-review.md +137 -0
- package/opencode/agent/the-architect/quality-review.md +67 -0
- package/opencode/agent/the-architect/security-review.md +127 -0
- package/opencode/agent/the-architect/system-architecture.md +119 -0
- package/opencode/agent/the-architect/system-documentation.md +83 -0
- package/opencode/agent/the-architect/technology-research.md +85 -0
- package/opencode/agent/the-chief.md +79 -0
- package/opencode/agent/the-designer/accessibility-implementation.md +101 -0
- package/opencode/agent/the-designer/design-foundation.md +74 -0
- package/opencode/agent/the-designer/interaction-architecture.md +75 -0
- package/opencode/agent/the-designer/user-research.md +70 -0
- package/opencode/agent/the-meta-agent.md +155 -0
- package/opencode/agent/the-platform-engineer/ci-cd-pipelines.md +109 -0
- package/opencode/agent/the-platform-engineer/containerization.md +106 -0
- package/opencode/agent/the-platform-engineer/data-architecture.md +81 -0
- package/opencode/agent/the-platform-engineer/dependency-review.md +144 -0
- package/opencode/agent/the-platform-engineer/deployment-automation.md +81 -0
- package/opencode/agent/the-platform-engineer/infrastructure-as-code.md +107 -0
- package/opencode/agent/the-platform-engineer/performance-tuning.md +82 -0
- package/opencode/agent/the-platform-engineer/pipeline-engineering.md +81 -0
- package/opencode/agent/the-platform-engineer/production-monitoring.md +105 -0
- package/opencode/agent/the-qa-engineer/exploratory-testing.md +66 -0
- package/opencode/agent/the-qa-engineer/performance-testing.md +81 -0
- package/opencode/agent/the-qa-engineer/quality-assurance.md +77 -0
- package/opencode/agent/the-qa-engineer/test-execution.md +66 -0
- package/opencode/agent/the-software-engineer/api-development.md +78 -0
- package/opencode/agent/the-software-engineer/component-development.md +79 -0
- package/opencode/agent/the-software-engineer/concurrency-review.md +141 -0
- package/opencode/agent/the-software-engineer/domain-modeling.md +66 -0
- package/opencode/agent/the-software-engineer/performance-optimization.md +113 -0
- package/opencode/command/analyze.md +149 -0
- package/opencode/command/constitution.md +178 -0
- package/opencode/command/debug.md +194 -0
- package/opencode/command/document.md +178 -0
- package/opencode/command/implement.md +225 -0
- package/opencode/command/refactor.md +207 -0
- package/opencode/command/review.md +229 -0
- package/opencode/command/simplify.md +267 -0
- package/opencode/command/specify.md +191 -0
- package/opencode/command/validate.md +224 -0
- package/opencode/skill/accessibility-design/SKILL.md +566 -0
- package/opencode/skill/accessibility-design/checklists/wcag-checklist.md +435 -0
- package/opencode/skill/agent-coordination/SKILL.md +224 -0
- package/opencode/skill/api-contract-design/SKILL.md +550 -0
- package/opencode/skill/api-contract-design/templates/graphql-schema-template.md +818 -0
- package/opencode/skill/api-contract-design/templates/rest-api-template.md +417 -0
- package/opencode/skill/architecture-design/SKILL.md +160 -0
- package/opencode/skill/architecture-design/examples/architecture-examples.md +170 -0
- package/opencode/skill/architecture-design/template.md +749 -0
- package/opencode/skill/architecture-design/validation.md +99 -0
- package/opencode/skill/architecture-selection/SKILL.md +522 -0
- package/opencode/skill/architecture-selection/examples/adrs/001-example-adr.md +71 -0
- package/opencode/skill/architecture-selection/examples/architecture-patterns.md +239 -0
- package/opencode/skill/bug-diagnosis/SKILL.md +235 -0
- package/opencode/skill/code-quality-review/SKILL.md +337 -0
- package/opencode/skill/code-quality-review/examples/anti-patterns.md +629 -0
- package/opencode/skill/code-quality-review/reference.md +322 -0
- package/opencode/skill/code-review/SKILL.md +363 -0
- package/opencode/skill/code-review/reference.md +450 -0
- package/opencode/skill/codebase-analysis/SKILL.md +139 -0
- package/opencode/skill/codebase-navigation/SKILL.md +227 -0
- package/opencode/skill/codebase-navigation/examples/exploration-patterns.md +263 -0
- package/opencode/skill/coding-conventions/SKILL.md +178 -0
- package/opencode/skill/coding-conventions/checklists/accessibility-checklist.md +176 -0
- package/opencode/skill/coding-conventions/checklists/performance-checklist.md +154 -0
- package/opencode/skill/coding-conventions/checklists/security-checklist.md +127 -0
- package/opencode/skill/constitution-validation/SKILL.md +315 -0
- package/opencode/skill/constitution-validation/examples/CONSTITUTION.md +202 -0
- package/opencode/skill/constitution-validation/reference/rule-patterns.md +328 -0
- package/opencode/skill/constitution-validation/template.md +115 -0
- package/opencode/skill/context-preservation/SKILL.md +445 -0
- package/opencode/skill/data-modeling/SKILL.md +385 -0
- package/opencode/skill/data-modeling/templates/schema-design-template.md +268 -0
- package/opencode/skill/deployment-pipeline-design/SKILL.md +579 -0
- package/opencode/skill/deployment-pipeline-design/templates/pipeline-template.md +633 -0
- package/opencode/skill/documentation-extraction/SKILL.md +259 -0
- package/opencode/skill/documentation-sync/SKILL.md +431 -0
- package/opencode/skill/domain-driven-design/SKILL.md +509 -0
- package/opencode/skill/domain-driven-design/examples/ddd-patterns.md +688 -0
- package/opencode/skill/domain-driven-design/reference.md +465 -0
- package/opencode/skill/drift-detection/SKILL.md +383 -0
- package/opencode/skill/drift-detection/reference.md +340 -0
- package/opencode/skill/error-recovery/SKILL.md +162 -0
- package/opencode/skill/error-recovery/examples/error-patterns.md +484 -0
- package/opencode/skill/feature-prioritization/SKILL.md +419 -0
- package/opencode/skill/feature-prioritization/examples/rice-template.md +139 -0
- package/opencode/skill/feature-prioritization/reference.md +256 -0
- package/opencode/skill/git-workflow/SKILL.md +453 -0
- package/opencode/skill/implementation-planning/SKILL.md +215 -0
- package/opencode/skill/implementation-planning/examples/phase-examples.md +217 -0
- package/opencode/skill/implementation-planning/template.md +220 -0
- package/opencode/skill/implementation-planning/validation.md +88 -0
- package/opencode/skill/implementation-verification/SKILL.md +272 -0
- package/opencode/skill/knowledge-capture/SKILL.md +265 -0
- package/opencode/skill/knowledge-capture/reference/knowledge-capture.md +402 -0
- package/opencode/skill/knowledge-capture/reference.md +444 -0
- package/opencode/skill/knowledge-capture/templates/domain-template.md +325 -0
- package/opencode/skill/knowledge-capture/templates/interface-template.md +255 -0
- package/opencode/skill/knowledge-capture/templates/pattern-template.md +144 -0
- package/opencode/skill/observability-design/SKILL.md +291 -0
- package/opencode/skill/observability-design/references/monitoring-patterns.md +461 -0
- package/opencode/skill/pattern-detection/SKILL.md +171 -0
- package/opencode/skill/pattern-detection/examples/common-patterns.md +359 -0
- package/opencode/skill/performance-analysis/SKILL.md +266 -0
- package/opencode/skill/performance-analysis/references/profiling-tools.md +499 -0
- package/opencode/skill/requirements-analysis/SKILL.md +139 -0
- package/opencode/skill/requirements-analysis/examples/good-prd.md +66 -0
- package/opencode/skill/requirements-analysis/template.md +177 -0
- package/opencode/skill/requirements-analysis/validation.md +69 -0
- package/opencode/skill/requirements-elicitation/SKILL.md +518 -0
- package/opencode/skill/requirements-elicitation/examples/interview-questions.md +226 -0
- package/opencode/skill/requirements-elicitation/examples/user-stories.md +414 -0
- package/opencode/skill/safe-refactoring/SKILL.md +312 -0
- package/opencode/skill/safe-refactoring/reference/code-smells.md +347 -0
- package/opencode/skill/security-assessment/SKILL.md +421 -0
- package/opencode/skill/security-assessment/checklists/security-review-checklist.md +285 -0
- package/opencode/skill/specification-management/SKILL.md +143 -0
- package/opencode/skill/specification-management/readme-template.md +32 -0
- package/opencode/skill/specification-management/reference.md +115 -0
- package/opencode/skill/specification-management/spec.py +229 -0
- package/opencode/skill/specification-validation/SKILL.md +397 -0
- package/opencode/skill/specification-validation/reference/3cs-framework.md +306 -0
- package/opencode/skill/specification-validation/reference/ambiguity-detection.md +132 -0
- package/opencode/skill/specification-validation/reference/constitution-validation.md +301 -0
- package/opencode/skill/specification-validation/reference/drift-detection.md +383 -0
- package/opencode/skill/task-delegation/SKILL.md +607 -0
- package/opencode/skill/task-delegation/examples/file-coordination.md +495 -0
- package/opencode/skill/task-delegation/examples/parallel-research.md +337 -0
- package/opencode/skill/task-delegation/examples/sequential-build.md +504 -0
- package/opencode/skill/task-delegation/reference.md +825 -0
- package/opencode/skill/tech-stack-detection/SKILL.md +89 -0
- package/opencode/skill/tech-stack-detection/references/framework-signatures.md +598 -0
- package/opencode/skill/technical-writing/SKILL.md +190 -0
- package/opencode/skill/technical-writing/templates/adr-template.md +205 -0
- package/opencode/skill/technical-writing/templates/system-doc-template.md +380 -0
- package/opencode/skill/test-design/SKILL.md +464 -0
- package/opencode/skill/test-design/examples/test-pyramid.md +724 -0
- package/opencode/skill/testing/SKILL.md +213 -0
- package/opencode/skill/testing/examples/test-pyramid.md +724 -0
- package/opencode/skill/user-insight-synthesis/SKILL.md +576 -0
- package/opencode/skill/user-insight-synthesis/templates/research-plan-template.md +217 -0
- package/opencode/skill/user-research/SKILL.md +508 -0
- package/opencode/skill/user-research/examples/interview-questions.md +265 -0
- package/opencode/skill/user-research/examples/personas.md +267 -0
- package/opencode/skill/vibe-security/SKILL.md +654 -0
- package/package.json +45 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
# Anti-Pattern Catalog
|
|
2
|
+
|
|
3
|
+
Detailed examples of common code anti-patterns: what they look like in practice, why they cause problems, and how to refactor them. Use this when a SKILL.md table entry needs deeper analysis or when explaining a finding to a developer.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## God Object
|
|
8
|
+
|
|
9
|
+
### What It Looks Like
|
|
10
|
+
|
|
11
|
+
A class that accumulates responsibilities over time until it knows too much and does too much. Often starts as a reasonable service class and grows through expedient additions.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
UserManager:
|
|
15
|
+
createUser()
|
|
16
|
+
updateUser()
|
|
17
|
+
deleteUser()
|
|
18
|
+
getUserById()
|
|
19
|
+
getUserByEmail()
|
|
20
|
+
sendWelcomeEmail()
|
|
21
|
+
sendPasswordResetEmail()
|
|
22
|
+
validateUserPermissions()
|
|
23
|
+
generateAuthToken()
|
|
24
|
+
revokeAuthToken()
|
|
25
|
+
refreshAuthToken()
|
|
26
|
+
logUserActivity()
|
|
27
|
+
getUserActivityReport()
|
|
28
|
+
exportUserDataToCsv()
|
|
29
|
+
importUsersFromCsv()
|
|
30
|
+
syncUsersWithLdap()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Why It Is Problematic
|
|
34
|
+
|
|
35
|
+
- Every team member who touches users touches this file — merge conflicts are constant
|
|
36
|
+
- Changing authentication logic risks breaking CSV export
|
|
37
|
+
- The class is untestable in isolation because it depends on email, database, LDAP, and auth systems simultaneously
|
|
38
|
+
- New developers cannot infer what "the system does with users" because the answer is "everything"
|
|
39
|
+
- The class has no single reason to change — it changes for every user-related requirement
|
|
40
|
+
|
|
41
|
+
### How to Refactor
|
|
42
|
+
|
|
43
|
+
Identify natural responsibility clusters. Each cluster becomes a focused class:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
UserRepository — persistence only (create, update, delete, findBy*)
|
|
47
|
+
UserEmailService — email communication (sendWelcome, sendPasswordReset)
|
|
48
|
+
AuthTokenService — token lifecycle (generate, revoke, refresh)
|
|
49
|
+
PermissionService — authorization checks (validatePermissions)
|
|
50
|
+
UserActivityLogger — audit trail (logActivity, getReport)
|
|
51
|
+
UserImportExport — CSV operations (import, export)
|
|
52
|
+
LdapSyncService — directory sync (syncUsers)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Each class has a single reason to change. `UserRepository` changes when the persistence layer changes. `UserEmailService` changes when email templates change. They can be tested independently.
|
|
56
|
+
|
|
57
|
+
Refactor incrementally — extract one cluster at a time, run tests between each extraction.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Spaghetti Code
|
|
62
|
+
|
|
63
|
+
### What It Looks Like
|
|
64
|
+
|
|
65
|
+
Control flow that jumps unpredictably through conditionals, early returns, exceptions used for control flow, and functions that call back into their callers. The execution path is difficult to trace.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
def process_payment(order, user, payment_method):
|
|
69
|
+
if payment_method == 'card':
|
|
70
|
+
if not user.has_saved_card:
|
|
71
|
+
if order.total > 0:
|
|
72
|
+
try:
|
|
73
|
+
result = stripe.charge(order.total, user.card_token)
|
|
74
|
+
if result.status == 'succeeded':
|
|
75
|
+
order.status = 'paid'
|
|
76
|
+
send_receipt(user, order)
|
|
77
|
+
if user.loyalty_enabled:
|
|
78
|
+
points = calculate_points(order.total)
|
|
79
|
+
if points > 0:
|
|
80
|
+
user.loyalty_points += points
|
|
81
|
+
if user.loyalty_points >= 1000:
|
|
82
|
+
send_loyalty_reward(user)
|
|
83
|
+
else:
|
|
84
|
+
order.status = 'failed'
|
|
85
|
+
log_failure(result)
|
|
86
|
+
if user.retry_enabled:
|
|
87
|
+
retry_payment(order, user)
|
|
88
|
+
except stripe.CardError as e:
|
|
89
|
+
handle_card_error(e, order, user)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise PaymentException("unexpected") from e
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError("no saved card")
|
|
94
|
+
elif payment_method == 'invoice':
|
|
95
|
+
# 40 more lines
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Why It Is Problematic
|
|
99
|
+
|
|
100
|
+
- Adding a new payment method requires understanding the entire existing structure
|
|
101
|
+
- The loyalty points logic is buried inside payment processing — impossible to test in isolation
|
|
102
|
+
- Cyclomatic complexity is high (10+ paths) — almost certainly under-tested
|
|
103
|
+
- When a bug is found in the card error path, the fix risks destabilizing the invoice path
|
|
104
|
+
- Reading the function requires holding many open conditionals in working memory
|
|
105
|
+
|
|
106
|
+
### How to Refactor
|
|
107
|
+
|
|
108
|
+
Separate concerns. Each logical step becomes a named function at the same abstraction level:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
def process_payment(order, user, payment_method):
|
|
112
|
+
validate_payment_prerequisites(order, user, payment_method)
|
|
113
|
+
result = charge_payment(order, user, payment_method)
|
|
114
|
+
apply_payment_result(result, order, user)
|
|
115
|
+
|
|
116
|
+
def validate_payment_prerequisites(order, user, payment_method):
|
|
117
|
+
if payment_method == 'card' and not user.has_saved_card:
|
|
118
|
+
raise MissingPaymentMethodError("User has no saved card on file")
|
|
119
|
+
|
|
120
|
+
def charge_payment(order, user, payment_method):
|
|
121
|
+
if payment_method == 'card':
|
|
122
|
+
return _charge_card(order, user)
|
|
123
|
+
if payment_method == 'invoice':
|
|
124
|
+
return _issue_invoice(order, user)
|
|
125
|
+
raise UnsupportedPaymentMethodError(payment_method)
|
|
126
|
+
|
|
127
|
+
def apply_payment_result(result, order, user):
|
|
128
|
+
order.status = 'paid' if result.succeeded else 'failed'
|
|
129
|
+
if result.succeeded:
|
|
130
|
+
_handle_successful_payment(order, user)
|
|
131
|
+
else:
|
|
132
|
+
_handle_failed_payment(result, order, user)
|
|
133
|
+
|
|
134
|
+
def _handle_successful_payment(order, user):
|
|
135
|
+
send_receipt(user, order)
|
|
136
|
+
_award_loyalty_points_if_eligible(user, order.total)
|
|
137
|
+
|
|
138
|
+
def _award_loyalty_points_if_eligible(user, amount):
|
|
139
|
+
if not user.loyalty_enabled:
|
|
140
|
+
return
|
|
141
|
+
points = calculate_points(amount)
|
|
142
|
+
user.loyalty_points += points
|
|
143
|
+
if user.loyalty_points >= LOYALTY_REWARD_THRESHOLD:
|
|
144
|
+
send_loyalty_reward(user)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Each function now has a single responsibility and a complexity of 1-3. The loyalty logic can be tested without processing a payment.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Copy-Paste Programming
|
|
152
|
+
|
|
153
|
+
### What It Looks Like
|
|
154
|
+
|
|
155
|
+
Identical or near-identical logic duplicated across the codebase. Often spotted by similar variable names, same comment blocks, or adjacent functions that differ only in the data they operate on.
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
function validateUserRegistration(data) {
|
|
159
|
+
const errors = [];
|
|
160
|
+
if (!data.email || !data.email.includes('@')) {
|
|
161
|
+
errors.push('Email is invalid');
|
|
162
|
+
}
|
|
163
|
+
if (!data.password || data.password.length < 8) {
|
|
164
|
+
errors.push('Password must be at least 8 characters');
|
|
165
|
+
}
|
|
166
|
+
if (!data.name || data.name.trim().length === 0) {
|
|
167
|
+
errors.push('Name is required');
|
|
168
|
+
}
|
|
169
|
+
return errors;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function validateUserUpdate(data) {
|
|
173
|
+
const errors = [];
|
|
174
|
+
if (data.email && !data.email.includes('@')) {
|
|
175
|
+
errors.push('Email is invalid');
|
|
176
|
+
}
|
|
177
|
+
if (data.password && data.password.length < 8) {
|
|
178
|
+
errors.push('Password must be at least 8 characters');
|
|
179
|
+
}
|
|
180
|
+
if (data.name !== undefined && data.name.trim().length === 0) {
|
|
181
|
+
errors.push('Name is required');
|
|
182
|
+
}
|
|
183
|
+
return errors;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Why It Is Problematic
|
|
188
|
+
|
|
189
|
+
- A bug fix in one copy must be found and applied to all copies — and copies are often not found
|
|
190
|
+
- The two functions have slightly different semantics (required vs optional fields) but the duplication obscures the intentional difference from the accidental duplication
|
|
191
|
+
- When the email validation rule changes (e.g., to use a proper regex), it will be changed in one place and missed in the other
|
|
192
|
+
- The next developer will copy-paste again rather than extract, deepening the problem
|
|
193
|
+
|
|
194
|
+
### How to Refactor
|
|
195
|
+
|
|
196
|
+
Extract the shared validation rules. Parameterize what varies:
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
const emailRule = (required = true) => ({
|
|
200
|
+
validate: (value) => !required && !value ? null : (value?.includes('@') ? null : 'Email is invalid'),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const passwordRule = (required = true) => ({
|
|
204
|
+
validate: (value) => !required && !value ? null : (value?.length >= 8 ? null : 'Password must be at least 8 characters'),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const nameRule = (required = true) => ({
|
|
208
|
+
validate: (value) => !required && value === undefined ? null : (value?.trim().length > 0 ? null : 'Name is required'),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
function validate(data, rules) {
|
|
212
|
+
return Object.entries(rules)
|
|
213
|
+
.map(([field, rule]) => rule.validate(data[field]))
|
|
214
|
+
.filter(Boolean);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const registrationRules = {
|
|
218
|
+
email: emailRule(true),
|
|
219
|
+
password: passwordRule(true),
|
|
220
|
+
name: nameRule(true),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const updateRules = {
|
|
224
|
+
email: emailRule(false),
|
|
225
|
+
password: passwordRule(false),
|
|
226
|
+
name: nameRule(false),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
function validateUserRegistration(data) {
|
|
230
|
+
return validate(data, registrationRules);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function validateUserUpdate(data) {
|
|
234
|
+
return validate(data, updateRules);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The intentional difference (required vs optional) is now explicit. The validation logic exists once.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Premature Optimization
|
|
243
|
+
|
|
244
|
+
### What It Looks Like
|
|
245
|
+
|
|
246
|
+
Complex code that sacrifices readability for performance before there is evidence of a performance problem. Often includes caching for operations that run once, bit manipulation for clarity, or micro-optimizations that modern compilers perform automatically.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
# Processing a list of ~50 user records from a form submission
|
|
250
|
+
def process_users(users):
|
|
251
|
+
# "Optimized" with manual hash map to avoid O(n) lookup
|
|
252
|
+
user_index = {}
|
|
253
|
+
for i in range(len(users)):
|
|
254
|
+
user_index[users[i]['id']] = i
|
|
255
|
+
|
|
256
|
+
# Bit manipulation instead of modulo
|
|
257
|
+
batch_size = 16
|
|
258
|
+
batch_mask = batch_size - 1
|
|
259
|
+
|
|
260
|
+
results = [None] * len(users) # Pre-allocated for "performance"
|
|
261
|
+
batch_buffer = []
|
|
262
|
+
|
|
263
|
+
for i in range(len(users)):
|
|
264
|
+
batch_buffer.append(users[i])
|
|
265
|
+
if (i & batch_mask) == batch_mask or i == len(users) - 1:
|
|
266
|
+
# Process batch
|
|
267
|
+
for user in batch_buffer:
|
|
268
|
+
idx = user_index[user['id']]
|
|
269
|
+
results[idx] = transform_user(user)
|
|
270
|
+
batch_buffer = []
|
|
271
|
+
|
|
272
|
+
return results
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Why It Is Problematic
|
|
276
|
+
|
|
277
|
+
- The code processes at most 50 items — the "optimization" provides zero measurable benefit at this scale
|
|
278
|
+
- A reviewer must understand bit masking, index remapping, and manual batching to verify correctness
|
|
279
|
+
- The complexity introduces a real bug opportunity (off-by-one in batch boundary detection)
|
|
280
|
+
- Performance assumptions baked into the code are not documented — the next developer does not know why this is complex
|
|
281
|
+
- If the actual bottleneck is the `transform_user` function or a database call, this optimization is in the wrong place
|
|
282
|
+
|
|
283
|
+
### How to Refactor
|
|
284
|
+
|
|
285
|
+
Write the simple version. Measure if performance becomes an issue. Optimize the measured bottleneck:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
def process_users(users):
|
|
289
|
+
return [transform_user(user) for user in users]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
If profiling reveals this is genuinely slow at real-world scale, the optimization is warranted. Document it:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
def process_users(users):
|
|
296
|
+
# Process in batches of 500 to stay within API rate limits.
|
|
297
|
+
# Batch size determined by load testing at 10k users/minute.
|
|
298
|
+
# See: docs/adr/0012-user-batch-processing.md
|
|
299
|
+
BATCH_SIZE = 500
|
|
300
|
+
results = []
|
|
301
|
+
for batch in chunked(users, BATCH_SIZE):
|
|
302
|
+
results.extend(_process_batch(batch))
|
|
303
|
+
return results
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Magic Numbers
|
|
309
|
+
|
|
310
|
+
### What It Looks Like
|
|
311
|
+
|
|
312
|
+
Numeric (or string) literals embedded in logic without explanation of what they represent or why they have that value.
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
function calculateShipping(orderTotal, destinationCountry) {
|
|
316
|
+
if (destinationCountry === 'US') {
|
|
317
|
+
if (orderTotal >= 75) {
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
return 8.99;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (orderTotal >= 150) {
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (['CA', 'MX'].includes(destinationCountry)) {
|
|
328
|
+
return 15.99;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return 29.99;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function calculateLoyaltyPoints(orderTotal) {
|
|
335
|
+
return Math.floor(orderTotal * 1.5);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
setTimeout(retryPayment, 30000);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Why It Is Problematic
|
|
342
|
+
|
|
343
|
+
- `75` and `150` appear to be free shipping thresholds — but a reader cannot confirm this without business documentation
|
|
344
|
+
- When marketing changes the domestic free shipping threshold from $75 to $100, the developer must find every `75` in the codebase (risky with grep)
|
|
345
|
+
- `1.5` in loyalty points has no unit — is it 1.5 points per dollar? Per cent? The calculation is opaque
|
|
346
|
+
- `30000` is milliseconds — readers must know this and convert mentally
|
|
347
|
+
- Magic numbers in tests are particularly dangerous: they assert specific values without explaining why those values are expected
|
|
348
|
+
|
|
349
|
+
### How to Refactor
|
|
350
|
+
|
|
351
|
+
Name every value that has domain meaning:
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
const FREE_SHIPPING_THRESHOLD_DOMESTIC = 75.00; // USD
|
|
355
|
+
const FREE_SHIPPING_THRESHOLD_INTERNATIONAL = 150.00; // USD
|
|
356
|
+
const SHIPPING_COST_DOMESTIC = 8.99;
|
|
357
|
+
const SHIPPING_COST_NORTH_AMERICA = 15.99;
|
|
358
|
+
const SHIPPING_COST_INTERNATIONAL = 29.99;
|
|
359
|
+
const NORTH_AMERICA_COUNTRIES = ['CA', 'MX'];
|
|
360
|
+
|
|
361
|
+
const LOYALTY_POINTS_PER_DOLLAR = 1.5;
|
|
362
|
+
|
|
363
|
+
const PAYMENT_RETRY_DELAY_MS = 30_000; // 30 seconds
|
|
364
|
+
|
|
365
|
+
function calculateShipping(orderTotal, destinationCountry) {
|
|
366
|
+
if (destinationCountry === 'US') {
|
|
367
|
+
return orderTotal >= FREE_SHIPPING_THRESHOLD_DOMESTIC ? 0 : SHIPPING_COST_DOMESTIC;
|
|
368
|
+
}
|
|
369
|
+
if (orderTotal >= FREE_SHIPPING_THRESHOLD_INTERNATIONAL) {
|
|
370
|
+
return 0;
|
|
371
|
+
}
|
|
372
|
+
return NORTH_AMERICA_COUNTRIES.includes(destinationCountry)
|
|
373
|
+
? SHIPPING_COST_NORTH_AMERICA
|
|
374
|
+
: SHIPPING_COST_INTERNATIONAL;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function calculateLoyaltyPoints(orderTotal) {
|
|
378
|
+
return Math.floor(orderTotal * LOYALTY_POINTS_PER_DOLLAR);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
setTimeout(retryPayment, PAYMENT_RETRY_DELAY_MS);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Constants that change together (e.g., all shipping thresholds) should live in a single configuration file — not scattered across the codebase.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Deep Nesting
|
|
389
|
+
|
|
390
|
+
### What It Looks Like
|
|
391
|
+
|
|
392
|
+
Control flow that indents further and further right, making it difficult to track which conditions govern a given block of code.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
function processOrderItem(order, item, user) {
|
|
396
|
+
if (order !== null) {
|
|
397
|
+
if (order.status === 'active') {
|
|
398
|
+
if (item !== null) {
|
|
399
|
+
if (item.inStock) {
|
|
400
|
+
if (user !== null) {
|
|
401
|
+
if (user.isVerified) {
|
|
402
|
+
if (item.price <= user.creditLimit) {
|
|
403
|
+
// The actual logic — buried at level 7
|
|
404
|
+
addItemToOrder(order, item);
|
|
405
|
+
deductFromCreditLimit(user, item.price);
|
|
406
|
+
updateInventory(item);
|
|
407
|
+
return { success: true };
|
|
408
|
+
} else {
|
|
409
|
+
return { success: false, reason: 'insufficient_credit' };
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
return { success: false, reason: 'unverified_user' };
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
return { success: false, reason: 'no_user' };
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
return { success: false, reason: 'out_of_stock' };
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
return { success: false, reason: 'no_item' };
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
return { success: false, reason: 'inactive_order' };
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
return { success: false, reason: 'no_order' };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Why It Is Problematic
|
|
433
|
+
|
|
434
|
+
- The happy path (the actual logic) is invisible — buried at the deepest level of nesting
|
|
435
|
+
- Each closing brace must be mentally matched to its opening — readers count braces
|
|
436
|
+
- Adding a new guard condition requires restructuring the entire block
|
|
437
|
+
- The function is 30+ lines for logic that occupies 3 lines
|
|
438
|
+
- Error handling is scattered — the `insufficient_credit` case and the `no_order` case look structurally identical but carry completely different meaning
|
|
439
|
+
|
|
440
|
+
### How to Refactor
|
|
441
|
+
|
|
442
|
+
Invert conditions with guard clauses. Return early on invalid cases. The happy path stays at the left margin:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
function processOrderItem(order, item, user) {
|
|
446
|
+
if (order === null) return { success: false, reason: 'no_order' };
|
|
447
|
+
if (order.status !== 'active') return { success: false, reason: 'inactive_order' };
|
|
448
|
+
if (item === null) return { success: false, reason: 'no_item' };
|
|
449
|
+
if (!item.inStock) return { success: false, reason: 'out_of_stock' };
|
|
450
|
+
if (user === null) return { success: false, reason: 'no_user' };
|
|
451
|
+
if (!user.isVerified) return { success: false, reason: 'unverified_user' };
|
|
452
|
+
if (item.price > user.creditLimit) return { success: false, reason: 'insufficient_credit' };
|
|
453
|
+
|
|
454
|
+
addItemToOrder(order, item);
|
|
455
|
+
deductFromCreditLimit(user, item.price);
|
|
456
|
+
updateInventory(item);
|
|
457
|
+
|
|
458
|
+
return { success: true };
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Same logic, same behavior, zero nesting. The happy path is immediately visible. New guard conditions are added as a new line at the top, not a new level of indentation.
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Feature Envy
|
|
467
|
+
|
|
468
|
+
### What It Looks Like
|
|
469
|
+
|
|
470
|
+
A method that is more interested in another class's data than its own. It reaches into another object repeatedly to do work that belongs to that other object.
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
class Order:
|
|
474
|
+
def __init__(self, items, customer):
|
|
475
|
+
self.items = items
|
|
476
|
+
self.customer = customer
|
|
477
|
+
|
|
478
|
+
def calculate_discount(self):
|
|
479
|
+
# This method "envies" the Customer class
|
|
480
|
+
if self.customer.membership_tier == 'gold':
|
|
481
|
+
if self.customer.years_as_customer >= 5:
|
|
482
|
+
return 0.20
|
|
483
|
+
return 0.15
|
|
484
|
+
if self.customer.membership_tier == 'silver':
|
|
485
|
+
if self.customer.years_as_customer >= 3:
|
|
486
|
+
return 0.10
|
|
487
|
+
return 0.05
|
|
488
|
+
if self.customer.total_lifetime_spend > 10000:
|
|
489
|
+
return 0.03
|
|
490
|
+
return 0
|
|
491
|
+
|
|
492
|
+
class InvoiceGenerator:
|
|
493
|
+
def format_shipping_address(self, order):
|
|
494
|
+
# This method knows too much about Customer's address structure
|
|
495
|
+
customer = order.customer
|
|
496
|
+
line1 = customer.address.street_number + ' ' + customer.address.street_name
|
|
497
|
+
if customer.address.apartment:
|
|
498
|
+
line1 += ', Apt ' + customer.address.apartment
|
|
499
|
+
city_line = customer.address.city + ', ' + customer.address.state
|
|
500
|
+
if customer.address.country != 'US':
|
|
501
|
+
city_line += ', ' + customer.address.country
|
|
502
|
+
return line1 + '\n' + city_line + '\n' + customer.address.postal_code
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Why It Is Problematic
|
|
506
|
+
|
|
507
|
+
- Discount rules live in `Order` but are entirely about `Customer` properties — when discount rules change, `Order` changes
|
|
508
|
+
- If `Customer.address` gains a new field, `InvoiceGenerator` must be updated — a coupling that should not exist
|
|
509
|
+
- The knowledge about how to format an address is now in two places: the generator and wherever else code accesses these fields
|
|
510
|
+
- Testing `calculate_discount` requires constructing full `Customer` objects instead of testing the discount logic alone
|
|
511
|
+
|
|
512
|
+
### How to Refactor
|
|
513
|
+
|
|
514
|
+
Move the method to the class whose data it uses:
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
class Customer:
|
|
518
|
+
def discount_rate(self):
|
|
519
|
+
if self.membership_tier == 'gold':
|
|
520
|
+
return 0.20 if self.years_as_customer >= 5 else 0.15
|
|
521
|
+
if self.membership_tier == 'silver':
|
|
522
|
+
return 0.10 if self.years_as_customer >= 3 else 0.05
|
|
523
|
+
if self.total_lifetime_spend > 10000:
|
|
524
|
+
return 0.03
|
|
525
|
+
return 0
|
|
526
|
+
|
|
527
|
+
def formatted_shipping_address(self):
|
|
528
|
+
return self.address.formatted()
|
|
529
|
+
|
|
530
|
+
class Address:
|
|
531
|
+
def formatted(self):
|
|
532
|
+
line1 = f"{self.street_number} {self.street_name}"
|
|
533
|
+
if self.apartment:
|
|
534
|
+
line1 += f", Apt {self.apartment}"
|
|
535
|
+
city_line = f"{self.city}, {self.state}"
|
|
536
|
+
if self.country != 'US':
|
|
537
|
+
city_line += f", {self.country}"
|
|
538
|
+
return f"{line1}\n{city_line}\n{self.postal_code}"
|
|
539
|
+
|
|
540
|
+
class Order:
|
|
541
|
+
def calculate_discount(self):
|
|
542
|
+
return self.customer.discount_rate()
|
|
543
|
+
|
|
544
|
+
class InvoiceGenerator:
|
|
545
|
+
def format_shipping_address(self, order):
|
|
546
|
+
return order.customer.formatted_shipping_address()
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Each class owns its own data and the logic that operates on it. Address formatting is in `Address`. Discount calculation is in `Customer`.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Shotgun Surgery
|
|
554
|
+
|
|
555
|
+
### What It Looks Like
|
|
556
|
+
|
|
557
|
+
A single conceptual change requires edits to many files. Common when a concern is spread across the codebase rather than encapsulated.
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
Scenario: "Add a new user role: 'moderator'"
|
|
561
|
+
|
|
562
|
+
Files that must change:
|
|
563
|
+
src/auth/permissions.ts — add role to enum
|
|
564
|
+
src/auth/guards/role.guard.ts — add role to allowed list
|
|
565
|
+
src/middleware/authorize.ts — handle new role in switch
|
|
566
|
+
src/db/migrations/... — add role to DB enum
|
|
567
|
+
src/db/seeds/roles.ts — add seed record
|
|
568
|
+
src/api/users/users.controller.ts — add role to DTO validation
|
|
569
|
+
src/api/users/users.service.ts — add role-specific logic
|
|
570
|
+
src/frontend/components/RoleBadge.tsx — add display case
|
|
571
|
+
src/frontend/hooks/usePermissions.ts — add permission checks
|
|
572
|
+
src/tests/fixtures/users.ts — update test fixtures
|
|
573
|
+
src/tests/auth/permissions.test.ts — add test cases
|
|
574
|
+
docs/api/users.md — update API documentation
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Why It Is Problematic
|
|
578
|
+
|
|
579
|
+
- Adding a role takes hours instead of minutes — not because of complexity, but because of coordination overhead
|
|
580
|
+
- Each file change is a merge conflict opportunity
|
|
581
|
+
- It is easy to miss one file — the bug only appears when that specific code path is triggered
|
|
582
|
+
- The codebase gives no indication that these files are related — a developer must know to look
|
|
583
|
+
- Reviewing the change is difficult: the reviewer must trace the conceptual change across 12 files
|
|
584
|
+
|
|
585
|
+
### How to Refactor
|
|
586
|
+
|
|
587
|
+
Encapsulate the concern. A role definition should exist in one place:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
// src/domain/roles/role.ts
|
|
591
|
+
export const Role = {
|
|
592
|
+
ADMIN: 'admin',
|
|
593
|
+
MODERATOR: 'moderator',
|
|
594
|
+
USER: 'user',
|
|
595
|
+
} as const;
|
|
596
|
+
|
|
597
|
+
export type RoleType = typeof Role[keyof typeof Role];
|
|
598
|
+
|
|
599
|
+
export const RolePermissions: Record<RoleType, Permission[]> = {
|
|
600
|
+
[Role.ADMIN]: [Permission.READ, Permission.WRITE, Permission.DELETE, Permission.MODERATE],
|
|
601
|
+
[Role.MODERATOR]: [Permission.READ, Permission.WRITE, Permission.MODERATE],
|
|
602
|
+
[Role.USER]: [Permission.READ, Permission.WRITE],
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
export const RoleDisplayName: Record<RoleType, string> = {
|
|
606
|
+
[Role.ADMIN]: 'Administrator',
|
|
607
|
+
[Role.MODERATOR]: 'Moderator',
|
|
608
|
+
[Role.USER]: 'User',
|
|
609
|
+
};
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Now adding a role is a single file change. Everything that depends on role definitions imports from this one location. The migration and tests still change, but the application logic does not scatter.
|
|
613
|
+
|
|
614
|
+
The test for whether Shotgun Surgery is resolved: can a developer make the conceptual change (add a role) by editing one logical location?
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## Pattern Summary
|
|
619
|
+
|
|
620
|
+
| Anti-Pattern | Primary Dimension | Key Signal | First Refactoring Step |
|
|
621
|
+
|--------------|-------------------|------------|------------------------|
|
|
622
|
+
| God Object | Design | Class with 10+ methods spanning unrelated concerns | List responsibilities, find natural clusters |
|
|
623
|
+
| Spaghetti Code | Readability | Cannot trace execution path without diagramming | Identify the happy path, separate guard clauses |
|
|
624
|
+
| Copy-Paste Programming | Maintainability | Two functions that differ in one parameter | Find what varies, extract shared logic |
|
|
625
|
+
| Premature Optimization | Readability | Complexity added before measuring performance | Write the obvious version, delete the complex one |
|
|
626
|
+
| Magic Numbers | Readability | Literal values in logic with no named constant | Name every value that has domain meaning |
|
|
627
|
+
| Deep Nesting | Readability | Happy path is at indent level 4+ | Invert conditions, return early on failure |
|
|
628
|
+
| Feature Envy | Design | Method uses another class's data more than its own | Move method to the class whose data it uses |
|
|
629
|
+
| Shotgun Surgery | Maintainability | One concept requires N file changes | Find the concept, encapsulate it in one place |
|