@jakerdy/agentica 0.0.2
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/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/cli.js +64 -0
- package/dist/cli.js.map +27 -0
- package/globals/anti-spaghetti.md +65 -0
- package/globals/lang-python.md +105 -0
- package/globals/lang-typescript.md +61 -0
- package/globals/use-agentica.md +127 -0
- package/package.json +55 -0
- package/prompts/architect.prompt.md +302 -0
- package/prompts/change.prompt.md +915 -0
- package/prompts/create.prompt.md +953 -0
- package/prompts/implement.prompt.md +389 -0
- package/prompts/init.prompt.md +123 -0
- package/prompts/readme.prompt.md +355 -0
- package/prompts/refactor.prompt.md +712 -0
- package/prompts/reverse.prompt.md +777 -0
- package/prompts/tasks.prompt.md +1041 -0
- package/prompts/validate.prompt.md +480 -0
- package/stacks/python/cli/product.md +41 -0
- package/stacks/python/cli/structure.md +40 -0
- package/stacks/python/cli/tech.md +29 -0
- package/stacks/python/gui/product.md +41 -0
- package/stacks/python/gui/structure.md +41 -0
- package/stacks/python/gui/tech.md +29 -0
- package/stacks/python/lib/product.md +41 -0
- package/stacks/python/lib/structure.md +34 -0
- package/stacks/python/lib/tech.md +30 -0
- package/stacks/python/monorepo/product.md +41 -0
- package/stacks/python/monorepo/structure.md +29 -0
- package/stacks/python/monorepo/tech.md +30 -0
- package/stacks/typescript/cli/product.md +41 -0
- package/stacks/typescript/cli/structure.md +34 -0
- package/stacks/typescript/cli/tech.md +31 -0
- package/stacks/typescript/lib/product.md +41 -0
- package/stacks/typescript/lib/structure.md +33 -0
- package/stacks/typescript/lib/tech.md +31 -0
- package/stacks/typescript/monorepo/product.md +41 -0
- package/stacks/typescript/monorepo/structure.md +34 -0
- package/stacks/typescript/monorepo/tech.md +47 -0
- package/stacks/typescript/server/product.md +41 -0
- package/stacks/typescript/server/structure.md +35 -0
- package/stacks/typescript/server/tech.md +30 -0
- package/stacks/typescript/spa/product.md +41 -0
- package/stacks/typescript/spa/structure.md +36 -0
- package/stacks/typescript/spa/tech.md +29 -0
- package/templates/architecture/AR-0000 - /320/220/321/200/321/205/320/270/321/202/320/265/320/272/321/202/321/203/321/200/320/260 /321/204/320/270/321/207/320/270 /320/270/320/273/320/270 /320/274/320/276/320/264/321/203/320/273/321/217 XXX.md" +136 -0
- package/templates/change/CH-0000 - /320/230/320/267/320/274/320/265/320/275/320/265/320/275/320/270/320/265 /320/262 /321/204/320/270/321/207/320/265 /320/270/320/273/320/270 /320/274/320/276/320/264/321/203/320/273/320/265 XXX.md" +33 -0
- package/templates/feature/FT-0000 - /320/235/320/260/320/267/320/262/320/260/320/275/320/270/320/265 /321/204/320/270/321/207/320/270 /320/270/320/273/320/270 /320/274/320/276/320/264/321/203/320/273/321/217 XXX/product.md" +121 -0
- package/templates/feature/FT-0000 - /320/235/320/260/320/267/320/262/320/260/320/275/320/270/320/265 /321/204/320/270/321/207/320/270 /320/270/320/273/320/270 /320/274/320/276/320/264/321/203/320/273/321/217 XXX/tasks.md" +38 -0
- package/templates/feature/FT-0000 - /320/235/320/260/320/267/320/262/320/260/320/275/320/270/320/265 /321/204/320/270/321/207/320/270 /320/270/320/273/320/270 /320/274/320/276/320/264/321/203/320/273/321/217 XXX/tech.md" +155 -0
- package/templates/feature/FT-0000 - /320/235/320/260/320/267/320/262/320/260/320/275/320/270/320/265 /321/204/320/270/321/207/320/270 /320/270/320/273/320/270 /320/274/320/276/320/264/321/203/320/273/321/217 XXX/validation.md" +31 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentica.refactor
|
|
3
|
+
description: Многопроходный рефакторинг существующего кода без изменения публичного API
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Ввод пользователя
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
$ARGUMENTS
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Ты **ОБЯЗАН** учесть ввод пользователя (аргументы и контекст) перед тем как продолжить.
|
|
13
|
+
|
|
14
|
+
## Цель и принципы работы
|
|
15
|
+
|
|
16
|
+
Твоя задача — улучшить качество существующего кода, сделав его **плоским, простым и структурированным**, при этом **не меняя публичный API**.
|
|
17
|
+
Работай строго линейно: **Диагностика → Pass 1 → Pass 2 → Pass 3 → Pass 4 → Проверка → Отчёт**.
|
|
18
|
+
|
|
19
|
+
Рефакторинг — это **не переписывание с нуля**. Это систематическое улучшение кода через серию небольших трансформаций, каждая из которых сохраняет работоспособность.
|
|
20
|
+
|
|
21
|
+
### Глобальные запреты (Safety Guards)
|
|
22
|
+
|
|
23
|
+
Останови выполнение и не вноси изменения, если:
|
|
24
|
+
1. Пользователь просит **добавить новую функциональность** (используй `implement` или `change`).
|
|
25
|
+
2. Публичный API нужно изменить (сигнатуры экспортируемых функций/классов) — это не рефакторинг, а breaking change.
|
|
26
|
+
3. Код работает корректно, но пользователь просит "сделать по-другому" без технических причин — уточни обоснование.
|
|
27
|
+
4. Нет тестов, и изменения могут сломать поведение — предупреди о рисках.
|
|
28
|
+
5. Входные данные не указывают, **какой именно код** нужно рефакторить.
|
|
29
|
+
|
|
30
|
+
В случае остановки: объясни причину и предложи корректную команду.
|
|
31
|
+
|
|
32
|
+
### Главный инвариант: API остаётся неизменным
|
|
33
|
+
|
|
34
|
+
**До рефакторинга:**
|
|
35
|
+
```typescript
|
|
36
|
+
export function processData(input: string): Result { ... }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**После рефакторинга:**
|
|
40
|
+
```typescript
|
|
41
|
+
export function processData(input: string): Result {
|
|
42
|
+
// Внутренняя реализация изменилась, но сигнатура — НЕТ
|
|
43
|
+
const processor = new DataProcessor(input);
|
|
44
|
+
return processor.execute();
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Если изменение API неизбежно — это **не рефакторинг**. Остановись и предложи пользователю воспользоваться агентом: `change` для таких случаев.
|
|
49
|
+
|
|
50
|
+
## Фаза 0: Диагностика и анализ
|
|
51
|
+
|
|
52
|
+
### Шаг 0.1: Определение скоупа рефакторинга
|
|
53
|
+
|
|
54
|
+
1. Прочитай код, который пользователь указал для рефакторинга.
|
|
55
|
+
2. Определи границы изменений:
|
|
56
|
+
- **Один файл:** Локальный рефакторинг.
|
|
57
|
+
- **Несколько файлов:** Системный рефакторинг (требуется анализ зависимостей).
|
|
58
|
+
- **Весь модуль:** Архитектурный рефакторинг (может потребоваться `architect`).
|
|
59
|
+
|
|
60
|
+
3. Найди **публичный API** (экспортированные функции/классы) — его сигнатуры **нельзя** менять.
|
|
61
|
+
|
|
62
|
+
### Шаг 0.2: Чтение контекста
|
|
63
|
+
|
|
64
|
+
Прочитай следующие файлы из скоупа проекта:
|
|
65
|
+
1. `tech.md` — технические стандарты и соглашения.
|
|
66
|
+
2. Существующие тесты для выбранного кода (если есть).
|
|
67
|
+
3. Места использования рефакторируемого кода (чтобы понять, как он применяется).
|
|
68
|
+
|
|
69
|
+
### Шаг 0.3: Составление карты проблем
|
|
70
|
+
|
|
71
|
+
Проанализируй код и составь список проблем:
|
|
72
|
+
|
|
73
|
+
1. **10-этажные конструкции:** Вложенность > 3 уровней, цепочки методов на 5+ операций.
|
|
74
|
+
2. **Мега-функции:** Функции на 100+ строк с состоянием в замыканиях.
|
|
75
|
+
3. **Избыточные проверки:** 3-4 `if` перед простой операцией, дублирование валидаций.
|
|
76
|
+
4. **Функции с 5+ аргументами:** Нужно переписывать в виде класса.
|
|
77
|
+
5. **Магические числа/строки:** Не вынесены в константы.
|
|
78
|
+
6. **Смешанные уровни абстракции:** Низкоуровневые операции рядом с бизнес-логикой.
|
|
79
|
+
|
|
80
|
+
**Критерий запуска:** Если найдено **3+ проблемы** из списка — продолжай. Если меньше — уточни у пользователя, что именно улучшать.
|
|
81
|
+
|
|
82
|
+
## Pass 1 — Распаковка вложенности (Flatten)
|
|
83
|
+
|
|
84
|
+
**Цель:** Сделать код **плоским** — убрать пирамиды вложенности, распаковать 10-этажные конструкции.
|
|
85
|
+
|
|
86
|
+
### Правило 1.1: Ранний возврат (Early Return)
|
|
87
|
+
|
|
88
|
+
**Было:**
|
|
89
|
+
```typescript
|
|
90
|
+
function process(data: Data): Result {
|
|
91
|
+
if (data.isValid) {
|
|
92
|
+
if (data.hasPermission) {
|
|
93
|
+
if (data.isActive) {
|
|
94
|
+
// 100 строк логики
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Стало:**
|
|
103
|
+
```typescript
|
|
104
|
+
function process(data: Data): Result {
|
|
105
|
+
if (!data.isValid) return null;
|
|
106
|
+
if (!data.hasPermission) return null;
|
|
107
|
+
if (!data.isActive) return null;
|
|
108
|
+
|
|
109
|
+
// 100 строк логики на нулевом уровне вложенности
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Правило 1.2: Извлечение функций из вложенных блоков
|
|
114
|
+
|
|
115
|
+
**Было:**
|
|
116
|
+
```typescript
|
|
117
|
+
function process(items: Item[]): void {
|
|
118
|
+
items.forEach(item => {
|
|
119
|
+
if (item.type === 'A') {
|
|
120
|
+
// 30 строк логики
|
|
121
|
+
} else if (item.type === 'B') {
|
|
122
|
+
// 40 строк логики
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Стало:**
|
|
129
|
+
```typescript
|
|
130
|
+
function process(items: Item[]): void {
|
|
131
|
+
items.forEach(item => {
|
|
132
|
+
if (item.type === 'A') return handleTypeA(item);
|
|
133
|
+
if (item.type === 'B') return handleTypeB(item);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function handleTypeA(item: Item): void { ... }
|
|
138
|
+
function handleTypeB(item: Item): void { ... }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Правило 1.3: Распаковка цепочек методов
|
|
142
|
+
|
|
143
|
+
**Было:**
|
|
144
|
+
```typescript
|
|
145
|
+
const result = data.filter(x => x.active).map(x => x.value).reduce((a, b) => a + b, 0).toFixed(2);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Стало:**
|
|
149
|
+
```typescript
|
|
150
|
+
const activeItems = data.filter(x => x.active);
|
|
151
|
+
const values = activeItems.map(x => x.value);
|
|
152
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
153
|
+
const result = sum.toFixed(2);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Правило 1.4: Избегай `else` после `return`
|
|
157
|
+
|
|
158
|
+
**Было:**
|
|
159
|
+
```typescript
|
|
160
|
+
if (condition) {
|
|
161
|
+
return value;
|
|
162
|
+
} else {
|
|
163
|
+
// ...
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Стало:**
|
|
168
|
+
```typescript
|
|
169
|
+
if (condition) return value;
|
|
170
|
+
// ...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Критерий завершения Pass 1
|
|
174
|
+
|
|
175
|
+
Проверь код:
|
|
176
|
+
- ✅ Нет вложенности глубже 3 уровней.
|
|
177
|
+
- ✅ Цепочки методов разбиты на именованные переменные.
|
|
178
|
+
- ✅ Нет `else` после `return/throw/break`.
|
|
179
|
+
|
|
180
|
+
## Pass 2 — Очистка от избыточных проверок
|
|
181
|
+
|
|
182
|
+
**Цель:** Удалить **паранойю** — проверки, которые дублируются или бессмысленны в данном контексте.
|
|
183
|
+
|
|
184
|
+
### Правило 2.1: Не проверяй дважды
|
|
185
|
+
|
|
186
|
+
**Было:**
|
|
187
|
+
```typescript
|
|
188
|
+
function handleUser(user: User): void {
|
|
189
|
+
if (!user) throw new Error('User required');
|
|
190
|
+
if (!user.id) throw new Error('User ID required');
|
|
191
|
+
|
|
192
|
+
processUser(user); // внутри тоже проверяет user
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function processUser(user: User): void {
|
|
196
|
+
if (!user) throw new Error('User required'); // ДУБЛЬ
|
|
197
|
+
// ...
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Стало:**
|
|
202
|
+
```typescript
|
|
203
|
+
function handleUser(user: User): void {
|
|
204
|
+
if (!user) throw new Error('User required');
|
|
205
|
+
if (!user.id) throw new Error('User ID required');
|
|
206
|
+
|
|
207
|
+
processUser(user); // доверяем вызывающему коду
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function processUser(user: User): void {
|
|
211
|
+
// Только реальная логика, без повторных проверок
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Правило 2.2: Доверяй типизации
|
|
216
|
+
|
|
217
|
+
Если TypeScript гарантирует, что значение не `null/undefined` — не проверяй это вручную.
|
|
218
|
+
|
|
219
|
+
**Было:**
|
|
220
|
+
```typescript
|
|
221
|
+
function getName(user: User): string {
|
|
222
|
+
if (!user) return ''; // User не может быть null по типу
|
|
223
|
+
if (!user.name) return ''; // name: string, не может быть null
|
|
224
|
+
return user.name;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Стало:**
|
|
229
|
+
```typescript
|
|
230
|
+
function getName(user: User): string {
|
|
231
|
+
return user.name;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Правило 2.3: Убери defensive programming там, где он не нужен
|
|
236
|
+
|
|
237
|
+
Проверки нужны **на границах** системы (публичный API, пользовательский ввод). Внутри модуля между приватными функциями — **не нужны**.
|
|
238
|
+
|
|
239
|
+
**Было:**
|
|
240
|
+
```typescript
|
|
241
|
+
function calculateTotal(items: Item[]): number {
|
|
242
|
+
if (!items) return 0; // Внутренняя функция, items всегда валидный
|
|
243
|
+
if (!Array.isArray(items)) return 0; // TypeScript не позволит передать не-массив
|
|
244
|
+
if (items.length === 0) return 0; // Можно, но избыточно
|
|
245
|
+
|
|
246
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Стало:**
|
|
251
|
+
```typescript
|
|
252
|
+
function calculateTotal(items: Item[]): number {
|
|
253
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Правило:** Проверки на `null/undefined/length` нужны **только** в публичных функциях. Внутри модуля — доверяй типам.
|
|
258
|
+
|
|
259
|
+
### Критерий завершения Pass 2
|
|
260
|
+
|
|
261
|
+
Проверь код:
|
|
262
|
+
- ✅ Нет дублирующих проверок между вызовами.
|
|
263
|
+
- ✅ Внутренние функции не проверяют то, что гарантирует TypeScript.
|
|
264
|
+
- ✅ Defensive programming применяется **только** на границах системы.
|
|
265
|
+
|
|
266
|
+
## Pass 3 — Извлечение классов из мега-функций
|
|
267
|
+
|
|
268
|
+
**Цель:** Превратить функции на 200+ строк с состоянием в замыканиях в **классы** с чётким жизненным циклом.
|
|
269
|
+
|
|
270
|
+
### Когда превращать функцию в класс
|
|
271
|
+
|
|
272
|
+
1. **Функция > 100 строк** и содержит несколько локальных переменных, используемых в разных частях.
|
|
273
|
+
2. **Функция с 5+ аргументами** или передаёт одни и те же параметры вглубь вызовов.
|
|
274
|
+
3. **Состояние в замыканиях:** `let state = {}; function inner() { state.x = ...; }`.
|
|
275
|
+
4. **Сложная последовательность шагов:** Функция делает 5+ разных операций подряд.
|
|
276
|
+
|
|
277
|
+
### Паттерн трансформации
|
|
278
|
+
|
|
279
|
+
**Было (мега-функция):**
|
|
280
|
+
```typescript
|
|
281
|
+
export async function processOrder(
|
|
282
|
+
orderId: string,
|
|
283
|
+
userId: string,
|
|
284
|
+
items: Item[],
|
|
285
|
+
config: Config,
|
|
286
|
+
logger: Logger
|
|
287
|
+
): Promise<Result> {
|
|
288
|
+
let totalPrice = 0;
|
|
289
|
+
let appliedDiscounts: Discount[] = [];
|
|
290
|
+
let validationErrors: string[] = [];
|
|
291
|
+
|
|
292
|
+
// 50 строк валидации
|
|
293
|
+
for (const item of items) {
|
|
294
|
+
if (!item.id) validationErrors.push(`Invalid item`);
|
|
295
|
+
// ...
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 50 строк расчёта
|
|
299
|
+
for (const item of items) {
|
|
300
|
+
totalPrice += item.price * item.quantity;
|
|
301
|
+
// ...
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 50 строк применения скидок
|
|
305
|
+
if (config.discountsEnabled) {
|
|
306
|
+
// ...
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 50 строк сохранения
|
|
310
|
+
await saveOrder({ orderId, userId, totalPrice, appliedDiscounts });
|
|
311
|
+
logger.info('Order processed');
|
|
312
|
+
|
|
313
|
+
return { success: true, orderId, totalPrice };
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Стало (класс):**
|
|
318
|
+
```typescript
|
|
319
|
+
//------------------- Public API ------------------//
|
|
320
|
+
|
|
321
|
+
export async function processOrder(
|
|
322
|
+
orderId: string,
|
|
323
|
+
userId: string,
|
|
324
|
+
items: Item[],
|
|
325
|
+
config: Config,
|
|
326
|
+
logger: Logger
|
|
327
|
+
): Promise<Result> {
|
|
328
|
+
const processor = new OrderProcessor(orderId, userId, items, config, logger);
|
|
329
|
+
return processor.execute();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
//------------------- Order Processor ------------------//
|
|
333
|
+
|
|
334
|
+
class OrderProcessor {
|
|
335
|
+
private totalPrice = 0;
|
|
336
|
+
private appliedDiscounts: Discount[] = [];
|
|
337
|
+
private validationErrors: string[] = [];
|
|
338
|
+
|
|
339
|
+
constructor(
|
|
340
|
+
private readonly orderId: string,
|
|
341
|
+
private readonly userId: string,
|
|
342
|
+
private readonly items: Item[],
|
|
343
|
+
private readonly config: Config,
|
|
344
|
+
private readonly logger: Logger
|
|
345
|
+
) {}
|
|
346
|
+
|
|
347
|
+
async execute(): Promise<Result> {
|
|
348
|
+
this.validate();
|
|
349
|
+
this.calculate();
|
|
350
|
+
this.applyDiscounts();
|
|
351
|
+
await this.save();
|
|
352
|
+
|
|
353
|
+
return { success: true, orderId: this.orderId, totalPrice: this.totalPrice };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private validate(): void {
|
|
357
|
+
for (const item of this.items) {
|
|
358
|
+
if (!item.id) this.validationErrors.push(`Invalid item`);
|
|
359
|
+
}
|
|
360
|
+
if (this.validationErrors.length > 0) {
|
|
361
|
+
throw new ValidationError(this.validationErrors);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private calculate(): void {
|
|
366
|
+
this.totalPrice = this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private applyDiscounts(): void {
|
|
370
|
+
if (!this.config.discountsEnabled) return;
|
|
371
|
+
// ...
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private async save(): Promise<void> {
|
|
375
|
+
await saveOrder({
|
|
376
|
+
orderId: this.orderId,
|
|
377
|
+
userId: this.userId,
|
|
378
|
+
totalPrice: this.totalPrice,
|
|
379
|
+
appliedDiscounts: this.appliedDiscounts
|
|
380
|
+
});
|
|
381
|
+
this.logger.info('Order processed');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Правило 3.1: Состояние → Поля класса
|
|
387
|
+
|
|
388
|
+
Все `let` переменные, которые мутируются в процессе выполнения → `private` поля класса.
|
|
389
|
+
|
|
390
|
+
### Правило 3.2: Шаги последовательности → Методы
|
|
391
|
+
|
|
392
|
+
Если в функции есть комментарии вида `// Step 1`, `// Validation`, `// Calculate` — каждый блок становится отдельным методом.
|
|
393
|
+
|
|
394
|
+
### Правило 3.3: Аргументы → Constructor
|
|
395
|
+
|
|
396
|
+
Все параметры, которые используются в нескольких местах → `readonly` поля, инициализируемые в конструкторе.
|
|
397
|
+
|
|
398
|
+
### Правило 3.4: Публичная функция остаётся
|
|
399
|
+
|
|
400
|
+
Экспортированная функция **не удаляется** — она становится тонкой обёрткой над классом. Это сохраняет **API**.
|
|
401
|
+
|
|
402
|
+
### Правило 3.5: НЕ создавай 10 DTO для передачи одних и тех же данных
|
|
403
|
+
|
|
404
|
+
**Антипаттерн (плохо):**
|
|
405
|
+
```typescript
|
|
406
|
+
interface ValidateInput { orderId: string; userId: string; items: Item[]; }
|
|
407
|
+
interface CalculateInput { orderId: string; userId: string; items: Item[]; }
|
|
408
|
+
interface DiscountInput { orderId: string; userId: string; items: Item[]; config: Config; }
|
|
409
|
+
// ... и так далее
|
|
410
|
+
|
|
411
|
+
function validate(input: ValidateInput) { ... }
|
|
412
|
+
function calculate(input: CalculateInput) { ... }
|
|
413
|
+
function applyDiscount(input: DiscountInput) { ... }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Правильно (класс):**
|
|
417
|
+
```typescript
|
|
418
|
+
class OrderProcessor {
|
|
419
|
+
constructor(
|
|
420
|
+
private orderId: string,
|
|
421
|
+
private userId: string,
|
|
422
|
+
private items: Item[],
|
|
423
|
+
private config: Config
|
|
424
|
+
) {}
|
|
425
|
+
|
|
426
|
+
private validate() { /* использует this.orderId, this.items */ }
|
|
427
|
+
private calculate() { /* использует this.items */ }
|
|
428
|
+
private applyDiscount() { /* использует this.config, this.items */ }
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Почему:** Код должен быть **простым для восприятия**. Класс с полями читается легче, чем 10 DTO с одинаковыми полями.
|
|
433
|
+
|
|
434
|
+
### Критерий завершения Pass 3
|
|
435
|
+
|
|
436
|
+
Проверь код:
|
|
437
|
+
- ✅ Функции не длиннее 40 строк (кроме фабричных/экспортируемых обёрток).
|
|
438
|
+
- ✅ Нет функций с 5+ аргументами (либо DTO, либо класс).
|
|
439
|
+
- ✅ Состояние не хранится в замыканиях — только в полях класса.
|
|
440
|
+
- ✅ Экспортируемые функции остались, но стали тонкими обёртками.
|
|
441
|
+
|
|
442
|
+
## Pass 4 — Структурирование и именование
|
|
443
|
+
|
|
444
|
+
**Цель:** Навести финальный лоск — правильный порядок элементов, чёткие имена, визуальная структура.
|
|
445
|
+
|
|
446
|
+
### Правило 4.1: Порядок элементов в модуле
|
|
447
|
+
|
|
448
|
+
**Канонический порядок (сверху вниз):**
|
|
449
|
+
1. Импорты
|
|
450
|
+
2. Константы
|
|
451
|
+
3. Интерфейсы/типы
|
|
452
|
+
4. Публичные функции/классы (экспортируемые)
|
|
453
|
+
5. Приватные функции/классы
|
|
454
|
+
6. Утилитарные функции
|
|
455
|
+
|
|
456
|
+
**Визуальное разделение через заголовки:**
|
|
457
|
+
```typescript
|
|
458
|
+
//------------------- Constants ---------------------//
|
|
459
|
+
const MAX_RETRY = 3;
|
|
460
|
+
|
|
461
|
+
//--------------------- Types -----------------------//
|
|
462
|
+
interface OrderData { ... }
|
|
463
|
+
|
|
464
|
+
//------------------- Public API --------------------//
|
|
465
|
+
export function processOrder(...) { ... }
|
|
466
|
+
|
|
467
|
+
//---------------- Order Processor ------------------//
|
|
468
|
+
class OrderProcessor { ... }
|
|
469
|
+
|
|
470
|
+
//--------------------- Utils -----------------------//
|
|
471
|
+
function formatPrice(value: number): string { ... }
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Правило 4.2: Порядок элементов в классе
|
|
475
|
+
|
|
476
|
+
**Канонический порядок (сверху вниз):**
|
|
477
|
+
1. Приватные поля (состояние)
|
|
478
|
+
2. Конструктор
|
|
479
|
+
3. Публичные свойства (геттеры/сеттеры)
|
|
480
|
+
4. Публичные методы
|
|
481
|
+
5. Приватные методы
|
|
482
|
+
6. Утилитарные методы (статические/вспомогательные)
|
|
483
|
+
|
|
484
|
+
### Правило 4.3: Одноуровневые типы
|
|
485
|
+
|
|
486
|
+
Не вкладывай типы друг в друга. Создавай **отдельный интерфейс** для каждого "объекта", даже если он используется один раз.
|
|
487
|
+
|
|
488
|
+
**Плохо:**
|
|
489
|
+
```typescript
|
|
490
|
+
function process(data: { items: { id: string; meta: { tags: string[] } }[] }): void { ... }
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**Хорошо:**
|
|
494
|
+
```typescript
|
|
495
|
+
interface ItemMeta {
|
|
496
|
+
tags: string[];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
interface Item {
|
|
500
|
+
id: string;
|
|
501
|
+
meta: ItemMeta;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
interface ProcessData {
|
|
505
|
+
items: Item[];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function process(data: ProcessData): void { ... }
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Правило 4.4: Максимумы и ограничения
|
|
512
|
+
|
|
513
|
+
- **Функция/метод:** Максимум 30-40 строк (если больше — разбивай).
|
|
514
|
+
- **Вложенность:** Максимум 3-4 уровня (if/for/while).
|
|
515
|
+
- **Аргументы функции:** Максимум 4 (если больше — группируй в объект/класс).
|
|
516
|
+
- **Длина строки:** Максимум 100-120 символов.
|
|
517
|
+
- **Цепочка методов:** Максимум 2-3 вызова подряд (если больше — разбивай на переменные).
|
|
518
|
+
|
|
519
|
+
### Правило 4.5: Магические числа и строки
|
|
520
|
+
|
|
521
|
+
Все конфигурационные значения → константы с говорящими именами.
|
|
522
|
+
|
|
523
|
+
**Плохо:**
|
|
524
|
+
```typescript
|
|
525
|
+
if (retryCount > 3) { ... }
|
|
526
|
+
setTimeout(() => ..., 5000);
|
|
527
|
+
if (status === 'pending') { ... }
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Хорошо:**
|
|
531
|
+
```typescript
|
|
532
|
+
const MAX_RETRY_COUNT = 3;
|
|
533
|
+
const RETRY_DELAY_MS = 5000;
|
|
534
|
+
const STATUS_PENDING = 'pending';
|
|
535
|
+
|
|
536
|
+
if (retryCount > MAX_RETRY_COUNT) { ... }
|
|
537
|
+
setTimeout(() => ..., RETRY_DELAY_MS);
|
|
538
|
+
if (status === STATUS_PENDING) { ... }
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Правило 4.6: Чистота булевых выражений
|
|
542
|
+
|
|
543
|
+
Если условие содержит 2+ логических оператора → выносим в переменную или метод.
|
|
544
|
+
|
|
545
|
+
**Плохо:**
|
|
546
|
+
```typescript
|
|
547
|
+
if (user.age > 18 && user.hasPermission && !user.isBanned) { ... }
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Хорошо:**
|
|
551
|
+
```typescript
|
|
552
|
+
const canAccess = user.age > 18 && user.hasPermission && !user.isBanned;
|
|
553
|
+
if (canAccess) { ... }
|
|
554
|
+
|
|
555
|
+
// Или ещё лучше:
|
|
556
|
+
if (user.canAccessContent()) { ... }
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Правило 4.7: Инкапсуляция условий в классы
|
|
560
|
+
|
|
561
|
+
Логика проверок должна **принадлежать объекту**, владеющему данными.
|
|
562
|
+
|
|
563
|
+
**Плохо:**
|
|
564
|
+
```typescript
|
|
565
|
+
if (order.total > 100 && order.user.isPremium) {
|
|
566
|
+
applyDiscount(order);
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Хорошо:**
|
|
571
|
+
```typescript
|
|
572
|
+
if (order.canApplyDiscount()) {
|
|
573
|
+
applyDiscount(order);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// В классе Order:
|
|
577
|
+
class Order {
|
|
578
|
+
canApplyDiscount(): boolean {
|
|
579
|
+
return this.total > 100 && this.user.isPremium;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Правило 4.8: Императивное > Функционального
|
|
585
|
+
|
|
586
|
+
Если можно написать простой цикл — пиши цикл. Не надо `map/filter/reduce`, если это не улучшает читаемость.
|
|
587
|
+
|
|
588
|
+
**Плохо (переусложнение):**
|
|
589
|
+
```typescript
|
|
590
|
+
const result = items
|
|
591
|
+
.filter(x => x.active)
|
|
592
|
+
.map(x => ({ ...x, price: x.price * 1.2 }))
|
|
593
|
+
.reduce((acc, x) => acc + x.price, 0);
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
**Хорошо (если логика сложная):**
|
|
597
|
+
```typescript
|
|
598
|
+
let total = 0;
|
|
599
|
+
for (const item of items) {
|
|
600
|
+
if (!item.active) continue;
|
|
601
|
+
const adjustedPrice = item.price * 1.2;
|
|
602
|
+
total += adjustedPrice;
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Правило 4.9: Прямые импорты типов
|
|
607
|
+
|
|
608
|
+
Используй типы напрямую (добавляй импорты), вместо `typeof` или `SomeType["field"]`.
|
|
609
|
+
|
|
610
|
+
**Плохо:**
|
|
611
|
+
```typescript
|
|
612
|
+
type UserType = typeof user;
|
|
613
|
+
type NameType = User["name"];
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Хорошо:**
|
|
617
|
+
```typescript
|
|
618
|
+
import { User, UserName } from './types';
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Правило 4.10: Запреты
|
|
622
|
+
|
|
623
|
+
- ❌ **Вложенные стрелочные функции** с логикой (только короткие lambda-expression).
|
|
624
|
+
- ❌ **Вложенные тернарные операторы** (только однострочные тернары).
|
|
625
|
+
- ❌ **Методы, целиком завёрнутые в try-catch** (только на верхнем уровне публичного API).
|
|
626
|
+
- ❌ **Закомментированный код** (для истории есть Git).
|
|
627
|
+
- ❌ **Side Effects в геттерах** (методы `get...` не должны менять состояние).
|
|
628
|
+
- ❌ **Мутация входных параметров** (immutability, избегай побочных эффектов).
|
|
629
|
+
- ❌ **Boolean-флаги, меняющие логику** (лучше разделить на две функции).
|
|
630
|
+
|
|
631
|
+
### Критерий завершения Pass 4
|
|
632
|
+
|
|
633
|
+
Проверь код:
|
|
634
|
+
- ✅ Элементы модуля/класса упорядочены по каноническому порядку.
|
|
635
|
+
- ✅ Визуальные заголовки разделяют секции.
|
|
636
|
+
- ✅ Нет магических чисел/строк.
|
|
637
|
+
- ✅ Типы одноуровневые, с отдельными интерфейсами.
|
|
638
|
+
- ✅ Булевые выражения чистые (вынесены в переменные/методы).
|
|
639
|
+
- ✅ Все запреты соблюдены.
|
|
640
|
+
|
|
641
|
+
## Фаза 5: Финальная проверка
|
|
642
|
+
|
|
643
|
+
Перед завершением рефакторинга проверь:
|
|
644
|
+
|
|
645
|
+
### Чек-лист качества кода
|
|
646
|
+
|
|
647
|
+
1. **API не изменился:**
|
|
648
|
+
- Сигнатуры экспортируемых функций/классов идентичны.
|
|
649
|
+
- Поведение осталось прежним (если есть тесты — запусти их).
|
|
650
|
+
|
|
651
|
+
2. **Код плоский:**
|
|
652
|
+
- Вложенность не превышает 3 уровня.
|
|
653
|
+
- Нет 10-этажных цепочек методов.
|
|
654
|
+
|
|
655
|
+
3. **Код простой:**
|
|
656
|
+
- Функции не длиннее 40 строк.
|
|
657
|
+
- Нет функций с 5+ аргументами.
|
|
658
|
+
- Мега-функции превращены в классы.
|
|
659
|
+
|
|
660
|
+
4. **Код чистый:**
|
|
661
|
+
- Нет избыточных проверок.
|
|
662
|
+
- Нет дублирующих валидаций.
|
|
663
|
+
- Defensive programming только на границах.
|
|
664
|
+
|
|
665
|
+
5. **Код структурированный:**
|
|
666
|
+
- Порядок элементов канонический.
|
|
667
|
+
- Визуальные заголовки разделяют секции.
|
|
668
|
+
- Типы одноуровневые.
|
|
669
|
+
|
|
670
|
+
6. **Запреты соблюдены:**
|
|
671
|
+
- Нет закомментированного кода.
|
|
672
|
+
- Нет магических чисел/строк.
|
|
673
|
+
- Нет мутаций входных параметров.
|
|
674
|
+
|
|
675
|
+
Если хотя бы одна проверка провалена — **вернись к соответствующему Pass** и исправь.
|
|
676
|
+
|
|
677
|
+
## Фаза 6: Отчёт пользователю
|
|
678
|
+
|
|
679
|
+
Выдай короткое резюме:
|
|
680
|
+
1. **Изменённые файлы:** [список файлов].
|
|
681
|
+
2. **Выполненные трансформации:**
|
|
682
|
+
- Pass 1: Убрано X уровней вложенности, распакованы Y цепочек методов.
|
|
683
|
+
- Pass 2: Удалено Z избыточных проверок.
|
|
684
|
+
- Pass 3: Извлечено N классов из мега-функций.
|
|
685
|
+
- Pass 4: Упорядочено M модулей/классов, вынесено K констант.
|
|
686
|
+
3. **Метрики до/после:**
|
|
687
|
+
- Средняя длина функции: было 120 строк → стало 35 строк.
|
|
688
|
+
- Максимальная вложенность: было 6 уровней → стало 2 уровня.
|
|
689
|
+
4. **Публичный API:** Не изменился ✅.
|
|
690
|
+
5. **Рекомендация:** Запусти тесты (если есть) для проверки работоспособности.
|
|
691
|
+
|
|
692
|
+
## Дополнительные правила
|
|
693
|
+
|
|
694
|
+
### Работа с легаси-кодом
|
|
695
|
+
|
|
696
|
+
Если код **критически плох** (500+ строк в одной функции, 10+ уровней вложенности):
|
|
697
|
+
1. Предложи **постепенный рефакторинг** (по частям).
|
|
698
|
+
2. Начни с самых проблемных участков (Pass 1 → Pass 3).
|
|
699
|
+
3. Остальное оставь на следующие итерации.
|
|
700
|
+
|
|
701
|
+
### Работа без тестов
|
|
702
|
+
|
|
703
|
+
Если тестов нет:
|
|
704
|
+
1. Предупреди пользователя о рисках.
|
|
705
|
+
2. Делай **минимальные изменения** за раз.
|
|
706
|
+
3. Предложи добавить тесты до рефакторинга (через `implement`).
|
|
707
|
+
|
|
708
|
+
### Терминология и стиль
|
|
709
|
+
|
|
710
|
+
- Используй термины из `tech.md` проекта (если они есть).
|
|
711
|
+
- Пиши кратко, но **точно**. Не добавляй "лишних абстракций" — код должен быть **простым**, а не "красивым".
|
|
712
|
+
- Если можно написать 5 строк императивного кода или 1 строку функционального, но непонятного — выбирай императивный.
|