@booklib/skills 1.2.0 → 1.3.1

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.
Files changed (100) hide show
  1. package/CONTRIBUTING.md +122 -0
  2. package/README.md +20 -2
  3. package/ROADMAP.md +36 -0
  4. package/animation-at-work/evals/evals.json +44 -0
  5. package/animation-at-work/examples/after.md +64 -0
  6. package/animation-at-work/examples/before.md +35 -0
  7. package/animation-at-work/scripts/audit_animations.py +295 -0
  8. package/bin/skills.js +552 -42
  9. package/clean-code-reviewer/SKILL.md +109 -1
  10. package/clean-code-reviewer/evals/evals.json +121 -3
  11. package/clean-code-reviewer/examples/after.md +48 -0
  12. package/clean-code-reviewer/examples/before.md +33 -0
  13. package/clean-code-reviewer/references/api_reference.md +158 -0
  14. package/clean-code-reviewer/references/practices-catalog.md +282 -0
  15. package/clean-code-reviewer/references/review-checklist.md +254 -0
  16. package/clean-code-reviewer/scripts/pre-review.py +206 -0
  17. package/data-intensive-patterns/evals/evals.json +43 -0
  18. package/data-intensive-patterns/examples/after.md +61 -0
  19. package/data-intensive-patterns/examples/before.md +38 -0
  20. package/data-intensive-patterns/scripts/adr.py +213 -0
  21. package/data-pipelines/evals/evals.json +45 -0
  22. package/data-pipelines/examples/after.md +97 -0
  23. package/data-pipelines/examples/before.md +37 -0
  24. package/data-pipelines/scripts/new_pipeline.py +444 -0
  25. package/design-patterns/evals/evals.json +46 -0
  26. package/design-patterns/examples/after.md +52 -0
  27. package/design-patterns/examples/before.md +29 -0
  28. package/design-patterns/scripts/scaffold.py +807 -0
  29. package/domain-driven-design/SKILL.md +120 -0
  30. package/domain-driven-design/evals/evals.json +48 -0
  31. package/domain-driven-design/examples/after.md +80 -0
  32. package/domain-driven-design/examples/before.md +43 -0
  33. package/domain-driven-design/scripts/scaffold.py +421 -0
  34. package/effective-java/evals/evals.json +46 -0
  35. package/effective-java/examples/after.md +83 -0
  36. package/effective-java/examples/before.md +37 -0
  37. package/effective-java/scripts/checkstyle_setup.py +211 -0
  38. package/effective-kotlin/evals/evals.json +45 -0
  39. package/effective-kotlin/examples/after.md +36 -0
  40. package/effective-kotlin/examples/before.md +38 -0
  41. package/effective-python/evals/evals.json +44 -0
  42. package/effective-python/examples/after.md +56 -0
  43. package/effective-python/examples/before.md +40 -0
  44. package/effective-python/references/api_reference.md +218 -0
  45. package/effective-python/references/practices-catalog.md +483 -0
  46. package/effective-python/references/review-checklist.md +190 -0
  47. package/effective-python/scripts/lint.py +173 -0
  48. package/kotlin-in-action/evals/evals.json +43 -0
  49. package/kotlin-in-action/examples/after.md +53 -0
  50. package/kotlin-in-action/examples/before.md +39 -0
  51. package/kotlin-in-action/scripts/setup_detekt.py +224 -0
  52. package/lean-startup/evals/evals.json +43 -0
  53. package/lean-startup/examples/after.md +80 -0
  54. package/lean-startup/examples/before.md +34 -0
  55. package/lean-startup/scripts/new_experiment.py +286 -0
  56. package/microservices-patterns/SKILL.md +140 -0
  57. package/microservices-patterns/evals/evals.json +45 -0
  58. package/microservices-patterns/examples/after.md +69 -0
  59. package/microservices-patterns/examples/before.md +40 -0
  60. package/microservices-patterns/scripts/new_service.py +583 -0
  61. package/package.json +2 -8
  62. package/refactoring-ui/evals/evals.json +45 -0
  63. package/refactoring-ui/examples/after.md +85 -0
  64. package/refactoring-ui/examples/before.md +58 -0
  65. package/refactoring-ui/scripts/audit_css.py +250 -0
  66. package/skill-router/SKILL.md +142 -0
  67. package/skill-router/evals/evals.json +38 -0
  68. package/skill-router/examples/after.md +63 -0
  69. package/skill-router/examples/before.md +39 -0
  70. package/skill-router/references/api_reference.md +24 -0
  71. package/skill-router/references/routing-heuristics.md +89 -0
  72. package/skill-router/references/skill-catalog.md +156 -0
  73. package/skill-router/scripts/route.py +266 -0
  74. package/storytelling-with-data/evals/evals.json +47 -0
  75. package/storytelling-with-data/examples/after.md +50 -0
  76. package/storytelling-with-data/examples/before.md +33 -0
  77. package/storytelling-with-data/scripts/chart_review.py +301 -0
  78. package/system-design-interview/evals/evals.json +45 -0
  79. package/system-design-interview/examples/after.md +94 -0
  80. package/system-design-interview/examples/before.md +27 -0
  81. package/system-design-interview/scripts/new_design.py +421 -0
  82. package/using-asyncio-python/evals/evals.json +43 -0
  83. package/using-asyncio-python/examples/after.md +68 -0
  84. package/using-asyncio-python/examples/before.md +39 -0
  85. package/using-asyncio-python/scripts/check_blocking.py +270 -0
  86. package/web-scraping-python/evals/evals.json +46 -0
  87. package/web-scraping-python/examples/after.md +109 -0
  88. package/web-scraping-python/examples/before.md +40 -0
  89. package/web-scraping-python/scripts/new_scraper.py +231 -0
  90. /package/{effective-python-skill → effective-python}/SKILL.md +0 -0
  91. /package/{effective-python-skill → effective-python}/ref-01-pythonic-thinking.md +0 -0
  92. /package/{effective-python-skill → effective-python}/ref-02-lists-and-dicts.md +0 -0
  93. /package/{effective-python-skill → effective-python}/ref-03-functions.md +0 -0
  94. /package/{effective-python-skill → effective-python}/ref-04-comprehensions-generators.md +0 -0
  95. /package/{effective-python-skill → effective-python}/ref-05-classes-interfaces.md +0 -0
  96. /package/{effective-python-skill → effective-python}/ref-06-metaclasses-attributes.md +0 -0
  97. /package/{effective-python-skill → effective-python}/ref-07-concurrency.md +0 -0
  98. /package/{effective-python-skill → effective-python}/ref-08-robustness-performance.md +0 -0
  99. /package/{effective-python-skill → effective-python}/ref-09-testing-debugging.md +0 -0
  100. /package/{effective-python-skill → effective-python}/ref-10-collaboration.md +0 -0
