@su-record/vibe 2.0.0 โ 2.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/.claude/agents/explorer.md +48 -48
- package/.claude/agents/implementer.md +53 -53
- package/.claude/agents/searcher.md +54 -54
- package/.claude/agents/simplifier.md +119 -119
- package/.claude/agents/tester.md +49 -49
- package/.claude/commands/vibe.analyze.md +239 -239
- package/.claude/commands/vibe.continue.md +88 -88
- package/.claude/commands/vibe.diagram.md +178 -178
- package/.claude/commands/vibe.reason.md +306 -306
- package/.claude/commands/vibe.run.md +760 -760
- package/.claude/commands/vibe.spec.md +339 -339
- package/.claude/commands/vibe.tool.md +153 -153
- package/.claude/commands/vibe.ui.md +137 -137
- package/.claude/commands/vibe.verify.md +238 -238
- package/.claude/settings.json +152 -152
- package/.claude/settings.local.json +4 -57
- package/.vibe/config.json +9 -0
- package/.vibe/constitution.md +184 -184
- package/.vibe/rules/core/communication-guide.md +104 -104
- package/.vibe/rules/core/development-philosophy.md +52 -52
- package/.vibe/rules/core/quick-start.md +120 -120
- package/.vibe/rules/quality/bdd-contract-testing.md +388 -388
- package/.vibe/rules/quality/checklist.md +276 -276
- package/.vibe/rules/quality/testing-strategy.md +437 -437
- package/.vibe/rules/standards/anti-patterns.md +369 -369
- package/.vibe/rules/standards/code-structure.md +291 -291
- package/.vibe/rules/standards/complexity-metrics.md +312 -312
- package/.vibe/rules/standards/naming-conventions.md +198 -198
- package/.vibe/rules/tools/mcp-hi-ai-guide.md +665 -665
- package/.vibe/rules/tools/mcp-workflow.md +51 -51
- package/.vibe/setup.sh +31 -31
- package/CLAUDE.md +122 -122
- package/LICENSE +21 -21
- package/README.md +568 -568
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +391 -406
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/MemoryManager.js +92 -92
- package/dist/lib/PythonParser.js +108 -108
- package/dist/lib/gemini-mcp.js +15 -15
- package/dist/lib/gemini-oauth.d.ts.map +1 -1
- package/dist/lib/gemini-oauth.js +41 -38
- package/dist/lib/gemini-oauth.js.map +1 -1
- package/dist/lib/gpt-mcp.js +17 -17
- package/dist/lib/gpt-oauth.d.ts.map +1 -1
- package/dist/lib/gpt-oauth.js +50 -45
- package/dist/lib/gpt-oauth.js.map +1 -1
- package/dist/tools/analytics/getUsageAnalytics.js +12 -12
- package/dist/tools/memory/createMemoryTimeline.js +10 -10
- package/dist/tools/memory/getMemoryGraph.js +12 -12
- package/dist/tools/memory/getSessionContext.js +9 -9
- package/dist/tools/memory/linkMemories.js +14 -14
- package/dist/tools/memory/listMemories.js +4 -4
- package/dist/tools/memory/recallMemory.js +4 -4
- package/dist/tools/memory/saveMemory.js +4 -4
- package/dist/tools/memory/searchMemoriesAdvanced.js +22 -22
- package/dist/tools/planning/generatePrd.js +46 -46
- package/dist/tools/prompt/enhancePromptGemini.js +160 -160
- package/dist/tools/reasoning/applyReasoningFramework.js +56 -56
- package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
- package/package.json +67 -67
- package/templates/constitution-template.md +184 -184
- package/templates/contract-backend-template.md +517 -517
- package/templates/contract-frontend-template.md +594 -594
- package/templates/feature-template.md +96 -96
- package/templates/hooks-template.json +103 -103
- package/templates/spec-template.md +199 -199
- package/dist/lib/vibe-mcp.d.ts.map +0 -1
- package/dist/lib/vibe-mcp.js.map +0 -1
|
@@ -1,312 +1,312 @@
|
|
|
1
|
-
# ๐ฌ ์ํํธ์จ์ด ์์ง๋์ด๋ง ๋ณต์ก๋ ์ธก์
|
|
2
|
-
|
|
3
|
-
## 4.1 ๋ณต์ก๋ ๋ฉํธ๋ฆญ
|
|
4
|
-
|
|
5
|
-
### Cyclomatic Complexity (์ํ ๋ณต์ก๋)
|
|
6
|
-
|
|
7
|
-
**์ ์**: ์ฝ๋์ ๋
๋ฆฝ์ ์ธ ์คํ ๊ฒฝ๋ก ์
|
|
8
|
-
|
|
9
|
-
**๋ชฉํ**: โค 10
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
// โ ๋์ ์ํ ๋ณต์ก๋ (6)
|
|
13
|
-
function processUser(user) {
|
|
14
|
-
if (user.isActive) { // +1
|
|
15
|
-
if (user.hasPermission) { // +1
|
|
16
|
-
if (user.email) { // +1
|
|
17
|
-
if (user.verified) { // +1
|
|
18
|
-
return processData();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// โ
๋ฎ์ ์ํ ๋ณต์ก๋ (4) - Early returns ์ฌ์ฉ
|
|
27
|
-
function processUser(user) {
|
|
28
|
-
if (!user.isActive) return null; // +1
|
|
29
|
-
if (!user.hasPermission) return null; // +1
|
|
30
|
-
if (!user.email) return null; // +1
|
|
31
|
-
if (!user.verified) return null; // +1
|
|
32
|
-
|
|
33
|
-
return processData();
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Cognitive Complexity (์ธ์ง ๋ณต์ก๋)
|
|
38
|
-
|
|
39
|
-
**์ ์**: ์ฝ๋๋ฅผ ์ดํดํ๋ ๋ฐ ํ์ํ ์ ์ ์ ๋
ธ๋ ฅ
|
|
40
|
-
|
|
41
|
-
**๋ชฉํ**: โค 15
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
// โ ๋์ ์ธ์ง ๋ณต์ก๋
|
|
45
|
-
function calculateDiscount(user, items) {
|
|
46
|
-
let discount = 0;
|
|
47
|
-
if (user.isPremium) { // +1
|
|
48
|
-
for (let item of items) { // +1 (nesting)
|
|
49
|
-
if (item.category === 'electronics') { // +2 (nested if)
|
|
50
|
-
discount += item.price * 0.1;
|
|
51
|
-
} else if (item.category === 'books') { // +1
|
|
52
|
-
discount += item.price * 0.05;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return discount;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// โ
๋ฎ์ ์ธ์ง ๋ณต์ก๋ - ํจ์ ๋ถ๋ฆฌ
|
|
60
|
-
function calculateDiscount(user, items) {
|
|
61
|
-
if (!user.isPremium) return 0; // +1
|
|
62
|
-
return items.reduce((total, item) => total + getItemDiscount(item), 0);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getItemDiscount(item) {
|
|
66
|
-
const discountRates = {
|
|
67
|
-
electronics: 0.1,
|
|
68
|
-
books: 0.05,
|
|
69
|
-
};
|
|
70
|
-
return item.price * (discountRates[item.category] || 0);
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Halstead Metrics (ํ ์คํ
๋ ๋ฉํธ๋ฆญ)
|
|
75
|
-
|
|
76
|
-
**์ธก์ ํญ๋ชฉ**:
|
|
77
|
-
- **Operators**: ์ฐ์ฐ์ (=, +, -, *, if, for ๋ฑ)
|
|
78
|
-
- **Operands**: ํผ์ฐ์ฐ์ (๋ณ์, ์์, ํจ์๋ช
)
|
|
79
|
-
- **Vocabulary**: ๊ณ ์ ์ฐ์ฐ์ + ๊ณ ์ ํผ์ฐ์ฐ์
|
|
80
|
-
- **Length**: ์ ์ฒด ํ ํฐ ์
|
|
81
|
-
- **Difficulty**: ์ฝ๋ ์ดํด ๋์ด๋
|
|
82
|
-
- **Effort**: ์ฝ๋ ์์ฑ์ ํ์ํ ์ ์ ์ ๋
ธ๋ ฅ
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
// Halstead ๋ฉํธ๋ฆญ ์ธก์ ์์
|
|
86
|
-
function calculateArea(radius: number): number {
|
|
87
|
-
const pi = 3.14159;
|
|
88
|
-
return pi * radius * radius;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/*
|
|
92
|
-
Operators: =, *, const, function, :, return (6๊ฐ)
|
|
93
|
-
Operands: calculateArea, radius, number, pi, 3.14159 (5๊ฐ)
|
|
94
|
-
Vocabulary: 6 + 5 = 11
|
|
95
|
-
Length: ์ ์ฒด ํ ํฐ ์
|
|
96
|
-
Difficulty: Vocabulary์ operand ๋ฐ๋ณต์ผ๋ก ๊ณ์ฐ
|
|
97
|
-
Effort: Difficulty ร Volume
|
|
98
|
-
*/
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## 4.2 ๊ฒฐํฉ๋ & ์์ง๋
|
|
102
|
-
|
|
103
|
-
### ๋์จํ ๊ฒฐํฉ (Loose Coupling)
|
|
104
|
-
|
|
105
|
-
**๋ชฉํ**: ๋ชจ๋ ๊ฐ ์์กด์ฑ ์ต์ํ
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
// โ ๊ฐํ ๊ฒฐํฉ - ์ง์ ์์กด์ฑ
|
|
109
|
-
class UserService {
|
|
110
|
-
constructor() {
|
|
111
|
-
this.database = new PostgreSQLDatabase(); // ์ง์ ์์กด
|
|
112
|
-
this.emailService = new SendGridEmail(); // ์ง์ ์์กด
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// โ
๋์จํ ๊ฒฐํฉ - ์์กด์ฑ ์ฃผ์
|
|
117
|
-
interface IDatabase {
|
|
118
|
-
save(data: unknown): Promise<void>;
|
|
119
|
-
load(id: string): Promise<unknown>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
interface IEmailService {
|
|
123
|
-
send(to: string, message: string): Promise<void>;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
class UserService {
|
|
127
|
-
constructor(
|
|
128
|
-
private database: IDatabase,
|
|
129
|
-
private emailService: IEmailService
|
|
130
|
-
) {}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ์ฌ์ฉ
|
|
134
|
-
const userService = new UserService(
|
|
135
|
-
new PostgreSQLDatabase(),
|
|
136
|
-
new SendGridEmail()
|
|
137
|
-
);
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### ๋์ ์์ง๋ (High Cohesion)
|
|
141
|
-
|
|
142
|
-
**๋ชฉํ**: ๊ด๋ จ๋ ๊ธฐ๋ฅ๋ง ๋ชจ์
|
|
143
|
-
|
|
144
|
-
```typescript
|
|
145
|
-
// โ ๋ฎ์ ์์ง๋ - ๊ด๋ จ ์๋ ๊ธฐ๋ฅ๋ค
|
|
146
|
-
class Utils {
|
|
147
|
-
validateEmail(email: string) { /* */ }
|
|
148
|
-
formatCurrency(amount: number) { /* */ }
|
|
149
|
-
sendNotification(message: string) { /* */ }
|
|
150
|
-
calculateTax(income: number) { /* */ }
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// โ
๋์ ์์ง๋ - ๊ด๋ จ ๊ธฐ๋ฅ๋ง
|
|
154
|
-
class EmailValidator {
|
|
155
|
-
validateFormat(email: string) { /* */ }
|
|
156
|
-
validateDomain(email: string) { /* */ }
|
|
157
|
-
validateMX(email: string) { /* */ }
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
class CurrencyFormatter {
|
|
161
|
-
formatKRW(amount: number) { /* */ }
|
|
162
|
-
formatUSD(amount: number) { /* */ }
|
|
163
|
-
parseAmount(formatted: string) { /* */ }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
class TaxCalculator {
|
|
167
|
-
calculateIncomeTax(income: number) { /* */ }
|
|
168
|
-
calculateVAT(amount: number) { /* */ }
|
|
169
|
-
calculateTotal(income: number) { /* */ }
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
## ๋ณต์ก๋ ๊ฐ์ ์ ๋ต
|
|
174
|
-
|
|
175
|
-
### 1. Early Return ํจํด
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// โ ์ค์ฒฉ๋ if๋ฌธ
|
|
179
|
-
function processOrder(order: Order) {
|
|
180
|
-
if (order) {
|
|
181
|
-
if (order.isValid) {
|
|
182
|
-
if (order.items.length > 0) {
|
|
183
|
-
if (order.user.isActive) {
|
|
184
|
-
return processItems(order.items);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// โ
Early return
|
|
193
|
-
function processOrder(order: Order) {
|
|
194
|
-
if (!order) return null;
|
|
195
|
-
if (!order.isValid) return null;
|
|
196
|
-
if (order.items.length === 0) return null;
|
|
197
|
-
if (!order.user.isActive) return null;
|
|
198
|
-
|
|
199
|
-
return processItems(order.items);
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### 2. ์ ๋ต ํจํด (Strategy Pattern)
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
// โ ๋ณต์กํ if-else ์ฒด์ธ
|
|
207
|
-
function calculateShipping(type: string, weight: number) {
|
|
208
|
-
if (type === 'express') {
|
|
209
|
-
return weight * 5 + 10;
|
|
210
|
-
} else if (type === 'standard') {
|
|
211
|
-
return weight * 3 + 5;
|
|
212
|
-
} else if (type === 'economy') {
|
|
213
|
-
return weight * 2;
|
|
214
|
-
}
|
|
215
|
-
return 0;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// โ
์ ๋ต ํจํด
|
|
219
|
-
interface ShippingStrategy {
|
|
220
|
-
calculate(weight: number): number;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
class ExpressShipping implements ShippingStrategy {
|
|
224
|
-
calculate(weight: number) {
|
|
225
|
-
return weight * 5 + 10;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
class StandardShipping implements ShippingStrategy {
|
|
230
|
-
calculate(weight: number) {
|
|
231
|
-
return weight * 3 + 5;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const strategies: Record<string, ShippingStrategy> = {
|
|
236
|
-
express: new ExpressShipping(),
|
|
237
|
-
standard: new StandardShipping(),
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
function calculateShipping(type: string, weight: number) {
|
|
241
|
-
const strategy = strategies[type];
|
|
242
|
-
return strategy ? strategy.calculate(weight) : 0;
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### 3. ํจ์ ์ถ์ถ (Extract Function)
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
// โ ๊ธด ํจ์
|
|
250
|
-
function processUserRegistration(userData: UserData) {
|
|
251
|
-
// 20์ค: ์ด๋ฉ์ผ ๊ฒ์ฆ
|
|
252
|
-
// 15์ค: ๋น๋ฐ๋ฒํธ ํด์ฑ
|
|
253
|
-
// 10์ค: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ
|
|
254
|
-
// 5์ค: ํ์ ์ด๋ฉ์ผ ๋ฐ์ก
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// โ
ํจ์ ์ถ์ถ
|
|
258
|
-
function processUserRegistration(userData: UserData) {
|
|
259
|
-
validateEmail(userData.email);
|
|
260
|
-
const hashedPassword = hashPassword(userData.password);
|
|
261
|
-
const user = saveToDatabase({ ...userData, password: hashedPassword });
|
|
262
|
-
sendWelcomeEmail(user.email);
|
|
263
|
-
return user;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function validateEmail(email: string) { /* ... */ }
|
|
267
|
-
function hashPassword(password: string) { /* ... */ }
|
|
268
|
-
function saveToDatabase(data: UserData) { /* ... */ }
|
|
269
|
-
function sendWelcomeEmail(email: string) { /* ... */ }
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
## ์ธก์ ๋๊ตฌ
|
|
273
|
-
|
|
274
|
-
### TypeScript/JavaScript
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
# ESLint (๋ณต์ก๋ ์ธก์ ํ๋ฌ๊ทธ์ธ)
|
|
278
|
-
npm install eslint-plugin-complexity
|
|
279
|
-
|
|
280
|
-
# .eslintrc.js
|
|
281
|
-
{
|
|
282
|
-
"rules": {
|
|
283
|
-
"complexity": ["error", 10],
|
|
284
|
-
"max-depth": ["error", 3],
|
|
285
|
-
"max-lines-per-function": ["error", 20]
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### Python
|
|
291
|
-
|
|
292
|
-
```bash
|
|
293
|
-
# Radon (๋ณต์ก๋ ์ธก์ ๋๊ตฌ)
|
|
294
|
-
pip install radon
|
|
295
|
-
|
|
296
|
-
# Cyclomatic Complexity
|
|
297
|
-
radon cc app/ -a -nc
|
|
298
|
-
|
|
299
|
-
# Maintainability Index
|
|
300
|
-
radon mi app/
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
## ๋ชฉํ ๋ฉํธ๋ฆญ ์์ฝ
|
|
304
|
-
|
|
305
|
-
| ๋ฉํธ๋ฆญ | ๋ชฉํ | ์ค๋ช
|
|
|
306
|
-
|--------|------|------|
|
|
307
|
-
| Cyclomatic Complexity | โค 10 | ๋
๋ฆฝ์ ์คํ ๊ฒฝ๋ก |
|
|
308
|
-
| Cognitive Complexity | โค 15 | ์ดํดํ๊ธฐ ์ฌ์ |
|
|
309
|
-
| Function Length | โค 20 lines | ์งง๊ณ ์ง์ค๋ ํจ์ |
|
|
310
|
-
| Nesting Depth | โค 3 levels | ํํํ ๊ตฌ์กฐ |
|
|
311
|
-
| Parameters | โค 5 | ํจ์ ๋งค๊ฐ๋ณ์ ์ ํ |
|
|
312
|
-
| Dependencies | โค 7 | ๋ชจ๋ ์์กด์ฑ ์ ํ |
|
|
1
|
+
# ๐ฌ ์ํํธ์จ์ด ์์ง๋์ด๋ง ๋ณต์ก๋ ์ธก์
|
|
2
|
+
|
|
3
|
+
## 4.1 ๋ณต์ก๋ ๋ฉํธ๋ฆญ
|
|
4
|
+
|
|
5
|
+
### Cyclomatic Complexity (์ํ ๋ณต์ก๋)
|
|
6
|
+
|
|
7
|
+
**์ ์**: ์ฝ๋์ ๋
๋ฆฝ์ ์ธ ์คํ ๊ฒฝ๋ก ์
|
|
8
|
+
|
|
9
|
+
**๋ชฉํ**: โค 10
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// โ ๋์ ์ํ ๋ณต์ก๋ (6)
|
|
13
|
+
function processUser(user) {
|
|
14
|
+
if (user.isActive) { // +1
|
|
15
|
+
if (user.hasPermission) { // +1
|
|
16
|
+
if (user.email) { // +1
|
|
17
|
+
if (user.verified) { // +1
|
|
18
|
+
return processData();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// โ
๋ฎ์ ์ํ ๋ณต์ก๋ (4) - Early returns ์ฌ์ฉ
|
|
27
|
+
function processUser(user) {
|
|
28
|
+
if (!user.isActive) return null; // +1
|
|
29
|
+
if (!user.hasPermission) return null; // +1
|
|
30
|
+
if (!user.email) return null; // +1
|
|
31
|
+
if (!user.verified) return null; // +1
|
|
32
|
+
|
|
33
|
+
return processData();
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Cognitive Complexity (์ธ์ง ๋ณต์ก๋)
|
|
38
|
+
|
|
39
|
+
**์ ์**: ์ฝ๋๋ฅผ ์ดํดํ๋ ๋ฐ ํ์ํ ์ ์ ์ ๋
ธ๋ ฅ
|
|
40
|
+
|
|
41
|
+
**๋ชฉํ**: โค 15
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// โ ๋์ ์ธ์ง ๋ณต์ก๋
|
|
45
|
+
function calculateDiscount(user, items) {
|
|
46
|
+
let discount = 0;
|
|
47
|
+
if (user.isPremium) { // +1
|
|
48
|
+
for (let item of items) { // +1 (nesting)
|
|
49
|
+
if (item.category === 'electronics') { // +2 (nested if)
|
|
50
|
+
discount += item.price * 0.1;
|
|
51
|
+
} else if (item.category === 'books') { // +1
|
|
52
|
+
discount += item.price * 0.05;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return discount;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// โ
๋ฎ์ ์ธ์ง ๋ณต์ก๋ - ํจ์ ๋ถ๋ฆฌ
|
|
60
|
+
function calculateDiscount(user, items) {
|
|
61
|
+
if (!user.isPremium) return 0; // +1
|
|
62
|
+
return items.reduce((total, item) => total + getItemDiscount(item), 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getItemDiscount(item) {
|
|
66
|
+
const discountRates = {
|
|
67
|
+
electronics: 0.1,
|
|
68
|
+
books: 0.05,
|
|
69
|
+
};
|
|
70
|
+
return item.price * (discountRates[item.category] || 0);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Halstead Metrics (ํ ์คํ
๋ ๋ฉํธ๋ฆญ)
|
|
75
|
+
|
|
76
|
+
**์ธก์ ํญ๋ชฉ**:
|
|
77
|
+
- **Operators**: ์ฐ์ฐ์ (=, +, -, *, if, for ๋ฑ)
|
|
78
|
+
- **Operands**: ํผ์ฐ์ฐ์ (๋ณ์, ์์, ํจ์๋ช
)
|
|
79
|
+
- **Vocabulary**: ๊ณ ์ ์ฐ์ฐ์ + ๊ณ ์ ํผ์ฐ์ฐ์
|
|
80
|
+
- **Length**: ์ ์ฒด ํ ํฐ ์
|
|
81
|
+
- **Difficulty**: ์ฝ๋ ์ดํด ๋์ด๋
|
|
82
|
+
- **Effort**: ์ฝ๋ ์์ฑ์ ํ์ํ ์ ์ ์ ๋
ธ๋ ฅ
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Halstead ๋ฉํธ๋ฆญ ์ธก์ ์์
|
|
86
|
+
function calculateArea(radius: number): number {
|
|
87
|
+
const pi = 3.14159;
|
|
88
|
+
return pi * radius * radius;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
Operators: =, *, const, function, :, return (6๊ฐ)
|
|
93
|
+
Operands: calculateArea, radius, number, pi, 3.14159 (5๊ฐ)
|
|
94
|
+
Vocabulary: 6 + 5 = 11
|
|
95
|
+
Length: ์ ์ฒด ํ ํฐ ์
|
|
96
|
+
Difficulty: Vocabulary์ operand ๋ฐ๋ณต์ผ๋ก ๊ณ์ฐ
|
|
97
|
+
Effort: Difficulty ร Volume
|
|
98
|
+
*/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 4.2 ๊ฒฐํฉ๋ & ์์ง๋
|
|
102
|
+
|
|
103
|
+
### ๋์จํ ๊ฒฐํฉ (Loose Coupling)
|
|
104
|
+
|
|
105
|
+
**๋ชฉํ**: ๋ชจ๋ ๊ฐ ์์กด์ฑ ์ต์ํ
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// โ ๊ฐํ ๊ฒฐํฉ - ์ง์ ์์กด์ฑ
|
|
109
|
+
class UserService {
|
|
110
|
+
constructor() {
|
|
111
|
+
this.database = new PostgreSQLDatabase(); // ์ง์ ์์กด
|
|
112
|
+
this.emailService = new SendGridEmail(); // ์ง์ ์์กด
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// โ
๋์จํ ๊ฒฐํฉ - ์์กด์ฑ ์ฃผ์
|
|
117
|
+
interface IDatabase {
|
|
118
|
+
save(data: unknown): Promise<void>;
|
|
119
|
+
load(id: string): Promise<unknown>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface IEmailService {
|
|
123
|
+
send(to: string, message: string): Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class UserService {
|
|
127
|
+
constructor(
|
|
128
|
+
private database: IDatabase,
|
|
129
|
+
private emailService: IEmailService
|
|
130
|
+
) {}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ์ฌ์ฉ
|
|
134
|
+
const userService = new UserService(
|
|
135
|
+
new PostgreSQLDatabase(),
|
|
136
|
+
new SendGridEmail()
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### ๋์ ์์ง๋ (High Cohesion)
|
|
141
|
+
|
|
142
|
+
**๋ชฉํ**: ๊ด๋ จ๋ ๊ธฐ๋ฅ๋ง ๋ชจ์
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// โ ๋ฎ์ ์์ง๋ - ๊ด๋ จ ์๋ ๊ธฐ๋ฅ๋ค
|
|
146
|
+
class Utils {
|
|
147
|
+
validateEmail(email: string) { /* */ }
|
|
148
|
+
formatCurrency(amount: number) { /* */ }
|
|
149
|
+
sendNotification(message: string) { /* */ }
|
|
150
|
+
calculateTax(income: number) { /* */ }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// โ
๋์ ์์ง๋ - ๊ด๋ จ ๊ธฐ๋ฅ๋ง
|
|
154
|
+
class EmailValidator {
|
|
155
|
+
validateFormat(email: string) { /* */ }
|
|
156
|
+
validateDomain(email: string) { /* */ }
|
|
157
|
+
validateMX(email: string) { /* */ }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class CurrencyFormatter {
|
|
161
|
+
formatKRW(amount: number) { /* */ }
|
|
162
|
+
formatUSD(amount: number) { /* */ }
|
|
163
|
+
parseAmount(formatted: string) { /* */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
class TaxCalculator {
|
|
167
|
+
calculateIncomeTax(income: number) { /* */ }
|
|
168
|
+
calculateVAT(amount: number) { /* */ }
|
|
169
|
+
calculateTotal(income: number) { /* */ }
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## ๋ณต์ก๋ ๊ฐ์ ์ ๋ต
|
|
174
|
+
|
|
175
|
+
### 1. Early Return ํจํด
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// โ ์ค์ฒฉ๋ if๋ฌธ
|
|
179
|
+
function processOrder(order: Order) {
|
|
180
|
+
if (order) {
|
|
181
|
+
if (order.isValid) {
|
|
182
|
+
if (order.items.length > 0) {
|
|
183
|
+
if (order.user.isActive) {
|
|
184
|
+
return processItems(order.items);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// โ
Early return
|
|
193
|
+
function processOrder(order: Order) {
|
|
194
|
+
if (!order) return null;
|
|
195
|
+
if (!order.isValid) return null;
|
|
196
|
+
if (order.items.length === 0) return null;
|
|
197
|
+
if (!order.user.isActive) return null;
|
|
198
|
+
|
|
199
|
+
return processItems(order.items);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 2. ์ ๋ต ํจํด (Strategy Pattern)
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// โ ๋ณต์กํ if-else ์ฒด์ธ
|
|
207
|
+
function calculateShipping(type: string, weight: number) {
|
|
208
|
+
if (type === 'express') {
|
|
209
|
+
return weight * 5 + 10;
|
|
210
|
+
} else if (type === 'standard') {
|
|
211
|
+
return weight * 3 + 5;
|
|
212
|
+
} else if (type === 'economy') {
|
|
213
|
+
return weight * 2;
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// โ
์ ๋ต ํจํด
|
|
219
|
+
interface ShippingStrategy {
|
|
220
|
+
calculate(weight: number): number;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
class ExpressShipping implements ShippingStrategy {
|
|
224
|
+
calculate(weight: number) {
|
|
225
|
+
return weight * 5 + 10;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
class StandardShipping implements ShippingStrategy {
|
|
230
|
+
calculate(weight: number) {
|
|
231
|
+
return weight * 3 + 5;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const strategies: Record<string, ShippingStrategy> = {
|
|
236
|
+
express: new ExpressShipping(),
|
|
237
|
+
standard: new StandardShipping(),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
function calculateShipping(type: string, weight: number) {
|
|
241
|
+
const strategy = strategies[type];
|
|
242
|
+
return strategy ? strategy.calculate(weight) : 0;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 3. ํจ์ ์ถ์ถ (Extract Function)
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// โ ๊ธด ํจ์
|
|
250
|
+
function processUserRegistration(userData: UserData) {
|
|
251
|
+
// 20์ค: ์ด๋ฉ์ผ ๊ฒ์ฆ
|
|
252
|
+
// 15์ค: ๋น๋ฐ๋ฒํธ ํด์ฑ
|
|
253
|
+
// 10์ค: ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ
|
|
254
|
+
// 5์ค: ํ์ ์ด๋ฉ์ผ ๋ฐ์ก
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// โ
ํจ์ ์ถ์ถ
|
|
258
|
+
function processUserRegistration(userData: UserData) {
|
|
259
|
+
validateEmail(userData.email);
|
|
260
|
+
const hashedPassword = hashPassword(userData.password);
|
|
261
|
+
const user = saveToDatabase({ ...userData, password: hashedPassword });
|
|
262
|
+
sendWelcomeEmail(user.email);
|
|
263
|
+
return user;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function validateEmail(email: string) { /* ... */ }
|
|
267
|
+
function hashPassword(password: string) { /* ... */ }
|
|
268
|
+
function saveToDatabase(data: UserData) { /* ... */ }
|
|
269
|
+
function sendWelcomeEmail(email: string) { /* ... */ }
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## ์ธก์ ๋๊ตฌ
|
|
273
|
+
|
|
274
|
+
### TypeScript/JavaScript
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# ESLint (๋ณต์ก๋ ์ธก์ ํ๋ฌ๊ทธ์ธ)
|
|
278
|
+
npm install eslint-plugin-complexity
|
|
279
|
+
|
|
280
|
+
# .eslintrc.js
|
|
281
|
+
{
|
|
282
|
+
"rules": {
|
|
283
|
+
"complexity": ["error", 10],
|
|
284
|
+
"max-depth": ["error", 3],
|
|
285
|
+
"max-lines-per-function": ["error", 20]
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Python
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Radon (๋ณต์ก๋ ์ธก์ ๋๊ตฌ)
|
|
294
|
+
pip install radon
|
|
295
|
+
|
|
296
|
+
# Cyclomatic Complexity
|
|
297
|
+
radon cc app/ -a -nc
|
|
298
|
+
|
|
299
|
+
# Maintainability Index
|
|
300
|
+
radon mi app/
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## ๋ชฉํ ๋ฉํธ๋ฆญ ์์ฝ
|
|
304
|
+
|
|
305
|
+
| ๋ฉํธ๋ฆญ | ๋ชฉํ | ์ค๋ช
|
|
|
306
|
+
|--------|------|------|
|
|
307
|
+
| Cyclomatic Complexity | โค 10 | ๋
๋ฆฝ์ ์คํ ๊ฒฝ๋ก |
|
|
308
|
+
| Cognitive Complexity | โค 15 | ์ดํดํ๊ธฐ ์ฌ์ |
|
|
309
|
+
| Function Length | โค 20 lines | ์งง๊ณ ์ง์ค๋ ํจ์ |
|
|
310
|
+
| Nesting Depth | โค 3 levels | ํํํ ๊ตฌ์กฐ |
|
|
311
|
+
| Parameters | โค 5 | ํจ์ ๋งค๊ฐ๋ณ์ ์ ํ |
|
|
312
|
+
| Dependencies | โค 7 | ๋ชจ๋ ์์กด์ฑ ์ ํ |
|