@raftlabs/raftstack 1.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.
@@ -0,0 +1,318 @@
1
+ ---
2
+ name: code-quality
3
+ description: Use when writing or reviewing code, creating functions, naming variables, structuring files, or when code feels messy, hard to read, or overly complex
4
+ ---
5
+
6
+ # Code Quality
7
+
8
+ ## Overview
9
+
10
+ Clean code reveals intent and minimizes complexity. Every function should do one thing, have a clear name, and fit on one screen.
11
+
12
+ ## When to Use
13
+
14
+ - Writing any new function or module
15
+ - Reviewing code for clarity
16
+ - Refactoring messy code
17
+ - Under time pressure (especially then)
18
+
19
+ **When NOT to use:** Quick prototypes explicitly marked as throwaway (rare).
20
+
21
+ ## The Iron Rules
22
+
23
+ ### 1. Function Length: Max 30 Lines
24
+
25
+ Functions over 30 lines are doing too much. Extract helpers.
26
+
27
+ **Exception:** Orchestration functions (calling other functions, minimal logic) may reach 50 lines. If you have conditionals or loops, max is 30.
28
+
29
+ **Cyclomatic Complexity:** Max 10 branches per function. Count: `if`, `else`, `case`, `&&`, `||`, `?:`, `catch`.
30
+
31
+ ```typescript
32
+ // ❌ BAD: 100+ line processOrder doing everything
33
+ async function processOrder(order: Order) {
34
+ // validation...
35
+ // pricing...
36
+ // inventory...
37
+ // payment...
38
+ // email...
39
+ // analytics...
40
+ }
41
+
42
+ // ✅ GOOD: Orchestrator with extracted concerns
43
+ async function processOrder(order: Order) {
44
+ const validation = validateOrder(order);
45
+ if (!validation.valid) return failure(validation.error);
46
+
47
+ const pricing = calculateOrderTotal(order);
48
+ const reservation = await reserveInventory(order);
49
+
50
+ const payment = await processPayment(order, pricing.total);
51
+ if (!payment.success) {
52
+ await releaseInventory(reservation);
53
+ return failure(payment.error);
54
+ }
55
+
56
+ await sendConfirmationEmail(order, pricing);
57
+ await recordAnalytics(order, pricing);
58
+
59
+ return success(order.id, pricing.total);
60
+ }
61
+ ```
62
+
63
+ ### 2. No Magic Numbers
64
+
65
+ Every number with meaning needs a name.
66
+
67
+ ```typescript
68
+ // ❌ BAD: What do these numbers mean?
69
+ if (totalItems >= 10) {
70
+ total = total * 0.90;
71
+ }
72
+ if (password.length < 8) { }
73
+
74
+ // ✅ GOOD: Self-documenting
75
+ const BULK_DISCOUNT_THRESHOLD = 10;
76
+ const BULK_DISCOUNT_RATE = 0.10;
77
+ const MIN_PASSWORD_LENGTH = 8;
78
+
79
+ if (totalItems >= BULK_DISCOUNT_THRESHOLD) {
80
+ total = total * (1 - BULK_DISCOUNT_RATE);
81
+ }
82
+ if (password.length < MIN_PASSWORD_LENGTH) { }
83
+ ```
84
+
85
+ ### 3. DRY: Extract Repeated Logic
86
+
87
+ If you copy-paste, extract.
88
+
89
+ ```typescript
90
+ // ❌ BAD: Rollback logic repeated 5 times
91
+ if (!paymentInfo) {
92
+ for (const item of order.items) {
93
+ inventoryDb[item.productId] += item.quantity;
94
+ }
95
+ delete reservedInventory[order.id];
96
+ return { success: false, error: 'Payment required' };
97
+ }
98
+ // ...same rollback code repeated 4 more times...
99
+
100
+ // ✅ GOOD: Extracted once
101
+ function rollbackInventory(orderId: string, items: OrderItem[]) {
102
+ for (const item of items) {
103
+ inventoryDb[item.productId] += item.quantity;
104
+ }
105
+ delete reservedInventory[orderId];
106
+ }
107
+
108
+ // Then use: rollbackInventory(order.id, order.items);
109
+ ```
110
+
111
+ ### 4. Single Responsibility
112
+
113
+ One function = one reason to change.
114
+
115
+ | Bad | Good |
116
+ |-----|------|
117
+ | `validateAndProcessOrder()` | `validateOrder()` + `processOrder()` |
118
+ | `fetchDataAndRender()` | `fetchData()` + `renderData()` |
119
+ | `parseAndValidateAndSave()` | `parse()` + `validate()` + `save()` |
120
+
121
+ ### 5. Naming Conventions
122
+
123
+ | Type | Convention | Examples |
124
+ |------|------------|----------|
125
+ | Functions | camelCase, verb-first | `validateEmail()`, `calculateTotal()` |
126
+ | Booleans | `is`, `has`, `should`, `can` | `isValid`, `hasPermission`, `shouldRetry` |
127
+ | Constants | SCREAMING_SNAKE_CASE | `MAX_RETRIES`, `API_TIMEOUT_MS` |
128
+ | Classes/Types | PascalCase | `OrderProcessor`, `ValidationResult` |
129
+
130
+ ### 6. Comments: WHY, Not WHAT
131
+
132
+ ```typescript
133
+ // ❌ BAD: Describes what code does (obvious)
134
+ // Check if user is admin
135
+ if (user.role === 'admin') { }
136
+
137
+ // ✅ GOOD: Explains why (not obvious)
138
+ // Admins bypass rate limiting per security policy SEC-2024-001
139
+ if (user.role === 'admin') { }
140
+ ```
141
+
142
+ ## Automated Enforcement
143
+
144
+ ### ESLint Configuration
145
+
146
+ Enforce code quality with automated tools:
147
+
148
+ ```javascript
149
+ // .eslintrc.js
150
+ module.exports = {
151
+ rules: {
152
+ // Max function length
153
+ 'max-lines-per-function': ['error', {
154
+ max: 30,
155
+ skipBlankLines: true,
156
+ skipComments: true,
157
+ }],
158
+
159
+ // Cyclomatic complexity
160
+ 'complexity': ['error', { max: 10 }],
161
+
162
+ // Max file length
163
+ 'max-lines': ['error', {
164
+ max: 300,
165
+ skipBlankLines: true,
166
+ skipComments: true,
167
+ }],
168
+
169
+ // Max function params
170
+ 'max-params': ['error', 3],
171
+
172
+ // Max nested callbacks
173
+ 'max-nested-callbacks': ['error', 2],
174
+
175
+ // No magic numbers
176
+ 'no-magic-numbers': ['error', {
177
+ ignore: [0, 1, -1],
178
+ ignoreArrayIndexes: true,
179
+ }],
180
+ },
181
+ };
182
+ ```
183
+
184
+ ### TypeScript-Specific Patterns
185
+
186
+ ```typescript
187
+ // ✅ GOOD: Type-safe options object (not 5 params)
188
+ interface CreateUserOptions {
189
+ email: string;
190
+ firstName: string;
191
+ lastName: string;
192
+ role?: UserRole;
193
+ sendWelcomeEmail?: boolean;
194
+ }
195
+
196
+ function createUser(options: CreateUserOptions): User {
197
+ // Single param, clear structure
198
+ }
199
+
200
+ // ✅ GOOD: Discriminated union for type-safe control flow
201
+ type Result<T> =
202
+ | { success: true; data: T }
203
+ | { success: false; error: string };
204
+
205
+ function processOrder(order: Order): Result<OrderConfirmation> {
206
+ if (!isValid(order)) {
207
+ return { success: false, error: 'Invalid order' };
208
+ }
209
+
210
+ const confirmation = executeOrder(order);
211
+ return { success: true, data: confirmation };
212
+ }
213
+
214
+ // ✅ GOOD: Branded types for type safety
215
+ type UserId = string & { readonly __brand: 'UserId' };
216
+ type OrderId = string & { readonly __brand: 'OrderId' };
217
+
218
+ function getUser(id: UserId): User { /* ... */ }
219
+
220
+ // Won't compile - prevents mixing up IDs
221
+ const orderId: OrderId = '123' as OrderId;
222
+ getUser(orderId); // Type error!
223
+ ```
224
+
225
+ ## Testing Strategy
226
+
227
+ ```typescript
228
+ // Test function length and complexity
229
+ describe('Code Quality', () => {
230
+ it('functions stay under 30 lines', () => {
231
+ const functionSource = processOrder.toString();
232
+ const lines = functionSource.split('\n').filter(l => l.trim()).length;
233
+ expect(lines).toBeLessThanOrEqual(30);
234
+ });
235
+
236
+ it('maintains low cyclomatic complexity', () => {
237
+ // Use eslint-plugin-complexity or similar
238
+ const complexity = calculateComplexity(processOrder);
239
+ expect(complexity).toBeLessThanOrEqual(10);
240
+ });
241
+ });
242
+
243
+ // Test extracted helpers
244
+ describe('Helper Functions', () => {
245
+ it('validateOrder handles invalid input', () => {
246
+ const result = validateOrder({ items: [] });
247
+ expect(result.valid).toBe(false);
248
+ });
249
+
250
+ it('calculateOrderTotal sums items correctly', () => {
251
+ const total = calculateOrderTotal(mockOrder);
252
+ expect(total).toBe(99.99);
253
+ });
254
+ });
255
+ ```
256
+
257
+ ## Quick Reference
258
+
259
+ | Smell | Fix | ESLint Rule |
260
+ |-------|-----|-------------|
261
+ | Function > 30 lines | Extract helpers | `max-lines-per-function` |
262
+ | Cyclomatic complexity > 10 | Extract conditionals | `complexity` |
263
+ | Repeated code block | Extract function | Manual review |
264
+ | Magic number | Named constant | `no-magic-numbers` |
265
+ | `validateAndProcess()` | Split into two functions | Manual review |
266
+ | Nested callbacks > 2 levels | Extract or use async/await | `max-nested-callbacks` |
267
+ | Parameter list > 3 | Use options object | `max-params` |
268
+
269
+ ## Red Flags - STOP and Refactor
270
+
271
+ These thoughts mean you're about to write bad code:
272
+
273
+ | Thought | Reality |
274
+ |---------|---------|
275
+ | "It's faster to write it all in one function" | It's faster to read small functions. Write for the reader. |
276
+ | "We'll refactor later" | Later never comes. Write it right the first time. |
277
+ | "It's just prototype code" | Prototypes become production. No excuse. |
278
+ | "The deadline is tight" | Bad code slows you down MORE. Clean code is faster. |
279
+ | "I'll add helpers if it gets complex" | It's already complex. Extract NOW. |
280
+ | "This is a special case" | There are no special cases for quality. |
281
+ | "It's only 35 lines, close enough" | The limit exists for a reason. Extract a helper. |
282
+ | "The user specifically asked for one function" | Push back. Explain why splitting is better. |
283
+ | "I need all this context in one place" | That's what orchestrator functions are for. |
284
+
285
+ ## Pressure Response
286
+
287
+ When someone says "just make it work fast":
288
+
289
+ 1. **Small functions ARE faster** - easier to debug, test, modify
290
+ 2. **Tech debt has interest** - every shortcut costs 10x later
291
+ 3. **Extract as you go** - takes 30 seconds, saves hours
292
+
293
+ **Violating code quality under pressure is violating code quality.**
294
+
295
+ ## References
296
+
297
+ - [ESLint Complexity Rule](https://eslint.org/docs/latest/rules/complexity) - Cyclomatic complexity enforcement
298
+ - [ESLintCC](https://eslintcc.github.io/) - Complexity measurement tool
299
+ - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/) - Advanced type patterns
300
+ - [Clean Code (Martin)](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) - Function length rationale
301
+
302
+ **Version Notes:**
303
+ - ESLint 9+: Flat config format, enhanced rule options
304
+ - TypeScript 5+: Improved discriminated union narrowing
305
+ - Cyclomatic complexity: Default threshold 20, recommended 10
306
+
307
+ ## Common Mistakes
308
+
309
+ | Mistake | Impact | Fix |
310
+ |---------|--------|-----|
311
+ | God function | Untestable, unreadable | Max 30 lines, single responsibility |
312
+ | Copy-paste code | Bugs multiply | Extract shared logic |
313
+ | Cryptic names | Confusion | Descriptive, verb-first names |
314
+ | No constants | Magic numbers everywhere | SCREAMING_SNAKE_CASE for all config |
315
+ | No ESLint enforcement | Quality drifts over time | Add `complexity` and `max-lines-per-function` rules |
316
+ | 5+ function parameters | Hard to call, hard to test | Use options object pattern |
317
+ | Comments describe WHAT | Redundant, unmaintained | Comment WHY, not WHAT |
318
+ | Mixing ID types (string) | Runtime bugs | Use branded types for type safety |