@@ -289,4 +289,112 @@ This is the definitive checklist. Reference these codes in reviews.
289
289
 
290
290
  ## Tone
291
291
 
292
- Be direct but constructive. You're a senior colleague doing a thoughtful code review, not a professor grading an exam. Assume the author is competent and point out the path to better code. Celebrate what's already clean. Remember the Boy Scout Rule: leave the code cleaner than you found it.
292
+ Be direct but constructive. You're a senior colleague doing a thoughtful code review, not a professor grading an exam. Assume the author is competent and point out the path to better code. Celebrate what's already clean. Remember the Boy Scout Rule: leave the code cleaner than you found it.
293
+ ---
294
+
295
+ ## Mode 3: Migration Planning
296
+
297
+ **Trigger phrases:** "migrate", "incrementally improve", "ratchet toward clean code", "legacy cleanup plan"
298
+
299
+ You are helping a developer incrementally migrate a legacy codebase toward Clean Code standards — without big-bang rewrites. The goal is a **phased, low-risk migration plan** where each phase delivers standalone value.
300
+
301
+ ### Step 1 — Inventory
302
+
303
+ List every smell found, tagged with:
304
+ - Severity: 🔴 Critical / 🟡 Important / 🟢 Suggestion
305
+ - Heuristic code (e.g., G5, N1, F3)
306
+ - Location (class/method name)
307
+
308
+ Present as a table:
309
+ | Smell | Location | Severity | Heuristic |
310
+ |-------|----------|----------|-----------|
311
+
312
+ ### Step 2 — Phase 1: Names & Comments (Zero-Risk)
313
+
314
+ **Goal:** Rename identifiers and clean comments with no structural change.
315
+ **Risk:** Near zero — no logic changes, safe to do in one PR.
316
+
317
+ Actions:
318
+ - Rename variables, methods, classes to intention-revealing names (N1, N4)
319
+ - Delete redundant, journal, and noise comments (C1, C3, C5)
320
+ - Remove commented-out code (C5)
321
+ - Add missing names for magic numbers (G25)
322
+
323
+ Output: A checklist of rename operations with before/after pairs.
324
+
325
+ **Definition of Done:** No cryptic names survive. All comments add information not in the code.
326
+
327
+ ### Step 3 — Phase 2: Functions (Low-Risk)
328
+
329
+ **Goal:** Refactor function shapes without changing class structure.
330
+ **Risk:** Low — changes are local to individual functions.
331
+
332
+ Actions:
333
+ - Extract functions to enforce Single Responsibility (G30)
334
+ - Reduce argument lists > 3; introduce Parameter Objects where needed (F1)
335
+ - Eliminate flag arguments by splitting into two functions (F3)
336
+ - Replace output arguments with return values or OO methods (F2)
337
+ - Add guard clauses / early returns to flatten nesting
338
+
339
+ Output: Before/after snippets for each refactored function.
340
+
341
+ **Definition of Done:** No function exceeds 20 lines. No function takes more than 3 arguments. No flag arguments remain.
342
+
343
+ ### Step 4 — Phase 3: Classes (Medium-Risk)
344
+
345
+ **Goal:** Reshape class responsibilities. Requires more planning than Phase 2.
346
+ **Risk:** Medium — touching class boundaries may affect callers.
347
+
348
+ Actions:
349
+ - Apply SRP: split classes with multiple reasons to change (Ch. 10)
350
+ - Reduce coupling; eliminate Feature Envy (G14)
351
+ - Encapsulate conditionals into well-named predicate methods (G28)
352
+ - Replace switch statements with polymorphism (G23)
353
+ - Eliminate data/object hybrids (Ch. 6)
354
+
355
+ Output: A class diagram showing before/after split, with migration order (most isolated first).
356
+
357
+ **Definition of Done:** Each class has one clear responsibility describable without "and" or "or."
358
+
359
+ ### Step 5 — Phase 4: Architecture (High-Risk)
360
+
361
+ **Goal:** Structural changes that affect multiple classes or modules.
362
+ **Risk:** High — requires careful testing before and after.
363
+
364
+ Actions:
365
+ - Refactor error handling: replace error codes with exceptions; remove null returns (Ch. 7)
366
+ - Fix Law of Demeter violations; restructure train wrecks (G36)
367
+ - Eliminate remaining DRY violations with shared abstractions (G5)
368
+ - Enforce consistent abstraction levels per function (G6, G34)
369
+
370
+ Output: Architecture diff showing before/after with integration points.
371
+
372
+ **Definition of Done:** No error codes remain. No null returns. No train wrecks. Duplication eliminated.
373
+
374
+ ### Migration Output Format
375
+
376
+ ```
377
+ ## Migration Plan: [ClassName/Module]
378
+
379
+ ### Smell Inventory
380
+ | Smell | Location | Severity | Heuristic |
381
+ |-------|----------|----------|-----------|
382
+ ...
383
+
384
+ ### Phase 1 — Names & Comments (start immediately)
385
+ - [ ] Rename `x` → `pendingOrderCount` in OrderProcessor.process()
386
+ - [ ] Delete journal comment at line 3
387
+ - [ ] Delete commented-out `sendEmail()` block
388
+ **Before:** `int x = getList().size();`
389
+ **After:** `int pendingOrderCount = getPendingOrders().size();`
390
+
391
+ ### Phase 2 — Functions (next sprint)
392
+ - [ ] Extract `validateInput()` from `processOrder()` (lines 45-67)
393
+ - [ ] Split `handleRequest(boolean isAdmin)` → `handleAdminRequest()` + `handleUserRequest()`
394
+
395
+ ### Phase 3 — Classes (following sprint)
396
+ - [ ] Split `UserService` into `AuthenticationService` + `NotificationService`
397
+
398
+ ### Phase 4 — Architecture (planned, requires test coverage first)
399
+ - [ ] Replace error code returns in `FileProcessor` with typed exceptions
400
+ ```
@@ -7,7 +7,7 @@
7
7
  "Flags poor naming using N1 (theList, getThem, list1, x, d, t, f, n, r)",
