@yaawn/workflow 0.0.0-stage → 0.4.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 +172 -2
- package/agents/instructions/output-styles/yaawn-eli5.instructions.md +45 -0
- package/agents/instructions/yaawn-accessibility.css.instructions.md +58 -0
- package/agents/instructions/yaawn-accessibility.html.instructions.md +90 -0
- package/agents/instructions/yaawn-accessibility.javascript.instructions.md +50 -0
- package/agents/instructions/yaawn-clean-code.typescript.instructions.md +74 -0
- package/agents/instructions/yaawn-function-coupling.typescript.instructions.md +100 -0
- package/agents/instructions/yaawn-html-css-js.html.instructions.md +32 -0
- package/agents/instructions/yaawn-open-source-first.instructions.md +38 -0
- package/agents/instructions/yaawn-personas.instructions.md +18 -0
- package/agents/instructions/yaawn-semantic-html.html.instructions.md +40 -0
- package/agents/instructions/yaawn-vanilla-css.css.instructions.md +50 -0
- package/agents/output-styles/yaawn-eli5.instructions.md +45 -0
- package/agents/personas/yaawn-developer.md +16 -0
- package/agents/personas/yaawn-product-owner.md +18 -0
- package/agents/personas/yaawn-reviewer.md +16 -0
- package/agents/skills/yaawn-clean-code/SKILL.md +61 -0
- package/agents/skills/yaawn-clean-code/references/classes.md +114 -0
- package/agents/skills/yaawn-clean-code/references/comments.md +164 -0
- package/agents/skills/yaawn-clean-code/references/error-handling.md +151 -0
- package/agents/skills/yaawn-clean-code/references/formatting.md +146 -0
- package/agents/skills/yaawn-clean-code/references/functions.md +261 -0
- package/agents/skills/yaawn-clean-code/references/meaningful-names.md +234 -0
- package/agents/skills/yaawn-clean-code/references/objects-and-data-structures.md +130 -0
- package/agents/skills/yaawn-clean-code/references/principles.md +76 -0
- package/agents/skills/yaawn-clean-code/references/simple-design-and-code-smells.md +130 -0
- package/agents/skills/yaawn-clean-code/references/unit-tests.md +114 -0
- package/agents/skills/yaawn-create-plan/SKILL.md +234 -0
- package/agents/skills/yaawn-grill-bug-report/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-bug-report/references/bug-report-checklist.md +27 -0
- package/agents/skills/yaawn-grill-design/SKILL.md +68 -0
- package/agents/skills/yaawn-grill-design/references/design-checklist.md +36 -0
- package/agents/skills/yaawn-grill-other/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-other/references/other-checklist.md +19 -0
- package/agents/skills/yaawn-grill-refactor/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-refactor/references/refactor-checklist.md +26 -0
- package/agents/skills/yaawn-grill-spike/SKILL.md +31 -0
- package/agents/skills/yaawn-grill-spike/references/spike-checklist.md +25 -0
- package/agents/skills/yaawn-grill-story/SKILL.md +34 -0
- package/agents/skills/yaawn-grill-story/references/concept-challenge.md +77 -0
- package/agents/skills/yaawn-grill-story/references/story-checklist.md +44 -0
- package/agents/skills/yaawn-implement-plan/SKILL.md +196 -0
- package/agents/skills/yaawn-implement-plan/assets/fix-prompt.md +66 -0
- package/agents/skills/yaawn-implement-plan/assets/review-prompt.md +89 -0
- package/agents/skills/yaawn-implement-plan/assets/session-prompt.md +80 -0
- package/agents/skills/yaawn-implement-plan/scripts/agent.ts +218 -0
- package/agents/skills/yaawn-implement-plan/scripts/caffeinate.ts +56 -0
- package/agents/skills/yaawn-implement-plan/scripts/groups.ts +233 -0
- package/agents/skills/yaawn-implement-plan/scripts/overnight.ts +772 -0
- package/agents/skills/yaawn-implement-plan/scripts/parallel.ts +1176 -0
- package/agents/skills/yaawn-implement-plan/scripts/processes.ts +53 -0
- package/agents/skills/yaawn-implement-plan/scripts/stages.ts +65 -0
- package/agents/skills/yaawn-implement-plan/scripts/targets.ts +381 -0
- package/agents/skills/yaawn-implement-plan/scripts/worktrees.ts +431 -0
- package/agents/skills/yaawn-owasp-security/SKILL.md +106 -0
- package/agents/skills/yaawn-owasp-security/references/a01-broken-access-control.md +58 -0
- package/agents/skills/yaawn-owasp-security/references/a02-security-misconfiguration.md +56 -0
- package/agents/skills/yaawn-owasp-security/references/a03-software-supply-chain-failures.md +56 -0
- package/agents/skills/yaawn-owasp-security/references/a04-cryptographic-failures.md +67 -0
- package/agents/skills/yaawn-owasp-security/references/a05-injection.md +53 -0
- package/agents/skills/yaawn-owasp-security/references/a06-insecure-design.md +48 -0
- package/agents/skills/yaawn-owasp-security/references/a07-authentication-failures.md +68 -0
- package/agents/skills/yaawn-owasp-security/references/a08-software-data-integrity-failures.md +45 -0
- package/agents/skills/yaawn-owasp-security/references/a09-security-logging-alerting-failures.md +59 -0
- package/agents/skills/yaawn-owasp-security/references/a10-mishandling-exceptional-conditions.md +63 -0
- package/agents/skills/yaawn-prepare-plan/SKILL.md +110 -0
- package/agents/skills/yaawn-save-plan/SKILL.md +84 -0
- package/agents/skills/yaawn-save-plan/scripts/plan-body.ts +397 -0
- package/agents/skills/yaawn-save-plan/scripts/save-plan.ts +539 -0
- package/agents/skills/yaawn-split-story/SKILL.md +189 -0
- package/agents/skills/yaawn-split-story/references/spider.md +64 -0
- package/agents/skills/yaawn-split-story/scripts/create-issue.ts +162 -0
- package/agents/skills/yaawn-verify-executed-plan/SKILL.md +154 -0
- package/agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts +362 -0
- package/agents/skills/yaawn-verify-executed-plan/scripts/pull-request.ts +131 -0
- package/agents/skills/yaawn-write-bug-report/SKILL.md +87 -0
- package/agents/skills/yaawn-write-other/SKILL.md +90 -0
- package/agents/skills/yaawn-write-refactor/SKILL.md +94 -0
- package/agents/skills/yaawn-write-spike/SKILL.md +94 -0
- package/agents/skills/yaawn-write-user-story/SKILL.md +187 -0
- package/agents/skills/yaawn-write-user-story/references/acceptance-criteria.md +186 -0
- package/agents/skills/yaawn-write-user-story/references/five-types-of-value.md +82 -0
- package/agents/skills/yaawn-write-user-story/references/user-story-fundamentals.md +75 -0
- package/agents/yaawn-glossary.md +146 -0
- package/agents/yaawn-grilling.md +75 -0
- package/agents/yaawn-worktrees.md +69 -0
- package/config/install-tasks.json +35 -0
- package/config/settings.json +23 -0
- package/config/wizard.json +128 -0
- package/github/ISSUE_TEMPLATE/yaawn-bug-report.md +59 -0
- package/github/ISSUE_TEMPLATE/yaawn-other.md +38 -0
- package/github/ISSUE_TEMPLATE/yaawn-refactor.md +38 -0
- package/github/ISSUE_TEMPLATE/yaawn-spike.md +16 -0
- package/github/ISSUE_TEMPLATE/yaawn-story.md +56 -0
- package/package.json +33 -4
- package/scripts/install.js +73 -0
- package/yaawn/gitignore +1 -0
- package/yaawn/yaawn-definition-of-done.md +5 -0
- package/yaawn/yaawn-project-glossary.md +7 -0
- package/yaawn/yaawn-user-roles.md +3 -0
- package/yaawn/yaawn-verification-commands.md +7 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Meaningful Names
|
|
2
|
+
|
|
3
|
+
Rules for creating good names in code. Names are everywhere — variables, functions, arguments, classes, files, directories. Good names make code self-documenting.
|
|
4
|
+
|
|
5
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapter 2: Meaningful Names
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
### Use intention-revealing names
|
|
10
|
+
|
|
11
|
+
The name should tell you **why it exists**, **what it does**, and **how it is used**. If a name requires a comment, it does not reveal its intent.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// Bad
|
|
15
|
+
const d: number; // elapsed time in days
|
|
16
|
+
|
|
17
|
+
// Good
|
|
18
|
+
const elapsedTimeInDays: number;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Bad
|
|
23
|
+
const theList: User[];
|
|
24
|
+
|
|
25
|
+
// Good
|
|
26
|
+
const users: User[];
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Avoid disinformation
|
|
30
|
+
|
|
31
|
+
Do not leave false clues that obscure meaning. Avoid words whose established meanings differ from your intended meaning.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Bad — "List" implies a specific data structure
|
|
35
|
+
const accountList: Account[]; // is it really a List?
|
|
36
|
+
|
|
37
|
+
// Good
|
|
38
|
+
const accounts: Account[];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Avoid similar shapes
|
|
42
|
+
|
|
43
|
+
Names that look almost identical force readers to slow down and compare character-by-character.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Bad
|
|
47
|
+
class ControllerForEfficientHandlingOfStrings {}
|
|
48
|
+
class ControllerForEfficientStorageOfStrings {}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Make meaningful distinctions
|
|
52
|
+
|
|
53
|
+
If names must be different, they should mean something different.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Bad — what's the difference?
|
|
57
|
+
interface Product {}
|
|
58
|
+
interface ProductInfo {}
|
|
59
|
+
interface ProductData {}
|
|
60
|
+
|
|
61
|
+
// Good — names convey different purposes
|
|
62
|
+
interface Product {}
|
|
63
|
+
interface ProductSummary {}
|
|
64
|
+
interface ProductDetails {}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Use pronounceable names
|
|
68
|
+
|
|
69
|
+
If you can't pronounce it, you can't discuss it without sounding like an idiot.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Bad
|
|
73
|
+
interface CstmrRcrd {}
|
|
74
|
+
|
|
75
|
+
// Good
|
|
76
|
+
interface CustomerRecord {}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Use searchable names
|
|
80
|
+
|
|
81
|
+
Single-letter names and numeric constants are hard to locate across a codebase. The length of a name should correspond to the size of its scope.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Bad
|
|
85
|
+
for (let j = 0; j < 34; j++) {
|
|
86
|
+
s += (t[j] * 4) / 5;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Good
|
|
90
|
+
const realDaysPerIdealDay = 4;
|
|
91
|
+
const workDaysPerWeek = 5;
|
|
92
|
+
const numberOfTasks = 34;
|
|
93
|
+
|
|
94
|
+
for (let j = 0; j < numberOfTasks; j++) {
|
|
95
|
+
const realTaskDays = taskEstimates[j] * realDaysPerIdealDay;
|
|
96
|
+
const realTaskWeeks = realTaskDays / workDaysPerWeek;
|
|
97
|
+
sum += realTaskWeeks;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Avoid encodings and member prefixes
|
|
102
|
+
|
|
103
|
+
Don't prefix member variables with `m_`, `_`, or Hungarian notation. Classes and functions should be small enough that prefixes are unnecessary.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Bad
|
|
107
|
+
class Part {
|
|
108
|
+
private m_dsc: string; // The textual description
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Good
|
|
112
|
+
class Part {
|
|
113
|
+
private description: string;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> **Note (project convention):** This codebase uses **camelCase** for all variables and constants — never snake_case or UPPER_SNAKE_CASE. See `AGENTS.md` for details.
|
|
118
|
+
|
|
119
|
+
### Don't prefix interfaces with "I"
|
|
120
|
+
|
|
121
|
+
Let the user work with the interface without knowing it's an interface. If you must encode, encode the implementation.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Bad
|
|
125
|
+
interface IShapeFactory {}
|
|
126
|
+
|
|
127
|
+
// Good
|
|
128
|
+
interface ShapeFactory {}
|
|
129
|
+
class ShapeFactoryImpl implements ShapeFactory {}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Avoid mental mapping
|
|
133
|
+
|
|
134
|
+
Readers shouldn't have to mentally translate your names. Single-letter names are only acceptable for tiny loop counters (`i`, `j`, `k`).
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Bad
|
|
138
|
+
const l = getStudents();
|
|
139
|
+
for (const i of l) {
|
|
140
|
+
i.doSomething();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Good
|
|
144
|
+
const students = getStudents();
|
|
145
|
+
for (const student of students) {
|
|
146
|
+
student.doSomething();
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Class names should be nouns
|
|
151
|
+
|
|
152
|
+
Classes and objects should have noun or noun phrase names. Avoid vague names like `Manager`, `Processor`, `Data`, `Info`, `Helper`, `Common`, `Utilities`.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Bad — too vague, does more than one thing
|
|
156
|
+
class CommonUtils {}
|
|
157
|
+
|
|
158
|
+
// Good — specific purpose
|
|
159
|
+
class JsonUtils {}
|
|
160
|
+
class StringUtils {}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Method names should be verbs
|
|
164
|
+
|
|
165
|
+
Methods should have verb or verb phrase names. Accessors, mutators, and predicates should be prefixed with `get`, `set`, and `is`/`has`.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Good
|
|
169
|
+
postPayment();
|
|
170
|
+
deletePage();
|
|
171
|
+
save();
|
|
172
|
+
isActive();
|
|
173
|
+
hasPermission();
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Adjust name length to scope size
|
|
177
|
+
|
|
178
|
+
Short names for small scopes, longer descriptive names for larger scopes.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Bad — acronym unclear outside this context
|
|
182
|
+
const wd = 5;
|
|
183
|
+
|
|
184
|
+
// Good
|
|
185
|
+
const workDaysPerWeek = 5;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Don't use the same name for different purposes
|
|
189
|
+
|
|
190
|
+
The word `add` could mean concatenate strings, insert a record, or append to an array. Be specific.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Bad
|
|
194
|
+
function add(value: string): void; // which kind of "add"?
|
|
195
|
+
|
|
196
|
+
// Good
|
|
197
|
+
function concatenate(value: string): string;
|
|
198
|
+
function insert(value: string): void;
|
|
199
|
+
function append(value: string): void;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Add meaningful context, but don't add gratuitous context
|
|
203
|
+
|
|
204
|
+
Use context to disambiguate names, but don't add more context than is necessary.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Bad — what does "state" mean?
|
|
208
|
+
let state: string;
|
|
209
|
+
|
|
210
|
+
// Good — context disambiguates
|
|
211
|
+
let addressState: string;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Bad — package already provides context
|
|
216
|
+
// in module: obnk
|
|
217
|
+
class ObnkConsentEntity {}
|
|
218
|
+
|
|
219
|
+
// Good
|
|
220
|
+
class ConsentEntity {}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Use solution domain names
|
|
224
|
+
|
|
225
|
+
Use CS terms, algorithm names, pattern names freely — your readers are programmers.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// Good — recognizable to developers familiar with the Visitor pattern
|
|
229
|
+
class AccountVisitor {}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Use problem domain names
|
|
233
|
+
|
|
234
|
+
When there is no CS term, use names from the problem domain so domain experts can verify the code.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Objects and Data Structures
|
|
2
|
+
|
|
3
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapter 6: Objects and Data Structures
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### Data abstraction
|
|
8
|
+
|
|
9
|
+
Don't expose the details of your data. Express data in abstract terms. Hiding implementation is about **abstractions**, not just putting a layer of getters/setters between the variables.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Bad — exposes raw data
|
|
13
|
+
class Point {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Good — abstracts the implementation
|
|
19
|
+
class Point {
|
|
20
|
+
private x: number;
|
|
21
|
+
private y: number;
|
|
22
|
+
|
|
23
|
+
getX(): number {
|
|
24
|
+
return this.x;
|
|
25
|
+
}
|
|
26
|
+
getY(): number {
|
|
27
|
+
return this.y;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setCartesian(x: number, y: number): void {
|
|
31
|
+
this.x = x;
|
|
32
|
+
this.y = y;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
In TypeScript, prefer **interfaces** and **readonly** properties for data that doesn't need encapsulation. Use classes with private state only when behavior is attached.
|
|
38
|
+
|
|
39
|
+
### Data/Object anti-symmetry
|
|
40
|
+
|
|
41
|
+
This is a fundamental trade-off:
|
|
42
|
+
|
|
43
|
+
| | Adding new functions | Adding new data types |
|
|
44
|
+
| ---------------------------------------------- | ------------------------------------ | ------------------------------------------- |
|
|
45
|
+
| **Data structures** (expose data, no behavior) | Easy — add function, no data changes | Hard — all functions must change |
|
|
46
|
+
| **Objects** (hide data, expose behavior) | Hard — all classes must change | Easy — add new class implementing interface |
|
|
47
|
+
|
|
48
|
+
Choose based on what's more likely to change:
|
|
49
|
+
|
|
50
|
+
- **Expect new data types?** Use objects/interfaces (polymorphism)
|
|
51
|
+
- **Expect new functions?** Use data structures
|
|
52
|
+
|
|
53
|
+
### Avoid hybrids
|
|
54
|
+
|
|
55
|
+
Hybrid structures that are half object and half data structure have the **worst of both worlds** — hard to add new functions AND hard to add new data types.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Bad — hybrid: has both behavior and instanceof checks
|
|
59
|
+
abstract class Shape {
|
|
60
|
+
draw(): void {
|
|
61
|
+
if (this instanceof Square) {
|
|
62
|
+
/* ... */
|
|
63
|
+
} else if (this instanceof Rectangle) {
|
|
64
|
+
/* ... */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Good — pure object with polymorphism
|
|
70
|
+
interface Shape {
|
|
71
|
+
draw(): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class Square implements Shape {
|
|
75
|
+
draw(): void {
|
|
76
|
+
/* square-specific drawing */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class Rectangle implements Shape {
|
|
81
|
+
draw(): void {
|
|
82
|
+
/* rectangle-specific drawing */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### The Law of Demeter
|
|
88
|
+
|
|
89
|
+
A method should only call methods on:
|
|
90
|
+
|
|
91
|
+
1. Its own object (`this`)
|
|
92
|
+
2. Objects passed as parameters
|
|
93
|
+
3. Objects it creates
|
|
94
|
+
4. Its direct component objects
|
|
95
|
+
|
|
96
|
+
**Don't chain calls through collaborators** (train wrecks):
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Bad — reaches deep into collaborator structure
|
|
100
|
+
const outputDir = context.getOptions().getScratchDir().getAbsolutePath();
|
|
101
|
+
|
|
102
|
+
// Good — ask, don't reach
|
|
103
|
+
const outputDir = context.getOutputDirectory();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Data Transfer Objects (DTOs)
|
|
107
|
+
|
|
108
|
+
A DTO is a class with public data and no behavior — used for transferring data between layers or processes.
|
|
109
|
+
|
|
110
|
+
Use DTOs to separate the data that crosses boundaries from your internal domain model.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Bad — using the domain entity directly as input
|
|
114
|
+
function createUser(firstName: string, lastName: string, email: string, birthday: Date, role: Role): void {}
|
|
115
|
+
|
|
116
|
+
// Good — DTO wraps the input data
|
|
117
|
+
interface CreateUserDto {
|
|
118
|
+
firstName: string;
|
|
119
|
+
lastName: string;
|
|
120
|
+
email: string;
|
|
121
|
+
birthday: Date;
|
|
122
|
+
role: Role;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function createUser(dto: CreateUserDto): void {
|
|
126
|
+
const user = mapToUser(dto);
|
|
127
|
+
user.createdAt = new Date();
|
|
128
|
+
userRepository.save(user);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Principles
|
|
2
|
+
|
|
3
|
+
Core software design principles referenced throughout Clean Code.
|
|
4
|
+
|
|
5
|
+
> Source: Robert C. Martin, _Clean Code_ + [gpcoder gitbook bonus chapters](https://gpcoder.gitbook.io/clean-code/bonus)
|
|
6
|
+
|
|
7
|
+
## SOLID
|
|
8
|
+
|
|
9
|
+
### SRP — Single Responsibility Principle
|
|
10
|
+
|
|
11
|
+
A class should have **one, and only one, reason to change**. If you can think of more than one motive for changing a class, it has more than one responsibility.
|
|
12
|
+
|
|
13
|
+
### OCP — Open-Closed Principle
|
|
14
|
+
|
|
15
|
+
Software entities should be **open for extension** but **closed for modification**. Add new behavior by adding new code, not changing existing code. Use interfaces, abstract classes, and polymorphism.
|
|
16
|
+
|
|
17
|
+
### LSP — Liskov Substitution Principle
|
|
18
|
+
|
|
19
|
+
Objects of a superclass should be replaceable with objects of its subclasses **without breaking** the application. Subclasses must honor the contract of their parent.
|
|
20
|
+
|
|
21
|
+
### ISP — Interface Segregation Principle
|
|
22
|
+
|
|
23
|
+
No client should be forced to depend on methods it does not use. Prefer **many small, specific interfaces** over one large general-purpose interface.
|
|
24
|
+
|
|
25
|
+
### DIP — Dependency Inversion Principle
|
|
26
|
+
|
|
27
|
+
High-level modules should not depend on low-level modules. Both should depend on **abstractions**. Abstractions should not depend on details — details should depend on abstractions.
|
|
28
|
+
|
|
29
|
+
Use dependency injection to provide implementations of abstract services.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## KISS — Keep It Simple Stupid
|
|
34
|
+
|
|
35
|
+
Simplicity should be a key goal in design. Unnecessary complexity should be avoided. Ask: "Can this be written in a simpler way?"
|
|
36
|
+
|
|
37
|
+
## DRY — Don't Repeat Yourself
|
|
38
|
+
|
|
39
|
+
Each piece of knowledge must have a **single, unambiguous, authoritative representation** in a system. When DRY is applied successfully, changing one thing doesn't require changes in unrelated places.
|
|
40
|
+
|
|
41
|
+
## YAGNI — You Aren't Gonna Need It
|
|
42
|
+
|
|
43
|
+
Don't add functionality unless it's **actually needed now**. Never implement things when you just foresee that you might need them.
|
|
44
|
+
|
|
45
|
+
## Boy Scout Rule
|
|
46
|
+
|
|
47
|
+
> Always leave the code better than you found it.
|
|
48
|
+
|
|
49
|
+
If you touch a file, leave it a little cleaner than you found it — better names, removed dead code, simplified logic. Over time, the codebase improves.
|
|
50
|
+
|
|
51
|
+
## Don't Reinvent the Wheel
|
|
52
|
+
|
|
53
|
+
Check if a well-tested library or framework already solves the problem before writing a custom solution.
|
|
54
|
+
|
|
55
|
+
## Design Patterns
|
|
56
|
+
|
|
57
|
+
Use established design patterns (Strategy, Observer, Factory, Decorator, etc.) when they simplify the design. Don't force patterns where they don't fit.
|
|
58
|
+
|
|
59
|
+
## Coding Conventions
|
|
60
|
+
|
|
61
|
+
Follow the team's established conventions consistently. Inconsistent style makes code appear to be written by disagreeing individuals and increases cognitive load.
|
|
62
|
+
|
|
63
|
+
> **Project convention:** This codebase follows the conventions defined in `AGENTS.md`, `.editorconfig`, and `eslint.config.mjs`.
|
|
64
|
+
|
|
65
|
+
## Fail Fast
|
|
66
|
+
|
|
67
|
+
Detect and report problems as early as possible. Validate inputs at system boundaries. A clear, immediate failure is better than a silent, delayed corruption.
|
|
68
|
+
|
|
69
|
+
## Law of Demeter (LoD)
|
|
70
|
+
|
|
71
|
+
A method should only talk to its immediate friends — not to strangers reached through a chain of calls. See [objects-and-data-structures.md](./objects-and-data-structures.md) for details.
|
|
72
|
+
|
|
73
|
+
## Coupling and Cohesion
|
|
74
|
+
|
|
75
|
+
- **Low coupling**: Modules should have minimal dependencies on each other. Changes in one module shouldn't require changes in others.
|
|
76
|
+
- **High cohesion**: A module's internal elements should be strongly related. If a class has methods that don't use the same instance variables, it may need to be split.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Simple Design Rules and Code Smells
|
|
2
|
+
|
|
3
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapters 12 (Emergent Design) and 17 (Smells and Heuristics)
|
|
4
|
+
|
|
5
|
+
## Four Rules of Simple Design
|
|
6
|
+
|
|
7
|
+
A design is "simple" if it follows these rules in priority order:
|
|
8
|
+
|
|
9
|
+
### Rule 1: Runs all the tests
|
|
10
|
+
|
|
11
|
+
A system that cannot be verified should never be deployed. Writing tests drives better design:
|
|
12
|
+
|
|
13
|
+
- Classes conform to SRP (easier to test)
|
|
14
|
+
- Tight coupling is eliminated (hard to test → fix the design)
|
|
15
|
+
- Tests enable safe refactoring
|
|
16
|
+
|
|
17
|
+
### Rule 2: No duplication
|
|
18
|
+
|
|
19
|
+
Duplication is the primary enemy of a well-designed system. It represents additional work, risk, and complexity. Forms of duplication:
|
|
20
|
+
|
|
21
|
+
- Lines of code that look exactly the same
|
|
22
|
+
- Lines of code that are similar (same structure, different details)
|
|
23
|
+
- Duplication of implementation (same concept expressed differently)
|
|
24
|
+
|
|
25
|
+
Use the DRY principle, Template Method pattern, or Strategy pattern to eliminate duplication.
|
|
26
|
+
|
|
27
|
+
### Rule 3: Ensure expressiveness
|
|
28
|
+
|
|
29
|
+
Code should clearly express the intent of its author. Spend time choosing better names, splitting large functions, and using patterns. Well-written tests also serve as documentation.
|
|
30
|
+
|
|
31
|
+
> The most important way to be expressive is to **try**. Don't just get code working and move on — make it easy for the next reader.
|
|
32
|
+
|
|
33
|
+
### Rule 4: Minimal classes and methods
|
|
34
|
+
|
|
35
|
+
Keep function and class counts low. But this is the **lowest priority** rule — don't sacrifice tests, duplication removal, or expressiveness to minimize count.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Code Smells
|
|
40
|
+
|
|
41
|
+
Indicators that something may be wrong in the code:
|
|
42
|
+
|
|
43
|
+
| Smell | Description |
|
|
44
|
+
| ----------------------- | -------------------------------------------------------------------------------------- |
|
|
45
|
+
| **Rigidity** | Software is difficult to change; a small change causes a cascade of subsequent changes |
|
|
46
|
+
| **Fragility** | Software breaks in many places due to a single change |
|
|
47
|
+
| **Immobility** | Cannot reuse parts of the code in other contexts |
|
|
48
|
+
| **Needless complexity** | Over-engineering, speculative generality |
|
|
49
|
+
| **Needless repetition** | Two code fragments look almost identical |
|
|
50
|
+
| **Opacity** | Code is hard to understand |
|
|
51
|
+
|
|
52
|
+
### Comment smells
|
|
53
|
+
|
|
54
|
+
- Inappropriate information (author, dates — belongs in source control)
|
|
55
|
+
- Obsolete comments
|
|
56
|
+
- Redundant comments
|
|
57
|
+
- Poorly written comments
|
|
58
|
+
- Commented-out code
|
|
59
|
+
|
|
60
|
+
### Function smells
|
|
61
|
+
|
|
62
|
+
- **Long function**: More than ~15 lines — start asking questions
|
|
63
|
+
- **Too many arguments**: More than 3 parameters
|
|
64
|
+
- **Output arguments**: Readers expect arguments to be inputs
|
|
65
|
+
- **Flag arguments**: Boolean that selects behavior → split the function
|
|
66
|
+
- **Selector arguments**: Any type used to switch behavior (enum, int, etc.)
|
|
67
|
+
- **Dead function**: Methods that are never called — delete them
|
|
68
|
+
- **Feature envy**: A method that accesses another object's data more than its own
|
|
69
|
+
|
|
70
|
+
### Class smells
|
|
71
|
+
|
|
72
|
+
- **Large class**: Too many fields, methods, or lines
|
|
73
|
+
- **God class**: Concentrates too many responsibilities
|
|
74
|
+
- **Alternative classes with different interfaces**: Duplicate functionality, different API
|
|
75
|
+
- **Refused bequest**: Subclass doesn't use inherited methods → hierarchy is wrong
|
|
76
|
+
- **Change preventers**: Changing one class requires changes in many unrelated classes
|
|
77
|
+
- **Speculative generality**: Code created "just in case" for features never implemented
|
|
78
|
+
- **Dead code**: Variables, parameters, methods, or classes no longer used
|
|
79
|
+
|
|
80
|
+
### General smells
|
|
81
|
+
|
|
82
|
+
- **Multiple languages in one source file**
|
|
83
|
+
- **Obvious behavior not implemented** (Principle of Least Surprise)
|
|
84
|
+
- **Incorrect behavior at boundaries** — test every boundary condition
|
|
85
|
+
- **Duplication** — the DRY violation, in all its forms
|
|
86
|
+
- **Code at wrong abstraction level** — don't mix high-level and low-level concepts
|
|
87
|
+
- **Base classes depending on derivatives**
|
|
88
|
+
- **Too much information** — keep interfaces tight and small
|
|
89
|
+
- **Dead code** — code that isn't executed
|
|
90
|
+
- **Vertical separation** — define variables and functions near where they are used
|
|
91
|
+
- **Inconsistency** — if you do something one way, do all similar things the same way
|
|
92
|
+
- **Clutter** — unused variables, uncalled functions, uninformative comments
|
|
93
|
+
- **Artificial coupling** — a variable/function placed in a convenient but wrong location
|
|
94
|
+
- **Feature envy** — a method uses another class's data more than its own
|
|
95
|
+
- **Inappropriate static** — prefer instance methods that can be polymorphic
|
|
96
|
+
- **Use explanatory variables** — break complex expressions into named intermediates
|
|
97
|
+
- **Function names should say what they do**
|
|
98
|
+
- **Understand the algorithm** — don't just make it pass tests, understand **why** it works
|
|
99
|
+
- **Prefer polymorphism to if/else or switch/case**
|
|
100
|
+
- **Follow standard conventions**
|
|
101
|
+
- **Replace magic numbers with named constants**
|
|
102
|
+
- **Be precise** — make decisions deliberately, handle exceptions
|
|
103
|
+
- **Structure over convention** — enforce design with types, not naming rules
|
|
104
|
+
- **Encapsulate conditionals** — extract boolean expressions into named functions
|
|
105
|
+
- **Avoid negative conditionals**
|
|
106
|
+
- **Functions should do one thing**
|
|
107
|
+
- **Hidden temporal couplings** — make call ordering explicit through function signatures
|
|
108
|
+
- **Encapsulate boundary conditions** — handle edge cases in one place
|
|
109
|
+
- **Functions should descend one level of abstraction**
|
|
110
|
+
- **Keep configurable data at high levels** — don't bury config in low-level functions
|
|
111
|
+
- **Avoid transitive navigation** — Law of Demeter: don't reach through collaborators
|
|
112
|
+
|
|
113
|
+
### Name smells
|
|
114
|
+
|
|
115
|
+
- Choose descriptive names at the appropriate abstraction level
|
|
116
|
+
- Use standard nomenclature (e.g., `Decorator`, `Factory`, `Observer`)
|
|
117
|
+
- Use unambiguous names
|
|
118
|
+
- Use long names for long scopes
|
|
119
|
+
- Avoid encodings (Hungarian notation, prefixes)
|
|
120
|
+
- Names should describe side effects
|
|
121
|
+
|
|
122
|
+
### Test smells
|
|
123
|
+
|
|
124
|
+
- **Insufficient tests** — test everything that could break
|
|
125
|
+
- **Don't skip trivial tests** — their documentary value exceeds their cost
|
|
126
|
+
- **Ignored tests are questions about ambiguity**
|
|
127
|
+
- **Test boundary conditions**
|
|
128
|
+
- **Exhaustively test near bugs** — bugs congregate
|
|
129
|
+
- **Patterns of failure are revealing** — complete, well-ordered tests expose patterns
|
|
130
|
+
- **Tests should be fast**
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Unit Tests
|
|
2
|
+
|
|
3
|
+
Test code should be maintained to the same standards of quality as production code.
|
|
4
|
+
|
|
5
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapter 9: Unit Tests
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
### Keep tests clean
|
|
10
|
+
|
|
11
|
+
Dirty tests are worse than no tests. If tests are hard to maintain, developers stop maintaining them. When tests rot, production code rots with it.
|
|
12
|
+
|
|
13
|
+
### Focus on readability
|
|
14
|
+
|
|
15
|
+
Readability is **even more important** in tests than in production code. Say a lot with as few expressions as possible. Extract helpers for setup, execution, and verification.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Bad — setup, execution, and assertion all mixed together
|
|
19
|
+
it('should save a user', () => {
|
|
20
|
+
const jsonFile = 'path/to/file/user-dto.json';
|
|
21
|
+
const dto = JSON.parse(readFileSync(jsonFile, 'utf-8'));
|
|
22
|
+
const actual = userRepository.save(dto);
|
|
23
|
+
const expectedFile = 'path/to/file/user.json';
|
|
24
|
+
const expected = JSON.parse(readFileSync(expectedFile, 'utf-8'));
|
|
25
|
+
expect(actual).not.toBeNull();
|
|
26
|
+
expect(actual.id).not.toBeNull();
|
|
27
|
+
expect(actual.email).toBe(expected.email);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Good — clear AAA structure with helpers
|
|
31
|
+
it('should save user successfully', () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const dto = createUserDto();
|
|
34
|
+
|
|
35
|
+
// Act
|
|
36
|
+
const actual = userRepository.save(dto);
|
|
37
|
+
|
|
38
|
+
// Assert
|
|
39
|
+
const expected = createExpectedUser();
|
|
40
|
+
verifySavedUser(expected, actual);
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### One assert per test
|
|
45
|
+
|
|
46
|
+
Each test should verify **one specific behavior**. Multiple asserts testing different behaviors in one test hide failures and make diagnosis harder.
|
|
47
|
+
|
|
48
|
+
### Use a single concept per test
|
|
49
|
+
|
|
50
|
+
Don't combine multiple scenarios in one test function. Split boundary conditions, happy paths, and error cases into separate tests.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Bad — tests both in-range and out-of-range in one test
|
|
54
|
+
it('should check range', () => {
|
|
55
|
+
expect(isInRange(9)).toBe(false);
|
|
56
|
+
expect(isInRange(10)).toBe(true);
|
|
57
|
+
expect(isInRange(100)).toBe(true);
|
|
58
|
+
expect(isInRange(101)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Good — separate tests per concept
|
|
62
|
+
it('should return true for values in range', () => {
|
|
63
|
+
expect(isInRange(10)).toBe(true);
|
|
64
|
+
expect(isInRange(50)).toBe(true);
|
|
65
|
+
expect(isInRange(100)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return false for values out of range', () => {
|
|
69
|
+
expect(isInRange(9)).toBe(false);
|
|
70
|
+
expect(isInRange(101)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Use the given-when-then / AAA convention
|
|
75
|
+
|
|
76
|
+
Structure every test with three phases:
|
|
77
|
+
|
|
78
|
+
1. **Arrange** (Given): Set up the test data and preconditions
|
|
79
|
+
2. **Act** (When): Execute the behavior under test
|
|
80
|
+
3. **Assert** (Then): Verify the expected outcome
|
|
81
|
+
|
|
82
|
+
Use descriptive test names that communicate the scenario:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Good naming patterns
|
|
86
|
+
it('should return empty array when no users exist', () => {});
|
|
87
|
+
it('should throw AuthError when password is invalid', () => {});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Follow the F.I.R.S.T. principles
|
|
91
|
+
|
|
92
|
+
| Principle | Rule |
|
|
93
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| **Fast** | Tests should run quickly. Slow tests won't be run frequently. If you don't run them, you won't find problems early enough. |
|
|
95
|
+
| **Independent** | Tests should not depend on each other. No test should set up conditions for the next test. Run in any order. |
|
|
96
|
+
| **Repeatable** | Tests should work in any environment — CI, local, offline. No external dependencies that make tests flaky. |
|
|
97
|
+
| **Self-Validating** | Tests have a boolean output: pass or fail. No manual inspection of log files to determine success. |
|
|
98
|
+
| **Timely** | Write tests **before** (TDD) or **alongside** the production code. Writing tests after makes production code harder to test. |
|
|
99
|
+
|
|
100
|
+
### Test boundary conditions
|
|
101
|
+
|
|
102
|
+
Take special care to test boundaries. We often get the middle of an algorithm right but misjudge the edges.
|
|
103
|
+
|
|
104
|
+
### Exhaustively test near bugs
|
|
105
|
+
|
|
106
|
+
Bugs tend to congregate. When you find a bug in a function, do an exhaustive test of that function. The bug was probably not alone.
|
|
107
|
+
|
|
108
|
+
### Tests should be fast
|
|
109
|
+
|
|
110
|
+
If tests are slow, they won't be run often. Long feedback loops lead to code rot.
|
|
111
|
+
|
|
112
|
+
### Use a coverage tool
|
|
113
|
+
|
|
114
|
+
Find modules, classes, and functions that are insufficiently tested. Don't skip trivial tests — their documentary value exceeds their cost.
|