@su-record/vibe 2.4.17 β†’ 2.4.19

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.
Files changed (81) hide show
  1. package/.claude/settings.json +48 -48
  2. package/.claude/settings.local.json +28 -28
  3. package/.claude/vibe/constitution.md +184 -184
  4. package/.claude/vibe/rules/core/communication-guide.md +104 -104
  5. package/.claude/vibe/rules/core/development-philosophy.md +52 -52
  6. package/.claude/vibe/rules/core/quick-start.md +120 -120
  7. package/.claude/vibe/rules/languages/dart-flutter.md +509 -509
  8. package/.claude/vibe/rules/languages/go.md +396 -396
  9. package/.claude/vibe/rules/languages/java-spring.md +586 -586
  10. package/.claude/vibe/rules/languages/kotlin-android.md +491 -491
  11. package/.claude/vibe/rules/languages/python-django.md +371 -371
  12. package/.claude/vibe/rules/languages/python-fastapi.md +386 -386
  13. package/.claude/vibe/rules/languages/rust.md +425 -425
  14. package/.claude/vibe/rules/languages/swift-ios.md +516 -516
  15. package/.claude/vibe/rules/languages/typescript-nextjs.md +441 -441
  16. package/.claude/vibe/rules/languages/typescript-node.md +375 -375
  17. package/.claude/vibe/rules/languages/typescript-nuxt.md +521 -521
  18. package/.claude/vibe/rules/languages/typescript-react-native.md +446 -446
  19. package/.claude/vibe/rules/languages/typescript-react.md +525 -525
  20. package/.claude/vibe/rules/languages/typescript-vue.md +353 -353
  21. package/.claude/vibe/rules/quality/bdd-contract-testing.md +388 -388
  22. package/.claude/vibe/rules/quality/checklist.md +276 -276
  23. package/.claude/vibe/rules/quality/testing-strategy.md +437 -437
  24. package/.claude/vibe/rules/standards/anti-patterns.md +369 -369
  25. package/.claude/vibe/rules/standards/code-structure.md +291 -291
  26. package/.claude/vibe/rules/standards/complexity-metrics.md +312 -312
  27. package/.claude/vibe/rules/standards/naming-conventions.md +198 -198
  28. package/.claude/vibe/setup.sh +31 -31
  29. package/.claude/vibe/templates/constitution-template.md +184 -184
  30. package/.claude/vibe/templates/contract-backend-template.md +517 -517
  31. package/.claude/vibe/templates/contract-frontend-template.md +594 -594
  32. package/.claude/vibe/templates/feature-template.md +96 -96
  33. package/.claude/vibe/templates/spec-template.md +199 -199
  34. package/CLAUDE.md +333 -333
  35. package/LICENSE +21 -21
  36. package/README.md +205 -205
  37. package/agents/compounder.md +261 -261
  38. package/agents/diagrammer.md +178 -178
  39. package/agents/e2e-tester.md +266 -266
  40. package/agents/explorer.md +48 -48
  41. package/agents/implementer.md +53 -53
  42. package/agents/research/best-practices-agent.md +139 -139
  43. package/agents/research/codebase-patterns-agent.md +147 -147
  44. package/agents/research/framework-docs-agent.md +181 -181
  45. package/agents/research/security-advisory-agent.md +167 -167
  46. package/agents/review/architecture-reviewer.md +107 -107
  47. package/agents/review/complexity-reviewer.md +116 -116
  48. package/agents/review/data-integrity-reviewer.md +88 -88
  49. package/agents/review/git-history-reviewer.md +103 -103
  50. package/agents/review/performance-reviewer.md +86 -86
  51. package/agents/review/python-reviewer.md +152 -152
  52. package/agents/review/rails-reviewer.md +139 -139
  53. package/agents/review/react-reviewer.md +144 -144
  54. package/agents/review/security-reviewer.md +80 -80
  55. package/agents/review/simplicity-reviewer.md +140 -140
  56. package/agents/review/test-coverage-reviewer.md +116 -116
  57. package/agents/review/typescript-reviewer.md +127 -127
  58. package/agents/searcher.md +54 -54
  59. package/agents/simplifier.md +119 -119
  60. package/agents/tester.md +49 -49
  61. package/agents/ui-previewer.md +137 -137
  62. package/commands/vibe.analyze.md +260 -260
  63. package/commands/vibe.reason.md +223 -223
  64. package/commands/vibe.review.md +213 -213
  65. package/commands/vibe.run.md +935 -935
  66. package/commands/vibe.spec.md +442 -442
  67. package/commands/vibe.utils.md +101 -101
  68. package/commands/vibe.verify.md +282 -282
  69. package/dist/cli/collaborator.js +52 -52
  70. package/dist/cli/detect.js +32 -32
  71. package/dist/cli/index.js +0 -0
  72. package/dist/cli/llm.js +144 -144
  73. package/hooks/hooks.json +195 -195
  74. package/package.json +87 -87
  75. package/skills/context7-usage.md +82 -82
  76. package/skills/git-worktree.md +181 -181
  77. package/skills/multi-llm-orchestration.md +97 -97
  78. package/skills/parallel-research.md +77 -77
  79. package/skills/priority-todos.md +239 -239
  80. package/skills/tool-fallback.md +126 -126
  81. package/skills/vibe-capabilities.md +127 -127
@@ -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
+ ```