@yaawn/workflow 0.0.0-stage → 0.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.
- 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 +228 -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 +67 -0
- package/agents/skills/yaawn-grill-story/references/story-checklist.md +43 -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 +65 -0
- package/agents/skills/yaawn-save-plan/scripts/plan-body.ts +331 -0
- package/agents/skills/yaawn-save-plan/scripts/save-plan.ts +443 -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,151 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Error handling is important, but if it obscures logic, it's wrong.
|
|
4
|
+
|
|
5
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapter 7: Error Handling
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
### Use exceptions rather than return codes
|
|
10
|
+
|
|
11
|
+
Returning error codes leads to deeply nested structures and forces callers to handle errors immediately.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// Bad — error codes force nested handling
|
|
15
|
+
function login(dto: AuthDto): ResponseCode {
|
|
16
|
+
if (!dto.email || !dto.password) return ResponseCode.BadRequest;
|
|
17
|
+
const user = userRepository.get(dto);
|
|
18
|
+
if (!user) return ResponseCode.UserNotFound;
|
|
19
|
+
if (!matchPassword(user.password, dto.password)) return ResponseCode.WrongPassword;
|
|
20
|
+
setUserSession(user);
|
|
21
|
+
return ResponseCode.Success;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Good — exceptions separate the happy path from error handling
|
|
25
|
+
function login(dto: AuthDto): void {
|
|
26
|
+
if (!dto.email || !dto.password) {
|
|
27
|
+
throw new BadRequestError('Email and password are required');
|
|
28
|
+
}
|
|
29
|
+
const user = userRepository.get(dto);
|
|
30
|
+
if (!user) {
|
|
31
|
+
throw new UserNotFoundError('Invalid username or password');
|
|
32
|
+
}
|
|
33
|
+
if (!matchPassword(user.password, dto.password)) {
|
|
34
|
+
throw new AuthError('Invalid username or password');
|
|
35
|
+
}
|
|
36
|
+
setUserSession(user);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Extract try/catch bodies
|
|
41
|
+
|
|
42
|
+
If `try` exists in a function, it should be the first keyword. Extract the body into a separate function. Nothing should follow the catch/finally block.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Bad
|
|
46
|
+
function performLogin(dto: AuthDto): void {
|
|
47
|
+
try {
|
|
48
|
+
login(dto);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error(error);
|
|
51
|
+
}
|
|
52
|
+
redirect(); // should be inside the try block
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Good
|
|
56
|
+
function performLogin(dto: AuthDto): void {
|
|
57
|
+
try {
|
|
58
|
+
login(dto);
|
|
59
|
+
redirect();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.error(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Provide context with exceptions
|
|
67
|
+
|
|
68
|
+
Each exception should provide enough context to determine the source and location of the error. Include the root cause when wrapping errors.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Bad — loses the original stack trace
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new AuthError('Cannot get user');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Good — preserves the cause chain
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw new AuthError('Cannot get user', { cause: error });
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Define exception classes by the caller's needs
|
|
83
|
+
|
|
84
|
+
Wrap third-party API errors into a single common exception type. This minimizes dependencies, makes it easier to switch libraries, and simplifies mocking in tests.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Bad — caller must handle every specific error
|
|
88
|
+
try {
|
|
89
|
+
port.open();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof DeviceResponseError) {
|
|
92
|
+
/* ... */
|
|
93
|
+
}
|
|
94
|
+
if (error instanceof UnlockedError) {
|
|
95
|
+
/* ... */
|
|
96
|
+
}
|
|
97
|
+
if (error instanceof GMXError) {
|
|
98
|
+
/* ... */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Good — wrapper normalizes the error surface
|
|
103
|
+
class PortDeviceFailure extends Error {
|
|
104
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
105
|
+
super(message, options);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Inside the port module
|
|
110
|
+
try {
|
|
111
|
+
// low-level operations
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new PortDeviceFailure('Device communication failed', { cause: error });
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Don't return null
|
|
118
|
+
|
|
119
|
+
Returning `null` leads to null-check cascades. Prefer throwing an exception or returning a special-case object (empty array, empty object, `Optional`/`Result` types).
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Bad
|
|
123
|
+
function getEmployees(): Employee[] | null {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const employees = getEmployees();
|
|
128
|
+
if (employees !== null) {
|
|
129
|
+
for (const e of employees) {
|
|
130
|
+
/* ... */
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Good
|
|
135
|
+
function getEmployees(): Employee[] {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const employees = getEmployees();
|
|
140
|
+
for (const e of employees) {
|
|
141
|
+
/* ... */
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Don't pass null
|
|
146
|
+
|
|
147
|
+
Passing `null` into methods is worse than returning it. Forbid passing `null` by default. Use TypeScript's `strictNullChecks` to enforce this at compile time.
|
|
148
|
+
|
|
149
|
+
### Fail fast
|
|
150
|
+
|
|
151
|
+
Detect problems as early as possible. Validate inputs at system boundaries. When something is wrong, fail immediately with a clear error rather than propagating corrupted state.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Formatting
|
|
2
|
+
|
|
3
|
+
Code formatting is important. It is about **communication**, and communication is the professional developer's first order of business.
|
|
4
|
+
|
|
5
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapter 5: Formatting
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
### Vertical openness between concepts
|
|
10
|
+
|
|
11
|
+
Use blank lines to separate distinct concepts: imports, property blocks, methods. Each blank line is a visual cue identifying a new concept.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Injectable } from '@angular/core';
|
|
15
|
+
import { HttpClient } from '@angular/common/http';
|
|
16
|
+
|
|
17
|
+
@Injectable({ providedIn: 'root' })
|
|
18
|
+
export class UserService {
|
|
19
|
+
readonly apiUrl = '/api/users';
|
|
20
|
+
readonly defaultPageSize = 20;
|
|
21
|
+
|
|
22
|
+
constructor(private http: HttpClient) {}
|
|
23
|
+
|
|
24
|
+
getUsers(): Observable<User[]> {
|
|
25
|
+
// ...
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getUser(id: string): Observable<User> {
|
|
29
|
+
// ...
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Use vertical density for tightly related concepts
|
|
35
|
+
|
|
36
|
+
Lines of code that are tightly related should appear vertically dense — don't separate them with blank lines or useless comments.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Bad — comments break the association
|
|
40
|
+
/** The class name of the reporter listener */
|
|
41
|
+
private className: string;
|
|
42
|
+
|
|
43
|
+
/** The properties of the reporter listener */
|
|
44
|
+
private properties: Property[] = [];
|
|
45
|
+
|
|
46
|
+
// Good — tightly related, no visual noise
|
|
47
|
+
private className: string;
|
|
48
|
+
private properties: Property[] = [];
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Place dependent functions vertically close
|
|
52
|
+
|
|
53
|
+
If one function calls another, they should be vertically close. The **caller should appear above the callee** (stepdown rule).
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Good — createUser calls validateUser and sendNotification, which follow it
|
|
57
|
+
createUser(dto: UserDto): void {
|
|
58
|
+
this.validateUser(dto);
|
|
59
|
+
this.userRepository.save(dto);
|
|
60
|
+
this.sendNotification(dto);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private validateUser(dto: UserDto): void { /* ... */ }
|
|
64
|
+
|
|
65
|
+
private sendNotification(dto: UserDto): void { /* ... */ }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Declare variables close to their usage
|
|
69
|
+
|
|
70
|
+
Don't declare all variables at the top of a function. Declare them where they are first used.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Bad — response declared too early
|
|
74
|
+
function userStatistics(users: User[]): UserStatisticResponse {
|
|
75
|
+
const response = new UserStatisticResponse();
|
|
76
|
+
const admins: User[] = [];
|
|
77
|
+
const guests: User[] = [];
|
|
78
|
+
// ... loop ...
|
|
79
|
+
response.admins = admins;
|
|
80
|
+
response.guests = guests;
|
|
81
|
+
return response;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Good — response declared where first needed
|
|
85
|
+
function userStatistics(users: User[]): UserStatisticResponse {
|
|
86
|
+
const admins: User[] = [];
|
|
87
|
+
const guests: User[] = [];
|
|
88
|
+
|
|
89
|
+
for (const user of users) {
|
|
90
|
+
if (isAdmin(user)) admins.push(user);
|
|
91
|
+
else guests.push(user);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const response = new UserStatisticResponse();
|
|
95
|
+
response.admins = admins;
|
|
96
|
+
response.guests = guests;
|
|
97
|
+
return response;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Use horizontal whitespace to show association
|
|
102
|
+
|
|
103
|
+
- **No** space between function name and opening parenthesis (they are closely related)
|
|
104
|
+
- **Yes** space around assignment operators (left and right sides are distinct)
|
|
105
|
+
- **Yes** space after commas in argument lists (arguments are separate entities)
|
|
106
|
+
|
|
107
|
+
### Avoid horizontal alignment
|
|
108
|
+
|
|
109
|
+
Don't align variable names or types into columns — it draws the eye to the wrong thing and is hard to maintain.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Bad
|
|
113
|
+
private socket: Socket;
|
|
114
|
+
private input: InputStream;
|
|
115
|
+
private output: OutputStream;
|
|
116
|
+
|
|
117
|
+
// Good
|
|
118
|
+
private socket: Socket;
|
|
119
|
+
private input: InputStream;
|
|
120
|
+
private output: OutputStream;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Don't break indentation
|
|
124
|
+
|
|
125
|
+
Never collapse short functions or if statements onto one line. Use proper block formatting.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Bad
|
|
129
|
+
constructor(parent: ParentWidget) { super(parent); }
|
|
130
|
+
render(): string { return ''; }
|
|
131
|
+
|
|
132
|
+
// Good
|
|
133
|
+
constructor(parent: ParentWidget) {
|
|
134
|
+
super(parent);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
render(): string {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Follow team rules
|
|
143
|
+
|
|
144
|
+
Individual style preferences give way to team conventions. The codebase should look like it was written by one person, not a committee of disagreeing individuals.
|
|
145
|
+
|
|
146
|
+
> **Project convention:** This codebase enforces formatting via `.editorconfig` and ESLint. Follow the established conventions.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Functions
|
|
2
|
+
|
|
3
|
+
Functions should be small and do one thing.
|
|
4
|
+
|
|
5
|
+
> Source: Robert C. Martin, _Clean Code_ — Chapter 3: Functions
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
9
|
+
### Small
|
|
10
|
+
|
|
11
|
+
The first rule of functions is that they should be small. The second rule is that they should be smaller than that. Aim for **15 lines or fewer**. If a function grows beyond that, start asking questions.
|
|
12
|
+
|
|
13
|
+
### Do one thing
|
|
14
|
+
|
|
15
|
+
A function should do one thing, do it well, and do it only. If you can extract another function from it with a name that is **not merely a restatement** of its implementation, it's doing more than one thing.
|
|
16
|
+
|
|
17
|
+
### One level of abstraction per function (SLAP)
|
|
18
|
+
|
|
19
|
+
Statements within a function should all be at the same level of abstraction. Don't mix high-level orchestration with low-level details.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Bad — mixed abstraction levels
|
|
23
|
+
function getUserReportDtos(): UserReportDto[] {
|
|
24
|
+
const users = CsvUtils.read('user.csv', User);
|
|
25
|
+
const dtos: UserReportDto[] = [];
|
|
26
|
+
if (users && users.length > 0) {
|
|
27
|
+
for (const user of users) {
|
|
28
|
+
const dto: UserReportDto = { name: user.name, email: user.email };
|
|
29
|
+
dtos.push(dto);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return dtos;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Good — each function is at one level of abstraction
|
|
36
|
+
function getUserReportDtos(): UserReportDto[] {
|
|
37
|
+
const users = readUsersFromCsvFile('user.csv');
|
|
38
|
+
return mapToUserReportDtos(users);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readUsersFromCsvFile(fileName: string): User[] {
|
|
42
|
+
return CsvUtils.read(fileName, User);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function mapToUserReportDtos(users: User[]): UserReportDto[] {
|
|
46
|
+
if (!isValid(users)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return users.map(mapToUserReportDto);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### The stepdown rule
|
|
54
|
+
|
|
55
|
+
Read code from top to bottom. Every function should be followed by those at the next level of abstraction.
|
|
56
|
+
|
|
57
|
+
### Avoid nested structures
|
|
58
|
+
|
|
59
|
+
Use early returns (guard clauses) instead of deeply nested if statements.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Bad
|
|
63
|
+
function isUserValid(userDto: UserDto): boolean {
|
|
64
|
+
if (userDto) {
|
|
65
|
+
if (userDto.password) {
|
|
66
|
+
if (userDto.email) {
|
|
67
|
+
return !userService.isEmailExists(userDto.email);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Good
|
|
75
|
+
function isUserValid(userDto: UserDto): boolean {
|
|
76
|
+
if (!userDto) return false;
|
|
77
|
+
if (!userDto.password) return false;
|
|
78
|
+
if (!userDto.email) return false;
|
|
79
|
+
return !userService.isEmailExists(userDto.email);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Avoid negative conditionals
|
|
84
|
+
|
|
85
|
+
Negatives are harder to understand than positives.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Bad
|
|
89
|
+
if (!isNotEmpty(users)) {
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Good
|
|
93
|
+
if (isEmpty(users)) {
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Replace switch statements with polymorphism
|
|
98
|
+
|
|
99
|
+
Switch statements violate SRP and OCP — they grow with every new type. Use polymorphism instead.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Bad
|
|
103
|
+
function perform(vehicle: Vehicle): void {
|
|
104
|
+
switch (vehicle.type) {
|
|
105
|
+
case 'bicycle':
|
|
106
|
+
vehicle.ride();
|
|
107
|
+
break;
|
|
108
|
+
case 'car':
|
|
109
|
+
vehicle.drive();
|
|
110
|
+
break;
|
|
111
|
+
case 'airplane':
|
|
112
|
+
vehicle.fly();
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Good — each type knows how to perform
|
|
118
|
+
interface VehicleAction {
|
|
119
|
+
perform(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class Bicycle implements VehicleAction {
|
|
123
|
+
perform(): void {
|
|
124
|
+
this.ride();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Use descriptive names
|
|
130
|
+
|
|
131
|
+
A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Bad
|
|
135
|
+
function createPrd(): void {} // Create a product
|
|
136
|
+
|
|
137
|
+
// Good
|
|
138
|
+
function createProduct(): void {}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Be consistent: use the same verbs across the codebase (`get` vs `fetch`, `set` vs `update`, `add` vs `append`).
|
|
142
|
+
|
|
143
|
+
### Prefer fewer arguments
|
|
144
|
+
|
|
145
|
+
Zero arguments (niladic) is ideal. One (monadic) and two (dyadic) are fine. Three (triadic) should be avoided. More than three requires strong justification.
|
|
146
|
+
|
|
147
|
+
When a function needs many arguments, wrap them in an object.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Bad
|
|
151
|
+
function createUser(username: string, password: string, gender: Gender, roleId: number): void {}
|
|
152
|
+
|
|
153
|
+
// Good
|
|
154
|
+
function createUser(userDto: CreateUserDto): void {}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Avoid flag arguments
|
|
158
|
+
|
|
159
|
+
A boolean argument declares the function does more than one thing. Split into two functions.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Bad
|
|
163
|
+
function execute(isTestSuite: boolean): void {
|
|
164
|
+
if (isTestSuite) {
|
|
165
|
+
/* suite logic */
|
|
166
|
+
} else {
|
|
167
|
+
/* single test logic */
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Good
|
|
172
|
+
function executeTestSuite(): void {}
|
|
173
|
+
function executeSingleTest(): void {}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Have no side effects
|
|
177
|
+
|
|
178
|
+
A function promises to do one thing — don't do hidden things.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Bad — "get" function secretly updates the database
|
|
182
|
+
function getUserByEmail(email: string): User {
|
|
183
|
+
const user = userRepository.getUserByEmail(email);
|
|
184
|
+
user.lastAccess = new Date();
|
|
185
|
+
userRepository.update(user);
|
|
186
|
+
return user;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Avoid output arguments
|
|
191
|
+
|
|
192
|
+
Don't mutate arguments passed to a function. If a function must change state, have it change the state of its owning object.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// Bad
|
|
196
|
+
function appendFooter(report: StringBuffer): void;
|
|
197
|
+
|
|
198
|
+
// Good
|
|
199
|
+
report.appendFooter();
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Separate command from query
|
|
203
|
+
|
|
204
|
+
Functions should either **do something** or **answer something**, but not both.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Bad
|
|
208
|
+
function set(attribute: string, value: string): boolean;
|
|
209
|
+
if (set('username', 'gpcoder')) {
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Good
|
|
213
|
+
if (attributeExists('username')) {
|
|
214
|
+
setAttribute('username', 'gpcoder');
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Prefer exceptions to returning error codes
|
|
219
|
+
|
|
220
|
+
Error codes lead to deeply nested structures and violate command-query separation.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Bad
|
|
224
|
+
function login(dto: AuthDto): ResponseCode {
|
|
225
|
+
if (!dto.email || !dto.password) return ResponseCode.BadRequest;
|
|
226
|
+
// ...
|
|
227
|
+
return ResponseCode.Success;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Good
|
|
231
|
+
function login(dto: AuthDto): void {
|
|
232
|
+
if (!dto.email || !dto.password) {
|
|
233
|
+
throw new BadRequestError('Email and password are required');
|
|
234
|
+
}
|
|
235
|
+
// ...
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Extract try/catch bodies
|
|
240
|
+
|
|
241
|
+
If `try` exists in a function, it should be the first word. Extract the body into its own function.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Good
|
|
245
|
+
function performLogin(dto: AuthDto): void {
|
|
246
|
+
try {
|
|
247
|
+
login(dto);
|
|
248
|
+
redirect();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
logger.error(error);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### DRY — Don't Repeat Yourself
|
|
256
|
+
|
|
257
|
+
Duplication is the root of all evil in software. Every piece of knowledge must have a single, unambiguous representation.
|
|
258
|
+
|
|
259
|
+
### Write, test, and refine
|
|
260
|
+
|
|
261
|
+
> "When I write functions, they come out long and complicated. But I also have unit tests that cover every one of those clumsy lines of code. So then I massage and refine that code, splitting out functions, changing names, eliminating duplication." — Robert C. Martin
|