8
8
  "Identifies F1: Too Many Arguments in processData (5 args)",
9
9
  "Identifies F3: Flag Arguments (boolean f)",
10
- "Points out the deeply nested if-statements (G29: Avoid Negative Conditionals or guard clauses)",
10
+ "Points out the deeply nested positive if-statements; recommends guard clauses / early returns to flatten nesting (Ch. 3: functions should be small — blocks within if/else should be one line or a function call)",
11
11
  "Notes the function does multiple things: print, save, email, log (G30: Functions Should Do One Thing)",
12
12
  "Flags G25: magic number 4 in the comparison",
13
13
  "Suggests G28: Encapsulate Conditionals for the null/empty check",
@@ -23,7 +23,7 @@
23
23
  "Identifies C3: Redundant Comments — '# dictionary of users', '# Check if user exists', '# Return the user'",
24
24
  "Calls out C5: Commented-Out Code — both the uuid import and delete_user method",
25
25
  "Notes that get_user returns None — Don't Return Null (Ch. 7)",
26
- "Mentions shadowing Python's built-in `id`",
26
+ "Flags the variable name `id` as a poor name — it's ambiguous (what kind of id? user_id? sequence number?), single-letter-level clarity in a non-trivial scope, and shadows Python's built-in function (N1: choose descriptive names; N4: unambiguous names)",
27
27
  "Notes the class name 'UserManager' is vague (Ch. 2: avoid Manager/Processor names)",
28
28
  "Suggests concrete refactored code"
29
29
  ]
@@ -62,6 +62,124 @@
62
62
  "Notes 'new File(path)' never returns null, so the null check is G9: Dead Code",
63
63
  "Suggests exception-based refactoring with meaningful exception classes"
64
64
  ]
