@su-record/vibe 2.3.0 → 2.3.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/settings.json +35 -35
- package/.claude/settings.local.json +24 -25
- package/.claude/vibe/constitution.md +184 -184
- package/.claude/vibe/rules/core/communication-guide.md +104 -104
- package/.claude/vibe/rules/core/development-philosophy.md +52 -52
- package/.claude/vibe/rules/core/quick-start.md +120 -120
- package/.claude/vibe/rules/languages/dart-flutter.md +509 -509
- package/.claude/vibe/rules/languages/go.md +396 -396
- package/.claude/vibe/rules/languages/java-spring.md +586 -586
- package/.claude/vibe/rules/languages/kotlin-android.md +491 -491
- package/.claude/vibe/rules/languages/python-django.md +371 -371
- package/.claude/vibe/rules/languages/python-fastapi.md +386 -386
- package/.claude/vibe/rules/languages/rust.md +425 -425
- package/.claude/vibe/rules/languages/swift-ios.md +516 -516
- package/.claude/vibe/rules/languages/typescript-nextjs.md +441 -441
- package/.claude/vibe/rules/languages/typescript-node.md +375 -375
- package/.claude/vibe/rules/languages/typescript-nuxt.md +521 -521
- package/.claude/vibe/rules/languages/typescript-react-native.md +446 -446
- package/.claude/vibe/rules/languages/typescript-react.md +525 -525
- package/.claude/vibe/rules/languages/typescript-vue.md +353 -353
- package/.claude/vibe/rules/quality/bdd-contract-testing.md +388 -388
- package/.claude/vibe/rules/quality/checklist.md +276 -276
- package/.claude/vibe/rules/quality/testing-strategy.md +437 -437
- package/.claude/vibe/rules/standards/anti-patterns.md +369 -369
- package/.claude/vibe/rules/standards/code-structure.md +291 -291
- package/.claude/vibe/rules/standards/complexity-metrics.md +312 -312
- package/.claude/vibe/rules/standards/naming-conventions.md +198 -198
- package/.claude/vibe/setup.sh +31 -31
- package/.claude/vibe/templates/constitution-template.md +184 -184
- package/.claude/vibe/templates/contract-backend-template.md +517 -517
- package/.claude/vibe/templates/contract-frontend-template.md +594 -594
- package/.claude/vibe/templates/feature-template.md +96 -96
- package/.claude/vibe/templates/spec-template.md +199 -199
- package/CLAUDE.md +345 -323
- package/LICENSE +21 -21
- package/README.md +744 -724
- package/agents/compounder.md +261 -261
- package/agents/diagrammer.md +178 -178
- package/agents/e2e-tester.md +266 -266
- package/agents/explorer.md +48 -48
- package/agents/implementer.md +53 -53
- package/agents/research/best-practices-agent.md +139 -139
- package/agents/research/codebase-patterns-agent.md +147 -147
- package/agents/research/framework-docs-agent.md +181 -181
- package/agents/research/security-advisory-agent.md +167 -167
- package/agents/review/architecture-reviewer.md +107 -107
- package/agents/review/complexity-reviewer.md +116 -116
- package/agents/review/data-integrity-reviewer.md +88 -88
- package/agents/review/git-history-reviewer.md +103 -103
- package/agents/review/performance-reviewer.md +86 -86
- package/agents/review/python-reviewer.md +152 -152
- package/agents/review/rails-reviewer.md +139 -139
- package/agents/review/react-reviewer.md +144 -144
- package/agents/review/security-reviewer.md +80 -80
- package/agents/review/simplicity-reviewer.md +140 -140
- package/agents/review/test-coverage-reviewer.md +116 -116
- package/agents/review/typescript-reviewer.md +127 -127
- package/agents/searcher.md +54 -54
- package/agents/simplifier.md +119 -119
- package/agents/tester.md +49 -49
- package/agents/ui-previewer.md +137 -137
- package/commands/vibe.analyze.md +245 -180
- package/commands/vibe.reason.md +223 -183
- package/commands/vibe.review.md +200 -136
- package/commands/vibe.run.md +838 -836
- package/commands/vibe.spec.md +419 -383
- package/commands/vibe.utils.md +101 -101
- package/commands/vibe.verify.md +282 -241
- package/dist/cli/index.js +385 -385
- package/dist/lib/MemoryManager.d.ts.map +1 -1
- package/dist/lib/MemoryManager.js +119 -114
- package/dist/lib/MemoryManager.js.map +1 -1
- package/dist/lib/PythonParser.js +108 -108
- package/dist/lib/gemini-mcp.js +15 -15
- package/dist/lib/gemini-oauth.js +35 -35
- package/dist/lib/gpt-mcp.js +17 -17
- package/dist/lib/gpt-oauth.js +44 -44
- package/dist/tools/analytics/getUsageAnalytics.js +12 -12
- package/dist/tools/index.d.ts +50 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +61 -0
- package/dist/tools/index.js.map +1 -0
- 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/hooks/hooks.json +121 -103
- package/package.json +73 -69
- package/skills/git-worktree.md +178 -178
- package/skills/priority-todos.md +236 -236
|
@@ -1,369 +1,369 @@
|
|
|
1
|
-
# 🚫 자동 안티패턴 회피
|
|
2
|
-
|
|
3
|
-
## TypeScript 안티패턴
|
|
4
|
-
|
|
5
|
-
### 1. any 타입 사용
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
// ❌ any 사용
|
|
9
|
-
function processData(data: any) {
|
|
10
|
-
return data.value; // 타입 안전성 상실
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// ✅ unknown + type guard
|
|
14
|
-
function processData(data: unknown) {
|
|
15
|
-
if (isValidData(data)) {
|
|
16
|
-
return data.value; // 타입 안전
|
|
17
|
-
}
|
|
18
|
-
throw new Error('Invalid data');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isValidData(data: unknown): data is { value: string } {
|
|
22
|
-
return typeof data === 'object' && data !== null && 'value' in data;
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### 2. as any 강제 타입 캐스팅
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// ❌ as any로 타입 우회
|
|
30
|
-
const user = response as any;
|
|
31
|
-
user.name; // 런타임 에러 위험
|
|
32
|
-
|
|
33
|
-
// ✅ 적절한 타입 정의
|
|
34
|
-
interface User {
|
|
35
|
-
name: string;
|
|
36
|
-
email: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const user = response as User;
|
|
40
|
-
user.name; // 타입 안전
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 3. @ts-ignore 남용
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
// ❌ @ts-ignore로 에러 무시
|
|
47
|
-
// @ts-ignore
|
|
48
|
-
const result = problematicCode();
|
|
49
|
-
|
|
50
|
-
// ✅ 타입 문제 근본 해결
|
|
51
|
-
interface Expected {
|
|
52
|
-
id: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const result: Expected = {
|
|
56
|
-
id: String(problematicCode()),
|
|
57
|
-
};
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## React 안티패턴
|
|
61
|
-
|
|
62
|
-
### 1. dangerouslySetInnerHTML 사용
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
// ❌ XSS 취약점
|
|
66
|
-
function Component({ html }: { html: string }) {
|
|
67
|
-
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ✅ 안전한 렌더링
|
|
71
|
-
import DOMPurify from 'dompurify';
|
|
72
|
-
|
|
73
|
-
function Component({ html }: { html: string }) {
|
|
74
|
-
const sanitized = DOMPurify.sanitize(html);
|
|
75
|
-
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ✅ 더 나은 방법: 마크다운 라이브러리 사용
|
|
79
|
-
import ReactMarkdown from 'react-markdown';
|
|
80
|
-
|
|
81
|
-
function Component({ markdown }: { markdown: string }) {
|
|
82
|
-
return <ReactMarkdown>{markdown}</ReactMarkdown>;
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 2. Props Drilling (3단계 이상)
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
// ❌ Props drilling
|
|
90
|
-
function App() {
|
|
91
|
-
const [user, setUser] = useState<User>();
|
|
92
|
-
return <Parent user={user} />;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function Parent({ user }: { user: User }) {
|
|
96
|
-
return <Child user={user} />;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function Child({ user }: { user: User }) {
|
|
100
|
-
return <GrandChild user={user} />;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function GrandChild({ user }: { user: User }) {
|
|
104
|
-
return <div>{user.name}</div>;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ✅ Context API 사용
|
|
108
|
-
const UserContext = createContext<User | undefined>(undefined);
|
|
109
|
-
|
|
110
|
-
function App() {
|
|
111
|
-
const [user, setUser] = useState<User>();
|
|
112
|
-
return (
|
|
113
|
-
<UserContext.Provider value={user}>
|
|
114
|
-
<Parent />
|
|
115
|
-
</UserContext.Provider>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function GrandChild() {
|
|
120
|
-
const user = useContext(UserContext);
|
|
121
|
-
return <div>{user?.name}</div>;
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### 3. useEffect 의존성 배열 누락
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
// ❌ 의존성 누락
|
|
129
|
-
function Component({ userId }: { userId: string }) {
|
|
130
|
-
const [user, setUser] = useState<User>();
|
|
131
|
-
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
fetchUser(userId).then(setUser);
|
|
134
|
-
}, []); // userId 의존성 누락!
|
|
135
|
-
|
|
136
|
-
return <div>{user?.name}</div>;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ✅ 모든 의존성 명시
|
|
140
|
-
function Component({ userId }: { userId: string }) {
|
|
141
|
-
const [user, setUser] = useState<User>();
|
|
142
|
-
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
fetchUser(userId).then(setUser);
|
|
145
|
-
}, [userId]); // 의존성 명시
|
|
146
|
-
|
|
147
|
-
return <div>{user?.name}</div>;
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## JavaScript 안티패턴
|
|
152
|
-
|
|
153
|
-
### 1. var 사용
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
// ❌ var 사용
|
|
157
|
-
var count = 0;
|
|
158
|
-
if (true) {
|
|
159
|
-
var count = 1; // 같은 변수!
|
|
160
|
-
}
|
|
161
|
-
console.log(count); // 1
|
|
162
|
-
|
|
163
|
-
// ✅ const/let 사용
|
|
164
|
-
let count = 0;
|
|
165
|
-
if (true) {
|
|
166
|
-
let count = 1; // 블록 스코프
|
|
167
|
-
}
|
|
168
|
-
console.log(count); // 0
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### 2. == 사용 (느슨한 비교)
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
// ❌ == 사용
|
|
175
|
-
if (value == null) { } // undefined도 매칭
|
|
176
|
-
if ('5' == 5) { } // true (타입 강제 변환)
|
|
177
|
-
|
|
178
|
-
// ✅ === 사용
|
|
179
|
-
if (value === null) { }
|
|
180
|
-
if (value === undefined) { }
|
|
181
|
-
if ('5' === 5) { } // false
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 3. eval() 사용
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// ❌ eval() 사용 (보안 위험)
|
|
188
|
-
const code = userInput;
|
|
189
|
-
eval(code); // 임의 코드 실행 가능
|
|
190
|
-
|
|
191
|
-
// ✅ 대안 구현
|
|
192
|
-
const allowedOperations = {
|
|
193
|
-
add: (a: number, b: number) => a + b,
|
|
194
|
-
subtract: (a: number, b: number) => a - b,
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const operation = allowedOperations[userInput];
|
|
198
|
-
if (operation) {
|
|
199
|
-
result = operation(a, b);
|
|
200
|
-
}
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## CSS 안티패턴
|
|
204
|
-
|
|
205
|
-
### 1. !important 남용
|
|
206
|
-
|
|
207
|
-
```css
|
|
208
|
-
/* ❌ !important 남용 */
|
|
209
|
-
.button {
|
|
210
|
-
color: blue !important;
|
|
211
|
-
background: red !important;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/* ✅ 구체적인 선택자 사용 */
|
|
215
|
-
.navigation .button.primary {
|
|
216
|
-
color: blue;
|
|
217
|
-
background: red;
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### 2. 인라인 스타일 남용
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
// ❌ 인라인 스타일
|
|
225
|
-
function Button() {
|
|
226
|
-
return (
|
|
227
|
-
<button
|
|
228
|
-
style={{
|
|
229
|
-
backgroundColor: 'blue',
|
|
230
|
-
color: 'white',
|
|
231
|
-
padding: '10px',
|
|
232
|
-
borderRadius: '5px',
|
|
233
|
-
}}
|
|
234
|
-
>
|
|
235
|
-
Click me
|
|
236
|
-
</button>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ✅ CSS 클래스 사용
|
|
241
|
-
function Button() {
|
|
242
|
-
return <button className="btn-primary">Click me</button>;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// styles.css
|
|
246
|
-
.btn-primary {
|
|
247
|
-
background-color: blue;
|
|
248
|
-
color: white;
|
|
249
|
-
padding: 10px;
|
|
250
|
-
border-radius: 5px;
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## 성능 안티패턴
|
|
255
|
-
|
|
256
|
-
### 1. 불필요한 리렌더링
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
// ❌ 매번 새 객체/함수 생성
|
|
260
|
-
function Parent() {
|
|
261
|
-
return <Child config={{ theme: 'dark' }} onClick={() => {}} />;
|
|
262
|
-
// 매 렌더마다 새 객체/함수 생성 → Child 리렌더
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// ✅ useMemo/useCallback 사용
|
|
266
|
-
function Parent() {
|
|
267
|
-
const config = useMemo(() => ({ theme: 'dark' }), []);
|
|
268
|
-
const handleClick = useCallback(() => {}, []);
|
|
269
|
-
|
|
270
|
-
return <Child config={config} onClick={handleClick} />;
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### 2. 동기적 무거운 연산
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
// ❌ 메인 스레드 블로킹
|
|
278
|
-
function Component({ data }: { data: number[] }) {
|
|
279
|
-
const result = data
|
|
280
|
-
.map(heavyComputation)
|
|
281
|
-
.filter(x => x > 0)
|
|
282
|
-
.reduce((a, b) => a + b);
|
|
283
|
-
|
|
284
|
-
return <div>{result}</div>;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// ✅ useMemo로 메모이제이션
|
|
288
|
-
function Component({ data }: { data: number[] }) {
|
|
289
|
-
const result = useMemo(
|
|
290
|
-
() =>
|
|
291
|
-
data
|
|
292
|
-
.map(heavyComputation)
|
|
293
|
-
.filter(x => x > 0)
|
|
294
|
-
.reduce((a, b) => a + b),
|
|
295
|
-
[data]
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
return <div>{result}</div>;
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
## 보안 안티패턴
|
|
303
|
-
|
|
304
|
-
### 1. 민감 정보 하드코딩
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
// ❌ API 키 하드코딩
|
|
308
|
-
const API_KEY = 'sk-1234567890abcdef';
|
|
309
|
-
|
|
310
|
-
// ✅ 환경 변수 사용
|
|
311
|
-
const API_KEY = process.env.NEXT_PUBLIC_API_KEY;
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### 2. SQL Injection 취약점
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
// ❌ 직접 문자열 연결
|
|
318
|
-
const query = `SELECT * FROM users WHERE id = ${userId}`;
|
|
319
|
-
|
|
320
|
-
// ✅ 파라미터화된 쿼리
|
|
321
|
-
const query = 'SELECT * FROM users WHERE id = ?';
|
|
322
|
-
db.execute(query, [userId]);
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
## 에러 처리 안티패턴
|
|
326
|
-
|
|
327
|
-
### 1. 빈 catch 블록
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
// ❌ 에러 무시
|
|
331
|
-
try {
|
|
332
|
-
riskyOperation();
|
|
333
|
-
} catch (e) {
|
|
334
|
-
// 아무것도 안 함
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// ✅ 적절한 에러 처리
|
|
338
|
-
try {
|
|
339
|
-
riskyOperation();
|
|
340
|
-
} catch (error) {
|
|
341
|
-
console.error('Operation failed:', error);
|
|
342
|
-
showErrorNotification(error);
|
|
343
|
-
trackError(error);
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### 2. 에러 타입 확인 없이 처리
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
// ❌ 모든 에러 동일하게 처리
|
|
351
|
-
try {
|
|
352
|
-
await fetchData();
|
|
353
|
-
} catch (error) {
|
|
354
|
-
showError('Failed'); // 구체적이지 않음
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// ✅ 에러 타입별 처리
|
|
358
|
-
try {
|
|
359
|
-
await fetchData();
|
|
360
|
-
} catch (error) {
|
|
361
|
-
if (error instanceof NetworkError) {
|
|
362
|
-
showError('네트워크 연결을 확인해주세요');
|
|
363
|
-
} else if (error instanceof AuthError) {
|
|
364
|
-
redirectToLogin();
|
|
365
|
-
} else {
|
|
366
|
-
showError('알 수 없는 오류가 발생했습니다');
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
```
|
|
1
|
+
# 🚫 자동 안티패턴 회피
|
|
2
|
+
|
|
3
|
+
## TypeScript 안티패턴
|
|
4
|
+
|
|
5
|
+
### 1. any 타입 사용
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// ❌ any 사용
|
|
9
|
+
function processData(data: any) {
|
|
10
|
+
return data.value; // 타입 안전성 상실
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ✅ unknown + type guard
|
|
14
|
+
function processData(data: unknown) {
|
|
15
|
+
if (isValidData(data)) {
|
|
16
|
+
return data.value; // 타입 안전
|
|
17
|
+
}
|
|
18
|
+
throw new Error('Invalid data');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isValidData(data: unknown): data is { value: string } {
|
|
22
|
+
return typeof data === 'object' && data !== null && 'value' in data;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. as any 강제 타입 캐스팅
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// ❌ as any로 타입 우회
|
|
30
|
+
const user = response as any;
|
|
31
|
+
user.name; // 런타임 에러 위험
|
|
32
|
+
|
|
33
|
+
// ✅ 적절한 타입 정의
|
|
34
|
+
interface User {
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const user = response as User;
|
|
40
|
+
user.name; // 타입 안전
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. @ts-ignore 남용
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// ❌ @ts-ignore로 에러 무시
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
const result = problematicCode();
|
|
49
|
+
|
|
50
|
+
// ✅ 타입 문제 근본 해결
|
|
51
|
+
interface Expected {
|
|
52
|
+
id: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const result: Expected = {
|
|
56
|
+
id: String(problematicCode()),
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## React 안티패턴
|
|
61
|
+
|
|
62
|
+
### 1. dangerouslySetInnerHTML 사용
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// ❌ XSS 취약점
|
|
66
|
+
function Component({ html }: { html: string }) {
|
|
67
|
+
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ✅ 안전한 렌더링
|
|
71
|
+
import DOMPurify from 'dompurify';
|
|
72
|
+
|
|
73
|
+
function Component({ html }: { html: string }) {
|
|
74
|
+
const sanitized = DOMPurify.sanitize(html);
|
|
75
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ✅ 더 나은 방법: 마크다운 라이브러리 사용
|
|
79
|
+
import ReactMarkdown from 'react-markdown';
|
|
80
|
+
|
|
81
|
+
function Component({ markdown }: { markdown: string }) {
|
|
82
|
+
return <ReactMarkdown>{markdown}</ReactMarkdown>;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. Props Drilling (3단계 이상)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// ❌ Props drilling
|
|
90
|
+
function App() {
|
|
91
|
+
const [user, setUser] = useState<User>();
|
|
92
|
+
return <Parent user={user} />;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Parent({ user }: { user: User }) {
|
|
96
|
+
return <Child user={user} />;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function Child({ user }: { user: User }) {
|
|
100
|
+
return <GrandChild user={user} />;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function GrandChild({ user }: { user: User }) {
|
|
104
|
+
return <div>{user.name}</div>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ✅ Context API 사용
|
|
108
|
+
const UserContext = createContext<User | undefined>(undefined);
|
|
109
|
+
|
|
110
|
+
function App() {
|
|
111
|
+
const [user, setUser] = useState<User>();
|
|
112
|
+
return (
|
|
113
|
+
<UserContext.Provider value={user}>
|
|
114
|
+
<Parent />
|
|
115
|
+
</UserContext.Provider>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function GrandChild() {
|
|
120
|
+
const user = useContext(UserContext);
|
|
121
|
+
return <div>{user?.name}</div>;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 3. useEffect 의존성 배열 누락
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// ❌ 의존성 누락
|
|
129
|
+
function Component({ userId }: { userId: string }) {
|
|
130
|
+
const [user, setUser] = useState<User>();
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
fetchUser(userId).then(setUser);
|
|
134
|
+
}, []); // userId 의존성 누락!
|
|
135
|
+
|
|
136
|
+
return <div>{user?.name}</div>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ✅ 모든 의존성 명시
|
|
140
|
+
function Component({ userId }: { userId: string }) {
|
|
141
|
+
const [user, setUser] = useState<User>();
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
fetchUser(userId).then(setUser);
|
|
145
|
+
}, [userId]); // 의존성 명시
|
|
146
|
+
|
|
147
|
+
return <div>{user?.name}</div>;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## JavaScript 안티패턴
|
|
152
|
+
|
|
153
|
+
### 1. var 사용
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// ❌ var 사용
|
|
157
|
+
var count = 0;
|
|
158
|
+
if (true) {
|
|
159
|
+
var count = 1; // 같은 변수!
|
|
160
|
+
}
|
|
161
|
+
console.log(count); // 1
|
|
162
|
+
|
|
163
|
+
// ✅ const/let 사용
|
|
164
|
+
let count = 0;
|
|
165
|
+
if (true) {
|
|
166
|
+
let count = 1; // 블록 스코프
|
|
167
|
+
}
|
|
168
|
+
console.log(count); // 0
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 2. == 사용 (느슨한 비교)
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// ❌ == 사용
|
|
175
|
+
if (value == null) { } // undefined도 매칭
|
|
176
|
+
if ('5' == 5) { } // true (타입 강제 변환)
|
|
177
|
+
|
|
178
|
+
// ✅ === 사용
|
|
179
|
+
if (value === null) { }
|
|
180
|
+
if (value === undefined) { }
|
|
181
|
+
if ('5' === 5) { } // false
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 3. eval() 사용
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// ❌ eval() 사용 (보안 위험)
|
|
188
|
+
const code = userInput;
|
|
189
|
+
eval(code); // 임의 코드 실행 가능
|
|
190
|
+
|
|
191
|
+
// ✅ 대안 구현
|
|
192
|
+
const allowedOperations = {
|
|
193
|
+
add: (a: number, b: number) => a + b,
|
|
194
|
+
subtract: (a: number, b: number) => a - b,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const operation = allowedOperations[userInput];
|
|
198
|
+
if (operation) {
|
|
199
|
+
result = operation(a, b);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## CSS 안티패턴
|
|
204
|
+
|
|
205
|
+
### 1. !important 남용
|
|
206
|
+
|
|
207
|
+
```css
|
|
208
|
+
/* ❌ !important 남용 */
|
|
209
|
+
.button {
|
|
210
|
+
color: blue !important;
|
|
211
|
+
background: red !important;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* ✅ 구체적인 선택자 사용 */
|
|
215
|
+
.navigation .button.primary {
|
|
216
|
+
color: blue;
|
|
217
|
+
background: red;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 2. 인라인 스타일 남용
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// ❌ 인라인 스타일
|
|
225
|
+
function Button() {
|
|
226
|
+
return (
|
|
227
|
+
<button
|
|
228
|
+
style={{
|
|
229
|
+
backgroundColor: 'blue',
|
|
230
|
+
color: 'white',
|
|
231
|
+
padding: '10px',
|
|
232
|
+
borderRadius: '5px',
|
|
233
|
+
}}
|
|
234
|
+
>
|
|
235
|
+
Click me
|
|
236
|
+
</button>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ✅ CSS 클래스 사용
|
|
241
|
+
function Button() {
|
|
242
|
+
return <button className="btn-primary">Click me</button>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// styles.css
|
|
246
|
+
.btn-primary {
|
|
247
|
+
background-color: blue;
|
|
248
|
+
color: white;
|
|
249
|
+
padding: 10px;
|
|
250
|
+
border-radius: 5px;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 성능 안티패턴
|
|
255
|
+
|
|
256
|
+
### 1. 불필요한 리렌더링
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// ❌ 매번 새 객체/함수 생성
|
|
260
|
+
function Parent() {
|
|
261
|
+
return <Child config={{ theme: 'dark' }} onClick={() => {}} />;
|
|
262
|
+
// 매 렌더마다 새 객체/함수 생성 → Child 리렌더
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ✅ useMemo/useCallback 사용
|
|
266
|
+
function Parent() {
|
|
267
|
+
const config = useMemo(() => ({ theme: 'dark' }), []);
|
|
268
|
+
const handleClick = useCallback(() => {}, []);
|
|
269
|
+
|
|
270
|
+
return <Child config={config} onClick={handleClick} />;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 2. 동기적 무거운 연산
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// ❌ 메인 스레드 블로킹
|
|
278
|
+
function Component({ data }: { data: number[] }) {
|
|
279
|
+
const result = data
|
|
280
|
+
.map(heavyComputation)
|
|
281
|
+
.filter(x => x > 0)
|
|
282
|
+
.reduce((a, b) => a + b);
|
|
283
|
+
|
|
284
|
+
return <div>{result}</div>;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ✅ useMemo로 메모이제이션
|
|
288
|
+
function Component({ data }: { data: number[] }) {
|
|
289
|
+
const result = useMemo(
|
|
290
|
+
() =>
|
|
291
|
+
data
|
|
292
|
+
.map(heavyComputation)
|
|
293
|
+
.filter(x => x > 0)
|
|
294
|
+
.reduce((a, b) => a + b),
|
|
295
|
+
[data]
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
return <div>{result}</div>;
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## 보안 안티패턴
|
|
303
|
+
|
|
304
|
+
### 1. 민감 정보 하드코딩
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// ❌ API 키 하드코딩
|
|
308
|
+
const API_KEY = 'sk-1234567890abcdef';
|
|
309
|
+
|
|
310
|
+
// ✅ 환경 변수 사용
|
|
311
|
+
const API_KEY = process.env.NEXT_PUBLIC_API_KEY;
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### 2. SQL Injection 취약점
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// ❌ 직접 문자열 연결
|
|
318
|
+
const query = `SELECT * FROM users WHERE id = ${userId}`;
|
|
319
|
+
|
|
320
|
+
// ✅ 파라미터화된 쿼리
|
|
321
|
+
const query = 'SELECT * FROM users WHERE id = ?';
|
|
322
|
+
db.execute(query, [userId]);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## 에러 처리 안티패턴
|
|
326
|
+
|
|
327
|
+
### 1. 빈 catch 블록
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// ❌ 에러 무시
|
|
331
|
+
try {
|
|
332
|
+
riskyOperation();
|
|
333
|
+
} catch (e) {
|
|
334
|
+
// 아무것도 안 함
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ✅ 적절한 에러 처리
|
|
338
|
+
try {
|
|
339
|
+
riskyOperation();
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error('Operation failed:', error);
|
|
342
|
+
showErrorNotification(error);
|
|
343
|
+
trackError(error);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 2. 에러 타입 확인 없이 처리
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// ❌ 모든 에러 동일하게 처리
|
|
351
|
+
try {
|
|
352
|
+
await fetchData();
|
|
353
|
+
} catch (error) {
|
|
354
|
+
showError('Failed'); // 구체적이지 않음
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ✅ 에러 타입별 처리
|
|
358
|
+
try {
|
|
359
|
+
await fetchData();
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (error instanceof NetworkError) {
|
|
362
|
+
showError('네트워크 연결을 확인해주세요');
|
|
363
|
+
} else if (error instanceof AuthError) {
|
|
364
|
+
redirectToLogin();
|
|
365
|
+
} else {
|
|
366
|
+
showError('알 수 없는 오류가 발생했습니다');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|