65
+ },
66
+ {
67
+ "id": "eval-06-switch-polymorphism",
68
+ "prompt": "Review this Java code:\n\n```java\npublic class PaymentProcessor {\n public double calculateFee(Payment payment) {\n switch (payment.getType()) {\n case CREDIT_CARD: return payment.getAmount() * 0.029 + 0.30;\n case DEBIT_CARD: return payment.getAmount() * 0.015;\n case PAYPAL: return payment.getAmount() * 0.034 + 0.30;\n case BANK_TRANSFER: return 2.50;\n default: throw new IllegalArgumentException(\"Unknown type\");\n }\n }\n\n public String getReceiptLabel(Payment payment) {\n switch (payment.getType()) {\n case CREDIT_CARD: return \"Credit Card Payment\";\n case DEBIT_CARD: return \"Debit Card Payment\";\n case PAYPAL: return \"PayPal Transaction\";\n case BANK_TRANSFER: return \"Bank Wire Transfer\";\n default: throw new IllegalArgumentException(\"Unknown type\");\n }\n }\n\n public boolean requiresCvv(Payment payment) {\n switch (payment.getType()) {\n case CREDIT_CARD:\n case DEBIT_CARD: return true;\n default: return false;\n }\n }\n}\n```",
69
+ "expectations": [
70
+ "Flags G23: Prefer Polymorphism to If/Else or Switch/Case — the three switch blocks are the central issue",
71
+ "Notes G27: Structure over Convention — the repeated switch structure relies on developer discipline rather than compiler enforcement",
72
+ "Identifies the Open/Closed Principle violation: adding a new payment type requires modifying three separate switch statements",
73
+ "Suggests replacing the type enum with an abstract PaymentMethod class (or interface) with polymorphic methods: calculateFee(), getReceiptLabel(), requiresCvv()",
74
+ "Recommends an abstract factory or registry pattern so new payment types can be added without touching existing code",
75
+ "Notes that G25 (magic numbers) applies to the hardcoded fee percentages like 0.029 and 0.034"
76
+ ]
77
+ },
78
+ {
79
+ "id": "eval-07-dry-violation-copy-paste",
80
+ "prompt": "Review this Python code:\n\n```python\ndef validate_and_process_order(order_data: dict) -> dict:\n if not order_data.get('customer_id'):\n raise ValueError('customer_id is required')\n if not order_data.get('items'):\n raise ValueError('items cannot be empty')\n if order_data.get('total', 0) <= 0:\n raise ValueError('total must be positive')\n\n enriched = {\n **order_data,\n 'status': 'pending',\n 'created_at': datetime.utcnow().isoformat(),\n 'source': 'orders',\n }\n db.collection('orders').insert(enriched)\n audit_log.record('created', enriched['customer_id'], enriched)\n return enriched\n\n\ndef validate_and_process_invoice(invoice_data: dict) -> dict:\n if not invoice_data.get('customer_id'):\n raise ValueError('customer_id is required')\n if not invoice_data.get('items'):\n raise ValueError('items cannot be empty')\n if invoice_data.get('total', 0) <= 0:\n raise ValueError('total must be positive')\n\n enriched = {\n **invoice_data,\n 'status': 'pending',\n 'created_at': datetime.utcnow().isoformat(),\n 'source': 'invoices',\n }\n db.collection('invoices').insert(enriched)\n audit_log.record('created', enriched['customer_id'], enriched)\n return enriched\n```",
81
+ "expectations": [
82
+ "Flags G5: Duplication — the two functions share identical validation logic and near-identical enrichment/persistence logic",
83
+ "Identifies the specific repeated pattern: the three validation checks are copy-pasted verbatim between both functions",
84
+ "Suggests extracting a shared _validate_document(data) helper to eliminate the duplicated validation block",
85
+ "Recommends parameterizing the differing parts (collection name, source label) so a single _create_document(data, collection, source) function handles both cases",
86
+ "Notes that the Template Method pattern (or simple parameterization) is the right tool here — the algorithm skeleton is identical, only a few values differ",
87
+ "Points out that future changes to validation rules (e.g., adding a due_date check) must be applied in two places, a maintenance hazard"
88
+ ]
89
+ },
90
+ {
91
+ "id": "eval-08-large-class-srp",
92
+ "prompt": "Review this Java code:\n\n```java\npublic class UserService {\n private final DataSource dataSource;\n private final JavaMailSender mailSender;\n\n public User authenticate(String username, String password) {\n User user = findByUsername(username);\n if (user == null || !BCrypt.checkpw(password, user.getPasswordHash()))\n throw new AuthenticationException(\"Invalid credentials\");\n user.setLastLogin(Instant.now());\n return user;\n }\n\n public void sendWelcomeEmail(User user) {\n MimeMessage msg = mailSender.createMimeMessage();\n msg.setRecipient(Message.RecipientType.TO, new InternetAddress(user.getEmail()));\n msg.setSubject(\"Welcome to the platform!\");\n msg.setText(buildWelcomeBody(user));\n mailSender.send(msg);\n }\n\n public void save(User user) {\n try (Connection conn = dataSource.getConnection()) {\n PreparedStatement ps = conn.prepareStatement(\n \"INSERT INTO users (id, name, email, password_hash) VALUES (?,?,?,?)\");\n ps.setString(1, user.getId()); ps.setString(2, user.getName());\n ps.setString(3, user.getEmail()); ps.setString(4, user.getPasswordHash());\n ps.executeUpdate();\n }\n }\n\n public byte[] generateUserReport(User user) {\n // ... build PDF with user activity stats\n }\n}\n```",
93
+ "expectations": [
94
+ "Flags SRP violation (Ch. 10): the class has at least four distinct reasons to change — authentication logic, email delivery, database persistence, and report generation",
95
+ "Names each responsibility explicitly: authentication (AuthenticationService), email (UserEmailService or WelcomeEmailSender), persistence (UserRepository), reporting (UserReportService)",
96
+ "Notes that the class name 'UserService' is too vague — the Manager/Service anti-pattern (Ch. 2: avoid generic nouns that obscure intent)",
97
+ "Recommends splitting into focused, single-responsibility classes so each can evolve and be tested independently",
98
+ "Observes that mixing infrastructure concerns (JDBC, SMTP) with domain logic in one class makes unit testing difficult without heavy mocking"
99
+ ]
100
+ },
101
+ {
102
+ "id": "eval-09-bad-unit-tests",
103
+ "prompt": "Review this Python test code:\n\n```python\nclass TestOrderWorkflow(unittest.TestCase):\n processed_order_id = None\n\n def test_1_create_order(self):\n order = order_service.create({'item': 'book', 'qty': 2})\n TestOrderWorkflow.processed_order_id = order.id\n self.assertIsNotNone(order.id)\n\n def test_2_process_payment(self):\n # depends on test_1_create_order having run first\n result = payment_service.charge(TestOrderWorkflow.processed_order_id, 29.99)\n self.assertTrue(result.success)\n\n def test_sync_with_warehouse(self):\n import time\n time.sleep(5) # wait for warehouse API to be ready\n resp = warehouse_client.sync(order_id=42)\n print('Sync response:', resp)\n\n def test_validate_order_fields(self):\n order = Order(item='book', qty=2, price=9.99, customer='Alice',\n address='123 Main St', coupon='SAVE10')\n self.assertEqual(order.item, 'book')\n self.assertEqual(order.qty, 2)\n self.assertGreater(order.price, 0)\n self.assertIsNotNone(order.customer)\n self.assertTrue(order.address.startswith('123'))\n self.assertEqual(order.coupon, 'SAVE10')\n```",
104
+ "expectations": [
105
+ "Flags T9: Tests Should Be Fast — time.sleep(5) makes the test suite slow; the warehouse call should be mocked, not awaited with a sleep",
106
+ "Flags Independence violation (F.I.R.S.T. — Independent): test_2_process_payment depends on test_1_create_order via shared class state, so tests cannot be run in isolation or in arbitrary order",
107
+ "Flags T1: Insufficient Tests — test_sync_with_warehouse uses only print() and has no assertions; a test with no assert proves nothing",
108
+ "Flags Single Concept per test (Ch. 9): test_validate_order_fields asserts six unrelated fields in one test, making failure diagnosis harder; each concept should have its own focused test",
109
+ "Recommends applying F.I.R.S.T. principles: Fast, Independent, Repeatable, Self-Validating, Timely",
110
+ "Suggests using setUp() or fixtures to create fresh order state for each test instead of relying on class-level shared state"
111
+ ]
112
+ },
113
+ {
114
+ "id": "eval-10-data-object-hybrid",
115
+ "prompt": "Review this Java code:\n\n```java\npublic class Customer {\n public String id;\n public String name;\n public String email;\n public List<Order> orderHistory;\n public Address billingAddress;\n\n public double calculateLifetimeValue() {\n return orderHistory.stream()\n .mapToDouble(Order::getTotal)\n .sum();\n }\n\n public boolean isEligibleForLoyaltyReward() {\n return calculateLifetimeValue() > 500.0\n && orderHistory.size() >= 5;\n }\n\n public void updateEmail(String newEmail) {\n if (newEmail == null || !newEmail.contains(\"@\"))\n throw new IllegalArgumentException(\"Invalid email: \" + newEmail);\n this.email = newEmail;\n }\n}\n```",
116
+ "expectations": [
117
+ "Flags Ch. 6 Data/Object anti-symmetry: the class simultaneously exposes all its fields as public (data structure behavior) and provides business methods that operate on that data (object behavior), making it a hybrid",
118
+ "Explains the hybrid is the worst of both worlds: it neither grants the procedural flexibility of a plain data structure nor provides the encapsulation benefits of a proper object",
119
+ "Notes the asymmetry rule: data structures expose data and have no significant behavior; objects hide data behind an abstraction and expose behavior",
120
+ "Recommends choosing one design: either a pure DTO/record with all public fields and no behavior, or a proper domain object with private fields, encapsulated state, and meaningful methods",
121
+ "Points out that public fields like orderHistory and billingAddress give callers unrestricted mutation access, undermining updateEmail's attempt at encapsulation",
122
+ "Suggests that if business logic is needed, make all fields private and expose only intentional behavior through methods"
123
+ ]
124
+ },
125
+ {
126
+ "id": "eval-11-concurrency-shared-mutable-state",
127
+ "prompt": "Review this Java code:\n\n```java\npublic class RequestMetrics {\n private static int totalRequests = 0;\n private static int failedRequests = 0;\n private static final List<String> recentErrors = new ArrayList<>();\n\n public static void recordSuccess() {\n totalRequests++;\n }\n\n public static void recordFailure(String errorMessage) {\n totalRequests++;\n failedRequests++;\n recentErrors.add(errorMessage);\n if (recentErrors.size() > 100) {\n recentErrors.remove(0);\n }\n }\n\n public static double getErrorRate() {\n if (totalRequests == 0) return 0.0;\n return (double) failedRequests / totalRequests;\n }\n\n public static List<String> getRecentErrors() {\n return recentErrors;\n }\n}\n```",
128
+ "expectations": [
129
+ "Flags Ch. 13 shared mutable state: totalRequests, failedRequests, and recentErrors are static mutable fields accessed by multiple threads with no synchronization, creating race conditions",
130
+ "Recommends replacing the int counters with AtomicInteger (or LongAdder for high-throughput) to make increments atomic without explicit locking",
131
+ "Recommends replacing ArrayList with a thread-safe structure such as ConcurrentLinkedDeque or a synchronized wrapper for recentErrors",
132
+ "Notes that getRecentErrors() returns the live mutable list — callers can modify it externally; it should return a defensive copy or an unmodifiable view",
133
+ "Flags SRP for concurrency (Ch. 13): concurrency policy (how state is protected) is mixed with metrics logic (what is recorded); these concerns should be separated",
134
+ "Suggests that immutability or publishing only copies of state is the safest concurrency strategy, and references the recommendation to keep synchronized sections as small as possible"
135
+ ]
136
+ },
137
+ {
138
+ "id": "eval-12-naming-scope-abstraction",
139
+ "prompt": "Review this Python code:\n\n```python\ndef build_recommendation_payload(user_profile, catalog_snapshot):\n i = [item for item in catalog_snapshot if item['active']]\n j = [item for item in i if item['category'] in user_profile['preferred_categories']]\n k = sorted(j, key=lambda item: item['score'], reverse=True)[:20]\n\n data = {}\n for item in k:\n result = pricing_engine.lookup(item['sku'], user_profile['region'])\n data[item['sku']] = {\n 'title': item['title'],\n 'price': result['final_price'],\n 'badge': result['promo_badge'],\n }\n\n return data\n\n\nclass RecommendationService:\n def get_info(self, user_id: str):\n conn = self._db_pool.acquire()\n try:\n row = conn.execute(\n 'SELECT preferences, region FROM users WHERE id = ?', (user_id,)\n ).fetchone()\n return dict(row) if row else None\n finally:\n self._db_pool.release(conn)\n```",
140
+ "expectations": [
141
+ "Flags N5: Use Long Names for Long Scopes — i, j, k are used across the full body of build_recommendation_payload (a multi-line function), not inside a tiny loop; they should be named active_items, category_matches, and top_ranked_items",
142
+ "Flags N2: Names Should Be at the Appropriate Level of Abstraction — data and result are too vague for objects holding structured pricing and recommendation payloads; names like recommendations_by_sku and pricing_result convey intent",
143
+ "Flags N7: Names Should Describe Side-Effects — get_info() acquires a database connection and executes a query, which is a significant side effect; a name like fetch_user_profile() or load_user_preferences() accurately describes what the method does",
144
+ "Notes that the misleading get_ prefix suggests a cheap accessor but hides I/O and resource management, violating the principle of least surprise",
145
+ "Provides concrete better names for all flagged identifiers: active_items, category_matches, top_ranked_items, recommendations_by_sku, pricing_result, fetch_user_profile"
146
+ ]
147
+ },
148
+ {
149
+ "id": "eval-13-already-clean-python",
150
+ "prompt": "Review this Python code:\n\n```python\nfrom dataclasses import dataclass, field\nfrom datetime import date\nfrom typing import ClassVar\n\n\n@dataclass(frozen=True)\nclass DateRange:\n start: date\n end: date\n MAX_SPAN_DAYS: ClassVar[int] = 365\n\n def __post_init__(self) -> None:\n if self.end < self.start:\n raise ValueError(\n f\"end ({self.end}) must not be before start ({self.start})\"\n )\n if (self.end - self.start).days > self.MAX_SPAN_DAYS:\n raise ValueError(\n f\"Range exceeds maximum span of {self.MAX_SPAN_DAYS} days\"\n )\n\n def contains(self, day: date) -> bool:\n return self.start <= day <= self.end\n\n def overlaps(self, other: 'DateRange') -> bool:\n return self.start <= other.end and self.end >= other.start\n\n @classmethod\n def for_calendar_year(cls, year: int) -> 'DateRange':\n return cls(start=date(year, 1, 1), end=date(year, 12, 31))\n```",
151
+ "expectations": [
152
+ "Recognizes this is already clean code and says so clearly and directly — does not manufacture issues to fill the review",
153
+ "Praises frozen=True for immutability, which prevents accidental mutation and makes the object safe to use as a dict key or in sets (aligns with Ch. 6: prefer immutability)",
154
+ "Praises __post_init__ validation: invariants are enforced at construction time with clear error messages, exemplifying G28 (Encapsulate Conditionals) and fail-fast design",
155
+ "Praises the named factory classmethod for_calendar_year() as a G30-aligned (Functions Do One Thing) readable entry point with a descriptive name (N1)",
156
+ "Praises small, focused methods — contains() and overlaps() each do exactly one thing with meaningful names",
157
+ "Frames any observation (e.g., adding __repr__ or a duration property) explicitly as an optional enhancement, not a defect"
158
+ ]
159
+ },
160
+ {
161
+ "id": "eval-14-output-arguments-temporal-coupling",
162
+ "prompt": "Review this Java code:\n\n```java\npublic class ReportBuilder {\n public void addHeader(List<String> lines, String title, LocalDate reportDate) {\n lines.add(\"=== \" + title + \" ===\");\n lines.add(\"Generated: \" + reportDate.toString());\n lines.add(\"\");\n }\n\n public void addBody(List<String> lines, List<SaleRecord> records) {\n for (SaleRecord r : records) {\n lines.add(r.getSku() + \": \" + r.getQuantity() + \" units @ $\" + r.getUnitPrice());\n }\n }\n\n public void addFooter(List<String> lines, double grandTotal) {\n lines.add(\"\");\n lines.add(\"Grand Total: $\" + grandTotal);\n }\n\n // Callers must invoke addHeader -> addBody -> addFooter in that order.\n // Calling addFooter before addBody produces a malformed report.\n public String render(List<String> lines) {\n return String.join(\"\\n\", lines);\n }\n}\n```",
163
+ "expectations": [
164
+ "Flags F2: Output Arguments — addHeader, addBody, and addFooter all mutate a List passed in rather than building state internally or returning a value; the reader must mentally note that the argument is being written to, not read from",
165
+ "Suggests the object-oriented fix: the ReportBuilder should own the internal lines list and expose fluent or void mutation methods — callers should not manage the accumulator",
166
+ "Flags G31: Hidden Temporal Coupling — the comment 'Callers must invoke addHeader -> addBody -> addFooter in that order' reveals a sequencing constraint that is nowhere enforced by the type system",
167
+ "Recommends making temporal coupling explicit: each step can return an intermediate type (e.g., HeaderAdded, BodyAdded) so the compiler enforces the correct sequence and addFooter cannot be called on a BodyAdded value that skipped the header",
168
+ "Provides a refactored sketch where ReportBuilder accumulates lines internally via method chaining (builder.withHeader(...).withBody(...).build()) eliminating both the output argument and the hidden ordering requirement"
169
+ ]
170
+ },
171
+ {
172
+ "id": "eval-15-real-world-multiple-smells",
173
+ "prompt": "Review this Python code:\n\n```python\nclass ReportManager:\n def __init__(self, db_conn, storage_client, mailer, template_name,\n send_email=False, compress_output=False):\n self.db_conn = db_conn\n self.storage_client = storage_client\n self.mailer = mailer\n self.template_name = template_name\n self.send_email = send_email\n self.compress_output = compress_output\n\n def run(self, report_id: str):\n # rows = self.db_conn.execute('SELECT * FROM reports_v1 WHERE id = ?', report_id)\n rows = self.db_conn.execute('SELECT * FROM reports WHERE id = ?', report_id)\n if not rows:\n return None\n\n rendered = self.storage_client.get_bucket('reports-prod').get_blob(\n f'templates/{self.template_name}').download_as_text()\n filled = rendered.replace('{{ROWS}}', str(rows))\n\n if self.compress_output:\n filled = zlib.compress(filled.encode(), 6)\n\n url = self.storage_client.get_bucket('reports-prod').upload_blob(\n f'output/{report_id}.html', filled)\n\n if self.send_email:\n self.mailer.send(\n self.db_conn.execute('SELECT email FROM users WHERE report_id=?', report_id)[0][0],\n 'Your report is ready', url)\n\n return url\n```",
174
+ "expectations": [
175
+ "Flags F1: Too Many Arguments — the constructor takes 6 parameters; this is a sign the class is doing too much and should be split (SRP violation: report rendering, file storage, and email delivery are three separate responsibilities)",
176
+ "Flags F3: Flag Arguments — send_email and compress_output are boolean flags that select fundamentally different behavior paths; each flag suggests the method is doing more than one thing",
177
+ "Flags G25: Magic Numbers — the compression level 6 and the hardcoded bucket name 'reports-prod' are unexplained literals; they should be named constants",
178
+ "Flags G36: Train Wreck / Law of Demeter — self.storage_client.get_bucket('reports-prod').get_blob(...).download_as_text() is a multi-level navigation chain that tightly couples this class to the internal structure of storage_client",
179
+ "Flags C5: Commented-Out Code — the old SQL query referencing reports_v1 should be deleted, not left in source; version history belongs in git",
180
+ "Flags Don't Return None (Ch. 7) — returning None when rows is empty forces every caller to do a null check; raise a specific exception or return a typed Optional/sentinel instead",
181
+ "Prioritizes issues by severity: SRP/F1 and Don't Return None as critical (breaking design), flag arguments and train wrecks as major, magic numbers and commented-out code as minor"
182
+ ]
65
183
  }
66
184
  ]
67
- }
185
+ }
@@ -0,0 +1,48 @@
1
+ # After
2
+
3
+ The discount calculation is split into single-responsibility methods with intention-revealing names, guard clauses replacing nesting, and constants replacing magic numbers.
4
+
5
+ ```java
6
+ private static final double PREMIUM_DISCOUNT = 0.85;
7
+ private static final double MEMBER_DISCOUNT = 0.90;
8
+ private static final double SHIPPING_CREDIT = 5.99;
9
+ private static final double FREE_SHIPPING_THRESHOLD = 100.00;
10
+
11
+ public double calculateOrderTotal(Order order, CustomerTier tier, boolean freeShippingPromo) {
12
+ if (order == null || order.getItems() == null) {
13
+ return 0;
14
+ }
15
+ return order.getItems().stream()
16
+ .filter(item -> item.getQty() > 0)
17
+ .mapToDouble(item -> calculateLineTotal(item, tier, freeShippingPromo))
18
+ .sum();
19
+ }
20
+
21
+ private double calculateLineTotal(Item item, CustomerTier tier, boolean freeShippingPromo) {
22
+ double subtotal = item.getPrice() * item.getQty();
23
+ double discounted = applyTierDiscount(subtotal, tier);
24
+ return applyShippingCredit(discounted, freeShippingPromo);
25
+ }
26
+
27
+ private double applyTierDiscount(double subtotal, CustomerTier tier) {
28
+ return switch (tier) {
29
+ case PREMIUM -> subtotal * PREMIUM_DISCOUNT;
30
+ case MEMBER -> subtotal * MEMBER_DISCOUNT;
31
+ default -> subtotal;
32
+ };
33
+ }
34
+
35
+ private double applyShippingCredit(double amount, boolean freeShippingPromo) {
36
+ if (!freeShippingPromo || amount <= FREE_SHIPPING_THRESHOLD) {
37
+ return amount;
38
+ }
39
+ return amount - SHIPPING_CREDIT;
40
+ }
41
+ ```
42
+
43
+ Key improvements:
44
+ - Descriptive name `calculateOrderTotal` states exactly what the method does (G20: Function Names Should Say What They Do)
45
+ - Guard clause at the top of the public method eliminates two levels of nesting (G28: Encapsulate Conditionals)
46
+ - Each private method does exactly one thing: calculate, discount, or apply credit (G30: Functions Should Do One Thing)
47
+ - Magic numbers `0.85`, `0.90`, `5.99`, `100` extracted to named constants (G25: Replace Magic Numbers with Named Constants)
48
+ - `String` tier replaced with `CustomerTier` enum, enabling exhaustive `switch` (J3: Constants versus Enums)
@@ -0,0 +1,33 @@
1
+ # Before
2
+
3
+ A Java method that handles order discount calculation with poor naming, deep nesting, and multiple responsibilities mixed together.
4
+
5
+ ```java
6
+ public double calc(Order o, String t, boolean f) {
7
+ double r = 0;
8
+ if (o != null) {
9
+ if (o.getItems() != null && o.getItems().size() > 0) {
10
+ for (Item i : o.getItems()) {
11
+ if (i.getQty() > 0) {
12
+ double p = i.getPrice() * i.getQty();
13
+ if (t.equals("PREMIUM")) {
14
+ p = p * 0.85;
15
+ } else if (t.equals("MEMBER")) {
16
+ p = p * 0.90;
17
+ } else {
18
+ p = p * 1.0;
19
+ }
20
+ if (f) {
21
+ // free shipping promo
22
+ if (p > 100) {
23
+ p = p - 5.99;
24
+ }
25
+ }
26
+ r = r + p;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ return r;
32
+ }
33
+ ```
@@ -0,0 +1,158 @@
1
+ # Clean Code — Heuristic Code Quick Reference
2
+
3
+ Complete reference tables for all heuristic codes from Chapter 17 of
4
+ *Clean Code* by Robert C. Martin. Use these codes in review feedback
5
+ to give reviewees a precise pointer to the underlying principle.
6
+
7
+ ---
8
+
9
+ ## Comments (C1–C5)
10
+
11
+ | Code | Name | One-Line Description |
12
+ |------|------|----------------------|
13
+ | **C1** | Inappropriate Information | Comments contain changelog entries, author attributions, dates, or metadata — put it in version control |
14
+ | **C2** | Obsolete Comment | Comment describes behavior the code no longer performs; it has drifted from reality |
15
+ | **C3** | Redundant Comment | Comment restates exactly what the code already says clearly — pure noise |
16
+ | **C4** | Poorly Written Comment | Comment is grammatically wrong, rambling, sloppy, or unclear — if worth writing, write it well |
17
+ | **C5** | Commented-Out Code | Code has been commented out instead of deleted — delete it, VCS remembers |
18
+
19
+ ---
20
+
21
+ ## Environment (E1–E2)
22
+
23
+ | Code | Name | One-Line Description |
24
+ |------|------|----------------------|
25
+ | **E1** | Build Requires More Than One Step | Building the project requires more than a single command — should be a one-step operation |
26
+ | **E2** | Tests Require More Than One Step | Running the tests requires manual configuration or multiple steps — should be one command |
27
+
28
+ ---
29
+
30
+ ## Functions (F1–F4)
31
+
32
+ | Code | Name | One-Line Description |
33
+ |------|------|----------------------|
34
+ | **F1** | Too Many Arguments | Function has more than 3 arguments — group related args into an object or redesign |
35
+ | **F2** | Output Arguments | A parameter is used as an output — use a return value or mutate `this`/`self` instead |
36
+ | **F3** | Flag Arguments | Function takes a boolean to select one of two behaviors — split into two functions |
37
+ | **F4** | Dead Function | Function is never called anywhere in the codebase — delete it |
38
+
39
+ ---
40
+
41
+ ## General (G1–G36)
42
+
43
+ | Code | Name | One-Line Description |
44
+ |------|------|----------------------|
45
+ | **G1** | Multiple Languages in One Source File | Source file mixes languages (HTML in Java, SQL inline in Python) — separate them |
46
+ | **G2** | Obvious Behavior Is Unimplemented | Function or class doesn't do what its name obviously promises — Principle of Least Surprise |
47
+ | **G3** | Incorrect Behavior at the Boundaries | Boundary conditions (empty input, max values, zero) are not handled or tested |
48
+ | **G4** | Overridden Safeties | Compiler warnings disabled, test failures ignored, assertions commented out — never do this |
49
+ | **G5** | Duplication | Same algorithm, business rule, or conditional appears in more than one place — extract it |
50
+ | **G6** | Code at Wrong Level of Abstraction | Low-level implementation details appear in high-level abstractions, or vice versa |
51
+ | **G7** | Base Classes Depending on Their Derivatives | A parent class imports or references a child class — reverse the dependency |
52
+ | **G8** | Too Much Information | Class or interface exposes far more than its clients need — narrow the interface |
53
+ | **G9** | Dead Code | Unreachable code paths, unused variables, uncalled functions — delete all of it |
54
+ | **G10** | Vertical Separation | Variables or functions are defined far from where they are used — move them closer |
55
+ | **G11** | Inconsistency | Same concept is handled differently in different parts of the codebase — pick one approach |
56
+ | **G12** | Clutter | Unused constructors, variables, default implementations, or imports that add no value |
57
+ | **G13** | Artificial Coupling | Modules are coupled for no structural reason — a utility placed in the wrong class |
58
+ | **G14** | Feature Envy | A method uses another class's data or methods more than its own — move it there |
59
+ | **G15** | Selector Arguments | Boolean or enum argument selects which of several behaviors to perform — split into separate functions |
60
+ | **G16** | Obscured Intent | Magic numbers, Hungarian notation, or dense expressions hide the code's purpose |
61
+ | **G17** | Misplaced Responsibility | A function or constant is placed in a surprising module — Principle of Least Surprise for placement |
62
+ | **G18** | Inappropriate Static | A static method that should be polymorphic — make it an instance method |
63
+ | **G19** | Use Explanatory Variables | A complex calculation should be broken into named intermediate variables that explain each step |
64
+ | **G20** | Function Names Should Say What They Do | `date.add(5)` is ambiguous — `date.addDays(5)` says exactly what it does |
65
+ | **G21** | Understand the Algorithm | Code arrived at by trial-and-error — understand *why* the algorithm works before shipping |
66
+ | **G22** | Make Logical Dependencies Physical | One module assumes something about another — make the assumption explicit in the type system |
67
+ | **G23** | Prefer Polymorphism to If/Else or Switch/Case | A switch on a type tag should usually be a polymorphic method call |
68
+ | **G24** | Follow Standard Conventions | Code should conform to team naming, formatting, and structural conventions |
69
+ | **G25** | Replace Magic Numbers with Named Constants | Literals like `86400` or `3.14159` should be named constants |
70
+ | **G26** | Be Precise | Using float for currency, ignoring concurrency, or assuming approximate equality — be exact |
71
+ | **G27** | Structure over Convention | Enforce design decisions with abstract methods and types rather than naming conventions |
72
+ | **G28** | Encapsulate Conditionals | Extract complex boolean logic into a named function: `shouldBeDeleted(timer)` not `timer.hasExpired() && !timer.isRecurrent()` |
73
+ | **G29** | Avoid Negative Conditionals | `buffer.shouldCompact()` is easier to parse than `!buffer.shouldNotCompact()` |
74
+ | **G30** | Functions Should Do One Thing | A function that can be subdivided into meaningful named parts is doing more than one thing |
75
+ | **G31** | Hidden Temporal Couplings | When function A must be called before function B, make that dependency visible in the structure |
76
+ | **G32** | Don't Be Arbitrary | Every structural decision should have a reason — arbitrary structure creates confusion |
77
+ | **G33** | Encapsulate Boundary Conditions | `level + 1` scattered inline should be `nextLevel = level + 1` declared once |
78
+ | **G34** | Functions Should Descend Only One Level of Abstraction | Each function should drop exactly one level below its stated purpose |
79
+ | **G35** | Keep Configurable Data at High Levels | Constants and configuration values belong at the top of the system, passed down — not buried in business logic |
80
+ | **G36** | Avoid Transitive Navigation | `a.getB().getC().doSomething()` couples you to the whole chain — Law of Demeter |
81
+
82
+ ---
83
+
84
+ ## Java-Specific (J1–J3)
85
+
86
+ | Code | Name | One-Line Description |
87
+ |------|------|----------------------|
88
+ | **J1** | Avoid Long Import Lists by Using Wildcards | When using many classes from a package, prefer wildcard imports (adapt to team convention) |
89
+ | **J2** | Don't Inherit Constants | Implementing an interface just to access its constants pollutes the class hierarchy — use static import |
90
+ | **J3** | Constants versus Enums | Use enums instead of integer constants — enums can have methods, fields, and are type-safe |
91
+
92
+ ---
93
+
94
+ ## Names (N1–N7)
95
+
96
+ | Code | Name | One-Line Description |
97
+ |------|------|----------------------|
98
+ | **N1** | Choose Descriptive Names | Every name should reveal intent so completely that no comment is needed to explain it |
99
+ | **N2** | Choose Names at the Appropriate Level of Abstraction | Names should match the abstraction level — `connect(locator)` not `dial(phoneNumber)` on an interface |
100
+ | **N3** | Use Standard Nomenclature Where Possible | Use design pattern names (`Visitor`, `Factory`) and domain language where they apply |
101
+ | **N4** | Unambiguous Names | Names should be precise enough that no two things in the same context could be confused |
102
+ | **N5** | Use Long Names for Long Scopes | Single-letter names in tight loops are fine; in wide scopes, invest in descriptive names |
103
+ | **N6** | Avoid Encodings | No Hungarian notation, no `m_` member prefix, no `I` interface prefix — they are noise |
104
+ | **N7** | Names Should Describe Side Effects | If `getOos()` creates an object when none exists, call it `createOrReturnOos()` |
105
+
106
+ ---
107
+
108
+ ## Tests (T1–T9)
109
+
110
+ | Code | Name | One-Line Description |
111
+ |------|------|----------------------|
112
+ | **T1** | Insufficient Tests | Test suite does not cover everything that could possibly break — if it can break, test it |
113
+ | **T2** | Use a Coverage Tool | Use a coverage tool and review gaps — a coverage number alone is not enough |
114
+ | **T3** | Don't Skip Trivial Tests | Even obvious tests have documentary value — their cost is low and their value is real |
115
+ | **T4** | An Ignored Test Is a Question about an Ambiguity | A skipped test represents an open question — document what is unclear and why |
116
+ | **T5** | Test Boundary Conditions | Bugs hide at edges — test minimum, maximum, empty, null, and off-by-one conditions |
117
+ | **T6** | Exhaustively Test Near Bugs | Bugs cluster — when you find a bug, test all surrounding logic thoroughly |
118
+ | **T7** | Patterns of Failure Are Revealing | Use test failure patterns to diagnose root causes rather than treating each failure in isolation |
119
+ | **T8** | Test Coverage Patterns Can Be Revealing | Look at which lines are uncovered — the pattern may reveal structural problems |
120
+ | **T9** | Tests Should Be Fast | Slow tests don't get run — if tests are slow, developers skip them and quality degrades |
121
+
122
+ ---
123
+
124
+ ## Chapter Summary Reference
125
+
126
+ | Chapter | Topic | Core Principle |
127
+ |---------|-------|----------------|
128
+ | Ch. 2 | Meaningful Names | Names should reveal intent; avoid disinformation and noise words |
129
+ | Ch. 3 | Functions | Small, one thing, one level of abstraction, minimal arguments |
130
+ | Ch. 4 | Comments | Comments are a failure to express intent in code; use sparingly and purposefully |
131
+ | Ch. 5 | Formatting | Code is communication; newspaper metaphor, vertical density/openness, team consistency |
132
+ | Ch. 6 | Objects and Data Structures | Objects hide data; data structures expose it. Law of Demeter. No hybrids. |
133
+ | Ch. 7 | Error Handling | Use exceptions; never return null; exceptions carry context |
134
+ | Ch. 8 | Boundaries | Wrap third-party APIs; write learning tests; maintain clean boundaries |
135
+ | Ch. 9 | Unit Tests | F.I.R.S.T.; one concept per test; tests are as important as production code |
136
+ | Ch. 10 | Classes | Small (by responsibility); SRP; high cohesion; OCP; DIP |
137
+ | Ch. 11 | Systems | Separate construction from use; dependency injection; cross-cutting concerns |
138
+ | Ch. 12 | Emergence | Four rules: tests pass, no duplication, expresses intent, minimizes classes/methods |
139
+ | Ch. 13 | Concurrency | Separate concurrency from logic; limit shared data; keep synchronized sections small |
140
+ | Ch. 17 | Smells and Heuristics | C1-C5, E1-E2, F1-F4, G1-G36, J1-J3, N1-N7, T1-T9 |
141
+
142
+ ---
143
+
144
+ ## Heuristic Code Pattern for Review Comments
145
+
146
+ When writing review feedback, reference heuristic codes inline:
147
+
148
+ ```
149
+ The function `processData` takes four arguments [F1] including a boolean flag [F3].
150
+ The flag controls whether to validate or skip — split into `processAndValidate()`
151
+ and `processWithoutValidation()` [G30].
152
+
153
+ The variable `x` is used 40 lines from its declaration [G10] and the name
154
+ doesn't reveal its purpose [N1].
155
+
156
+ There are two near-identical validation blocks on lines 34 and 78 [G5].
157
+ Extract to a shared `validateInput()` function.
158
+ ```