@wooojin/forgen 0.4.8 → 0.4.9

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 (122) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/assets/dev-guide/be/README.md +226 -0
  3. package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
  4. package/assets/dev-guide/be/principles/common.md +433 -0
  5. package/assets/dev-guide/be/principles/go.md +469 -0
  6. package/assets/dev-guide/be/principles/node.md +388 -0
  7. package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
  8. package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
  9. package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
  10. package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
  11. package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
  12. package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
  13. package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
  14. package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
  15. package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
  16. package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
  17. package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
  18. package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
  19. package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
  20. package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
  21. package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
  22. package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
  23. package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
  24. package/assets/dev-guide/fe/README.md +197 -0
  25. package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
  26. package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
  27. package/assets/dev-guide/fe/principles/common.md +160 -0
  28. package/assets/dev-guide/fe/principles/react.md +183 -0
  29. package/assets/dev-guide/fe/principles/vue.md +196 -0
  30. package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
  31. package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
  32. package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
  33. package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
  34. package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
  35. package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
  36. package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
  37. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
  38. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
  39. package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
  40. package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
  41. package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
  42. package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
  43. package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
  44. package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
  45. package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
  46. package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
  47. package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
  48. package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
  49. package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
  50. package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
  51. package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
  52. package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
  53. package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
  54. package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
  55. package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
  56. package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
  57. package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
  58. package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
  59. package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
  60. package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
  61. package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
  62. package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
  63. package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
  64. package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
  65. package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
  66. package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
  67. package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
  68. package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
  69. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
  70. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
  71. package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
  72. package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
  73. package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
  74. package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
  75. package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
  76. package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
  77. package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
  78. package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
  79. package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
  80. package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
  81. package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
  82. package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
  83. package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
  84. package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
  85. package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
  86. package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
  87. package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
  88. package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
  89. package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
  90. package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
  91. package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
  92. package/dist/cli.js +42 -0
  93. package/dist/core/dashboard-cli.d.ts +12 -0
  94. package/dist/core/dashboard-cli.js +226 -0
  95. package/dist/core/dev-guide-injector.d.ts +26 -0
  96. package/dist/core/dev-guide-injector.js +137 -0
  97. package/dist/core/init.js +53 -0
  98. package/dist/core/lifecycle-classifier.d.ts +23 -0
  99. package/dist/core/lifecycle-classifier.js +104 -0
  100. package/dist/core/observability-backfill.d.ts +31 -0
  101. package/dist/core/observability-backfill.js +178 -0
  102. package/dist/core/observability-store.d.ts +58 -0
  103. package/dist/core/observability-store.js +195 -0
  104. package/dist/core/session-store.js +4 -0
  105. package/dist/core/spawn.d.ts +17 -0
  106. package/dist/core/spawn.js +179 -2
  107. package/dist/core/statusline-cli.js +34 -1
  108. package/dist/engine/compound-extractor.js +39 -0
  109. package/dist/engine/compound-loop.js +6 -0
  110. package/dist/engine/compound-retire.d.ts +20 -0
  111. package/dist/engine/compound-retire.js +85 -0
  112. package/dist/hooks/context-guard.js +25 -1
  113. package/dist/hooks/post-tool-use.js +48 -0
  114. package/dist/hooks/solution-injector.js +93 -0
  115. package/dist/host/install-claude.d.ts +6 -2
  116. package/dist/host/install-claude.js +74 -2
  117. package/dist/host/install-codex.d.ts +4 -0
  118. package/dist/host/install-codex.js +71 -0
  119. package/dist/host/install-orchestrator.js +1 -0
  120. package/package.json +6 -6
  121. package/plugin.json +1 -1
  122. package/scripts/postinstall.js +134 -0
@@ -0,0 +1,194 @@
1
+ ---
2
+ title: useOptimistic
3
+ source: https://react.dev/reference/react/useOptimistic
4
+ fetched: 2026-05-18
5
+ category: api
6
+ react_version: 19
7
+ ---
8
+
9
+ ## 개요
10
+
11
+ 비동기 Action이 진행되는 동안 UI를 낙관적으로 즉시 업데이트할 수 있는 Hook.
12
+
13
+ ---
14
+
15
+ ## 시그니처
16
+
17
+ ```js
18
+ const [optimisticState, setOptimistic] = useOptimistic(value, reducer?);
19
+ ```
20
+
21
+ ### Parameters
22
+ - `value`: 진행 중인 Action이 없을 때 반환할 값
23
+ - `reducer(currentState, action)` (optional): 낙관적 state 계산 순수 함수
24
+
25
+ ### Returns
26
+ 1. **`optimisticState`**: Action 없으면 `value`, Action 중이면 `reducer` 반환값
27
+ 2. **`set` 함수**: Action 내부에서 낙관적 state를 업데이트
28
+
29
+ ### set 함수 제약
30
+ - **반드시 `startTransition` 또는 Action prop 내에서 호출**
31
+ - 외부에서 호출 시 경고 + 즉시 원래 값으로 롤백
32
+
33
+ ---
34
+
35
+ ## 동작 흐름
36
+
37
+ ```js
38
+ const [value, setValue] = useState('a');
39
+ const [optimistic, setOptimistic] = useOptimistic(value);
40
+
41
+ startTransition(async () => {
42
+ setOptimistic('b'); // 1. 즉시 'b' 표시
43
+ const newValue = await saveChanges('b'); // 2. 서버 요청
44
+ setValue(newValue); // 3. 실제 state 업데이트 → 낙관적 state 수렴
45
+ });
46
+ ```
47
+
48
+ > Action 실패 시 자동으로 원래 `value`로 롤백됨.
49
+
50
+ ---
51
+
52
+ ## 사용 패턴
53
+
54
+ ### Form Action과 함께 낙관적 업데이트
55
+
56
+ ```js
57
+ import { useOptimistic, startTransition } from 'react';
58
+ import { updateName } from './actions.js';
59
+
60
+ export default function EditName({ name, action }) {
61
+ const [optimisticName, setOptimisticName] = useOptimistic(name);
62
+
63
+ async function submitAction(formData) {
64
+ const newName = formData.get('name');
65
+ setOptimisticName(newName); // Action prop 내부이므로 startTransition 불필요
66
+
67
+ const updatedName = await updateName(newName);
68
+ startTransition(() => {
69
+ action(updatedName);
70
+ });
71
+ }
72
+
73
+ return (
74
+ <form action={submitAction}>
75
+ <p>Your name is: {optimisticName}</p>
76
+ <input type="text" name="name" disabled={name !== optimisticName} />
77
+ </form>
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### Reducer로 복잡한 낙관적 업데이트
83
+
84
+ ```js
85
+ const [optimisticState, updateOptimistic] = useOptimistic(
86
+ { isFollowing: user.isFollowing, followerCount: user.followerCount },
87
+ (current, isFollowing) => ({
88
+ isFollowing,
89
+ followerCount: current.followerCount + (isFollowing ? 1 : -1),
90
+ })
91
+ );
92
+
93
+ function handleClick() {
94
+ const newFollowState = !optimisticState.isFollowing;
95
+ startTransition(async () => {
96
+ updateOptimistic(newFollowState);
97
+ await followAction(newFollowState);
98
+ });
99
+ }
100
+ ```
101
+
102
+ ### 목록에 낙관적 항목 추가
103
+
104
+ ```js
105
+ const [optimisticTodos, addOptimisticTodo] = useOptimistic(
106
+ todos,
107
+ (currentTodos, newTodo) => [
108
+ ...currentTodos,
109
+ { id: newTodo.id, text: newTodo.text, pending: true },
110
+ ]
111
+ );
112
+
113
+ function handleAddTodo(text) {
114
+ const newTodo = { id: crypto.randomUUID(), text };
115
+ startTransition(async () => {
116
+ addOptimisticTodo(newTodo);
117
+ await addTodoAction(newTodo);
118
+ });
119
+ }
120
+ ```
121
+
122
+ ### 에러 시 롤백 패턴
123
+
124
+ ```js
125
+ const [error, setError] = useState(null);
126
+ const [optimisticItems, removeItem] = useOptimistic(
127
+ items,
128
+ (currentItems, idToRemove) =>
129
+ currentItems.map(item =>
130
+ item.id === idToRemove ? { ...item, deleting: true } : item
131
+ )
132
+ );
133
+
134
+ function handleDelete(id) {
135
+ setError(null);
136
+ startTransition(async () => {
137
+ removeItem(id);
138
+ try {
139
+ await deleteAction(id);
140
+ } catch (e) {
141
+ setError(e.message); // 실패 시 UI 자동 롤백
142
+ }
143
+ });
144
+ }
145
+ ```
146
+
147
+ ### 여러 독립 낙관적 state
148
+
149
+ ```js
150
+ function MyComponent({ age, name, todos }) {
151
+ const [optimisticAge, setOptimisticAge] = useOptimistic(age);
152
+ const [optimisticName, setOptimisticName] = useOptimistic(name);
153
+ const [optimisticTodos, setOptimisticTodos] = useOptimistic(todos, reducer);
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 주요 특성
160
+
161
+ - **임시성**: Action 진행 중에만 낙관적 state 표시, 완료 후 `value`로 수렴
162
+ - **자동 롤백**: Action 실패 시 원래 `value` 렌더링
163
+ - **추가 렌더 없음**: Transition 완료 시 낙관적/실제 state가 단일 렌더에서 수렴
164
+ - Reducer 사용 시 변경 중인 base state에도 올바르게 적용됨
165
+
166
+ ---
167
+
168
+ ## Troubleshooting
169
+
170
+ ```js
171
+ // ❌ Transition 외부에서 호출
172
+ function handleClick() {
173
+ setOptimistic(newValue);
174
+ }
175
+
176
+ // ✅ startTransition 내부
177
+ function handleClick() {
178
+ startTransition(async () => {
179
+ setOptimistic(newValue);
180
+ });
181
+ }
182
+ ```
183
+
184
+ **pending 여부 확인 3가지 방법:**
185
+ ```js
186
+ // 1. 값 비교
187
+ const isPending = optimisticValue !== value;
188
+
189
+ // 2. useTransition
190
+ const [isPending, startTransition] = useTransition();
191
+
192
+ // 3. reducer에 pending 플래그 포함
193
+ (state, newItem) => [...state, { ...newItem, isPending: true }]
194
+ ```
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: Toss Frontend Fundamentals — 챕터 인덱스
3
+ fetched: 2026-05-18
4
+ source: https://github.com/toss/frontend-fundamentals + https://frontend-fundamentals.com/code-quality/
5
+ ---
6
+
7
+ # Toss Frontend Fundamentals — 챕터 인덱스
8
+
9
+ 수집 일자: 2026-05-18
10
+ 출처: https://github.com/toss/frontend-fundamentals / https://frontend-fundamentals.com/code-quality/
11
+
12
+ ## 개요
13
+
14
+ | 파일 | 원칙 | 한 줄 요약 |
15
+ |------|------|-----------|
16
+ | overview-4-principles.md | 전체 | 4대 원칙(가독성·예측성·응집도·결합도) 정의 및 트레이드오프 |
17
+
18
+ ## 가독성 (Readability)
19
+
20
+ | 파일 | 한 줄 요약 | 핵심 안티패턴 |
21
+ |------|-----------|--------------|
22
+ | readability-submit-button.md | 같이 실행되지 않는 코드 분리하기 | 권한별 분기가 교차된 단일 컴포넌트 → 권한별 컴포넌트 분리 |
23
+ | readability-login-start-page.md | 구현 상세 추상화하기 | 로그인 체크 로직 노출 → AuthGuard HOC/Wrapper로 추상화 |
24
+ | readability-use-page-state.md | 로직 종류에 따라 합쳐진 함수 쪼개기 | 페이지 전체 쿼리파람 관리 훅 → 파라미터별 독립 훅 |
25
+ | readability-condition-name.md | 복잡한 조건에 이름 붙이기 | 중첩 filter/some 익명 → `isSameCategory`, `isPriceInRange` 변수 추출 |
26
+ | readability-magic-number.md | 매직 넘버에 이름 붙이기 | `delay(300)` → `const ANIMATION_DELAY_MS = 300` |
27
+ | readability-user-policy.md | 시점 이동 줄이기 | POLICY_SET → getPolicyByRole → policy 3단계 참조 → 인라인 객체로 단순화 |
28
+ | readability-ternary-operator.md | 삼항 연산자 단순하게 하기 | 중첩 삼항 → IIFE + early return if문 |
29
+ | readability-comparison-order.md | 왼쪽에서 오른쪽으로 읽히게 하기 | `a >= b && a <= c` → `b <= a && a <= c` (수학 부등식 순서) |
30
+
31
+ ## 예측 가능성 (Predictability)
32
+
33
+ | 파일 | 한 줄 요약 | 핵심 안티패턴 |
34
+ |------|-----------|--------------|
35
+ | predictability-http.md | 이름 겹치지 않게 관리하기 | 라이브러리와 동일한 `http` 이름 → `httpService.getWithAuth`로 구분 |
36
+ | predictability-use-user.md | 같은 종류의 함수는 반환 타입 통일하기 | useUser는 Query 객체, useServerTime은 data만 반환 → 둘 다 Query 객체로 통일 |
37
+ | predictability-hidden-logic.md | 숨은 로직 드러내기 | fetchBalance 안에 logging 숨김 → fetch는 fetch만, logging은 호출 측으로 분리 |
38
+
39
+ ## 응집도 (Cohesion)
40
+
41
+ | 파일 | 한 줄 요약 | 핵심 안티패턴 |
42
+ |------|-----------|--------------|
43
+ | cohesion-code-directory.md | 함께 수정되는 파일을 같은 디렉토리에 두기 | 모듈 종류별 flat 구조 → 도메인별 계층 구조 |
44
+ | cohesion-magic-number.md | 매직 넘버 없애기 (응집도 관점) | `delay(300)` 애니메이션 변경 시 둘 중 하나만 수정 위험 → 상수로 결합 |
45
+ | cohesion-form-fields.md | 폼의 응집도 생각하기 | 필드 단위 응집(react-hook-form validate) vs 폼 전체 응집(zod schema) 선택 기준 |
46
+
47
+ ## 결합도 (Coupling)
48
+
49
+ | 파일 | 한 줄 요약 | 핵심 안티패턴 |
50
+ |------|-----------|--------------|
51
+ | coupling-use-page-state.md | 책임을 하나씩 관리하기 | 모든 쿼리파람 통합 훅 → 수정 영향범위 과대 → 파라미터별 분리 |
52
+ | coupling-use-bottom-sheet.md | 중복 코드 허용하기 | 유사해 보이는 바텀시트 로직 공통화 → 페이지별 분기 과잉 → 중복 허용 권장 |
53
+ | coupling-item-edit-modal.md | Props Drilling 지우기 | 3단계 prop 전달 → Composition 패턴(`children`) 또는 Context API |
54
+
55
+ ## 총계
56
+
57
+ - 총 파일 수: 15개 (INDEX.md 제외)
58
+ - 원칙별: 가독성 8, 예측가능성 3, 응집도 3, 결합도 3
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: 함께 수정되는 파일을 같은 디렉토리에 두기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/code-directory.html
4
+ fetched: 2026-05-18
5
+ principle: cohesion
6
+ ---
7
+
8
+ # 함께 수정되는 파일을 같은 디렉토리에 두기
9
+
10
+ 프로젝트에서 코드를 작성하다 보면 Hook, 컴포넌트, 유틸리티 함수 등을 여러 파일로 나누어서 관리하게 돼요. 이런 파일들을 쉽게 만들고, 찾고, 삭제할 수 있도록 올바른 디렉토리 구조를 갖추는 것이 중요해요.
11
+
12
+ 함께 수정되는 소스 파일을 하나의 디렉토리에 배치하면 코드의 의존 관계를 명확하게 드러낼 수 있어요.
13
+
14
+ ## 📝 코드 예시
15
+
16
+ 다음 코드는 프로젝트의 모든 파일을 모듈의 종류에 따라 분류한 디렉토리 구조예요.
17
+
18
+ ```text
19
+ └─ src
20
+ ├─ components
21
+ ├─ constants
22
+ ├─ containers
23
+ ├─ contexts
24
+ ├─ remotes
25
+ ├─ hooks
26
+ ├─ utils
27
+ └─ ...
28
+ ```
29
+
30
+ ## 👃 코드 냄새 맡아보기
31
+
32
+ ### 응집도
33
+
34
+ 파일을 이렇게 종류별로 나누면 어떤 코드가 어떤 코드를 참조하는지 쉽게 확인할 수 없어요. 코드 파일 사이의 의존 관계는 개발자가 스스로 코드를 분석하면서 챙겨야 해요.
35
+
36
+ 또한 더 이상 특정 컴포넌트나 Hook, 유틸리티 함수가 사용되지 않아서 삭제된다고 했을 때, 연관된 코드가 함께 삭제되지 못해서 사용되지 않는 코드가 남아있게 될 수도 있어요.
37
+
38
+ ## ✏️ 개선해보기
39
+
40
+ 다음은 함께 수정되는 코드 파일끼리 하나의 디렉토리를 이루도록 구조를 개선한 예시예요.
41
+
42
+ ```text
43
+ └─ src
44
+ │ // 전체 프로젝트에서 사용되는 코드
45
+ ├─ components
46
+ ├─ containers
47
+ ├─ hooks
48
+ ├─ utils
49
+ ├─ ...
50
+
51
+ └─ domains
52
+ │ // Domain1에서만 사용되는 코드
53
+ ├─ Domain1
54
+ │ ├─ components
55
+ │ ├─ containers
56
+ │ ├─ hooks
57
+ │ ├─ utils
58
+ │ └─ ...
59
+
60
+ │ // Domain2에서만 사용되는 코드
61
+ └─ Domain2
62
+ ├─ components
63
+ ├─ containers
64
+ ├─ hooks
65
+ ├─ utils
66
+ └─ ...
67
+ ```
68
+
69
+ 함께 수정되는 코드 파일을 하나의 디렉토리 아래에 둔다면, 코드 사이의 의존 관계를 파악하기 쉬워요.
70
+
71
+ 예를 들어, 다음과 같이 한 도메인(`Domain1`)의 하위 코드에서 다른 도메인(`Domain2`)의 소스 코드를 참조한다고 생각해 볼게요.
72
+
73
+ ```typescript
74
+ import { useFoo } from "../../../Domain2/hooks/useFoo";
75
+ ```
76
+
77
+ 이런 import 문을 만난다면 잘못된 파일을 참조하고 있다는 것을 쉽게 인지할 수 있게 돼요.
78
+
79
+ 또한, 특정 기능과 관련된 코드를 삭제할 때 한 디렉토리 전체를 삭제하면 깔끔하게 모든 코드가 삭제되므로, 프로젝트 내부에 더 이상 사용되지 않는 코드가 없도록 할 수 있어요.
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: 폼의 응집도 생각하기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/form-fields.html
4
+ fetched: 2026-05-18
5
+ principle: cohesion
6
+ ---
7
+
8
+ # 폼의 응집도 생각하기
9
+
10
+ 프론트엔드 개발을 하다 보면 Form으로 사용자에게 값을 입력받아야 하는 경우가 많아요.
11
+ Form을 관리할 때는 2가지의 방법으로 응집도를 관리해서, 함께 수정되어야 할 코드가 함께 수정되도록 할 수 있어요.
12
+
13
+ ## 필드 단위 응집도
14
+
15
+ 필드 단위 응집은 개별 입력 요소를 독립적으로 관리하는 방식이에요.
16
+ 각 필드가 고유의 검증 로직을 가지므로 변경이 필요한 범위가 줄어들어 특정 필드의 유지보수가 쉬워져요.
17
+
18
+ ```tsx
19
+ import { useForm } from "react-hook-form";
20
+
21
+ export function Form() {
22
+ const { register, formState: { errors }, handleSubmit } = useForm({
23
+ defaultValues: { name: "", email: "" }
24
+ });
25
+
26
+ return (
27
+ <form onSubmit={handleSubmit(console.log)}>
28
+ <div>
29
+ <input
30
+ {...register("name", {
31
+ validate: (value) =>
32
+ isEmptyStringOrNil(value) ? "이름을 입력해주세요." : ""
33
+ })}
34
+ placeholder="이름"
35
+ />
36
+ {errors.name && <p>{errors.name.message}</p>}
37
+ </div>
38
+
39
+ <div>
40
+ <input
41
+ {...register("email", {
42
+ validate: (value) => {
43
+ if (isEmptyStringOrNil(value)) return "이메일을 입력해주세요.";
44
+ if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value))
45
+ return "유효한 이메일 주소를 입력해주세요.";
46
+ return "";
47
+ }
48
+ })}
49
+ placeholder="이메일"
50
+ />
51
+ {errors.email && <p>{errors.email.message}</p>}
52
+ </div>
53
+
54
+ <button type="submit">제출</button>
55
+ </form>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ## 폼 전체 단위 응집도
61
+
62
+ 폼 전체 응집은 모든 필드의 검증 로직이 폼에 종속되는 방식이에요.
63
+
64
+ ```tsx
65
+ import * as z from "zod";
66
+ import { useForm } from "react-hook-form";
67
+ import { zodResolver } from "@hookform/resolvers/zod";
68
+
69
+ const schema = z.object({
70
+ name: z.string().min(1, "이름을 입력해주세요."),
71
+ email: z
72
+ .string()
73
+ .min(1, "이메일을 입력해주세요.")
74
+ .email("유효한 이메일 주소를 입력해주세요.")
75
+ });
76
+
77
+ export function Form() {
78
+ const { register, formState: { errors }, handleSubmit } = useForm({
79
+ defaultValues: { name: "", email: "" },
80
+ resolver: zodResolver(schema)
81
+ });
82
+
83
+ return (
84
+ <form onSubmit={handleSubmit(console.log)}>
85
+ <div>
86
+ <input {...register("name")} placeholder="이름" />
87
+ {errors.name && <p>{errors.name.message}</p>}
88
+ </div>
89
+ <div>
90
+ <input {...register("email")} placeholder="이메일" />
91
+ {errors.email && <p>{errors.email.message}</p>}
92
+ </div>
93
+ <button type="submit">제출</button>
94
+ </form>
95
+ );
96
+ }
97
+ ```
98
+
99
+ ## 필드 단위 vs. 폼 전체 단위 응집도
100
+
101
+ ### 필드 단위 응집도를 선택하면 좋을 때
102
+
103
+ - **독립적인 검증이 필요할 때**: 이메일 형식 검사, 아이디 중복 확인, 추천 코드 유효성 확인처럼 각 필드가 독립적이고 고유한 검증이 필요할 때
104
+ - **재사용이 필요할 때**: 필드와 검증 로직이 다른 폼에서도 동일하게 사용될 수 있는 경우
105
+
106
+ ### 폼 전체 단위 응집도를 선택하면 좋을 때
107
+
108
+ - **단일 기능을 나타낼 때**: 결제 정보나 배송 정보처럼 모든 필드가 하나의 비즈니스 로직을 이룰 때
109
+ - **단계별 입력이 필요할 때**: Wizard Form과 같이 스텝별로 동작하는 복잡한 폼
110
+ - **필드 간 의존성이 있을 때**: 비밀번호 확인이나 총액 계산처럼 필드 간 상호작용이 필요할 때
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: 매직 넘버 없애기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/magic-number-cohesion.html
4
+ fetched: 2026-05-18
5
+ principle: cohesion
6
+ ---
7
+
8
+ # 매직 넘버 없애기
9
+
10
+ **매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요.
11
+
12
+ ## 📝 코드 예시
13
+
14
+ 다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요.
15
+
16
+ ```typescript
17
+ async function onLikeClick() {
18
+ await postLike(url);
19
+ await delay(300);
20
+ await refetchPostLike();
21
+ }
22
+ ```
23
+
24
+ ## 👃 코드 냄새 맡아보기
25
+
26
+ ### 응집도
27
+
28
+ `300`이라고 하는 숫자를 애니메이션 완료를 기다리려고 사용했다면, 재생하는 애니메이션을 바꿨을 때 조용히 서비스가 깨질 수 있는 위험성이 있어요.
29
+ 충분한 시간동안 애니메이션을 기다리지 않고 바로 다음 로직이 시작될 수도 있죠.
30
+
31
+ 같이 수정되어야 할 코드 중 한쪽만 수정된다는 점에서, 응집도가 낮은 코드라고도 할 수 있어요.
32
+
33
+ ## ✏️ 개선해보기
34
+
35
+ 숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요.
36
+
37
+ ```typescript
38
+ const ANIMATION_DELAY_MS = 300;
39
+
40
+ async function onLikeClick() {
41
+ await postLike(url);
42
+ await delay(ANIMATION_DELAY_MS);
43
+ await refetchPostLike();
44
+ }
45
+ ```
46
+
47
+ 이렇게 하면 애니메이션 지속 시간이 변경될 때, 상수 하나만 수정하면 관련된 모든 곳이 함께 수정돼요.
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Props Drilling 지우기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/item-edit-modal.html
4
+ fetched: 2026-05-18
5
+ principle: coupling
6
+ ---
7
+
8
+ # Props Drilling 지우기
9
+
10
+ Props Drilling은 부모 컴포넌트와 자식 컴포넌트 사이에 결합도가 생겼다는 것을 나타내는 명확한 표시예요.
11
+ 만약에 Drilling되는 `name` prop의 이름이 `firstName`으로 변경되면, 해당 prop을 참조하는 모든 컴포넌트를 수정해야 해요.
12
+
13
+ ## 📝 코드 예시
14
+
15
+ 다음 코드는 사용자가 `item`을 선택할 때 사용하는 `<ItemEditModal />` 컴포넌트예요.
16
+
17
+ ```tsx
18
+ function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) {
19
+ const [keyword, setKeyword] = useState("");
20
+
21
+ return (
22
+ <Modal open={open} onClose={onClose}>
23
+ <ItemEditBody
24
+ items={items}
25
+ keyword={keyword}
26
+ onKeywordChange={setKeyword}
27
+ recommendedItems={recommendedItems}
28
+ onConfirm={onConfirm}
29
+ onClose={onClose}
30
+ />
31
+ </Modal>
32
+ );
33
+ }
34
+
35
+ function ItemEditBody({
36
+ keyword, onKeywordChange, items, recommendedItems, onConfirm, onClose
37
+ }) {
38
+ return (
39
+ <>
40
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
41
+ <Input value={keyword} onChange={(e) => onKeywordChange(e.target.value)} />
42
+ <Button onClick={onClose}>닫기</Button>
43
+ </div>
44
+ <ItemEditList
45
+ keyword={keyword}
46
+ items={items}
47
+ recommendedItems={recommendedItems}
48
+ onConfirm={onConfirm}
49
+ />
50
+ </>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ## 👃 코드 냄새 맡아보기
56
+
57
+ ### 결합도
58
+
59
+ 이 컴포넌트는 부모인 `ItemEditModal`과 자식인 `ItemEditBody`, `ItemEditList` 등이 동일한 값인 `recommendedItems`, `onConfirm`, `keyword` 등을 prop으로 공유하고 있어요.
60
+
61
+ Props Drilling이 발생하면, prop을 불필요하게 참조하는 컴포넌트의 숫자가 많아져요.
62
+ 그런데 prop이 변경되면 prop을 참조하는 모든 컴포넌트가 수정되어야 해요.
63
+
64
+ ## ✏️ 개선해보기
65
+
66
+ ### A. 조합(Composition) 패턴 활용
67
+
68
+ ```tsx
69
+ function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) {
70
+ const [keyword, setKeyword] = useState("");
71
+
72
+ return (
73
+ <Modal open={open} onClose={onClose}>
74
+ <ItemEditBody
75
+ keyword={keyword}
76
+ onKeywordChange={setKeyword}
77
+ onClose={onClose}
78
+ >
79
+ <ItemEditList
80
+ keyword={keyword}
81
+ items={items}
82
+ recommendedItems={recommendedItems}
83
+ onConfirm={onConfirm}
84
+ />
85
+ </ItemEditBody>
86
+ </Modal>
87
+ );
88
+ }
89
+
90
+ function ItemEditBody({ children, keyword, onKeywordChange, onClose }) {
91
+ return (
92
+ <>
93
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
94
+ <Input value={keyword} onChange={(e) => onKeywordChange(e.target.value)} />
95
+ <Button onClick={onClose}>닫기</Button>
96
+ </div>
97
+ {children}
98
+ </>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### B. ContextAPI 활용
104
+
105
+ ```tsx
106
+ function ItemEditModal({ open, onConfirm, onClose }) {
107
+ const [keyword, setKeyword] = useState("");
108
+
109
+ return (
110
+ <Modal open={open} onClose={onClose}>
111
+ <ItemEditBody keyword={keyword} onKeywordChange={setKeyword} onClose={onClose}>
112
+ <ItemEditList keyword={keyword} onConfirm={onConfirm} />
113
+ </ItemEditBody>
114
+ </Modal>
115
+ );
116
+ }
117
+
118
+ function ItemEditList({ keyword, onConfirm }) {
119
+ const { items, recommendedItems } = useItemEditModalContext();
120
+ // ...
121
+ }
122
+ ```
123
+
124
+ ContextAPI를 사용하면 매우 쉽게 Props Drilling을 해결할 수 있지만, Props Drilling이 되는 모든 값을 ContextAPI로 관리해야 하는 것은 아니에요. `children` prop을 이용해서 컴포넌트를 전달해 depth를 줄이는 것을 먼저 고려하세요.
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: 중복 코드 허용하기
3
+ source: https://frontend-fundamentals.com/code-quality/code/examples/use-bottom-sheet.html
4
+ fetched: 2026-05-18
5
+ principle: coupling
6
+ ---
7
+
8
+ # 중복 코드 허용하기
9
+
10
+ 개발자로서 여러 페이지나 컴포넌트에 걸친 중복 코드를 하나의 Hook이나 컴포넌트로 공통화하는 경우가 많아요.
11
+ 중복 코드를 하나의 컴포넌트나 Hook으로 공통화하면, 좋은 코드의 특징 중 하나인 응집도를 챙겨서, 함께 수정되어야 할 코드들을 한꺼번에 수정할 수 있어요.
12
+
13
+ 그렇지만, 불필요한 결합도가 생겨서, 공통 컴포넌트나 Hook을 수정함에 따라 영향을 받는 코드의 범위가 넓어져서, 오히려 수정이 어려워질 수도 있어요.
14
+
15
+ ## 📝 코드 예시
16
+
17
+ 아래와 같이 점검 정보를 인자로 받아서, 점검 중이라면 점검 바텀시트를 열고, 사용자가 알림 받기에 동의하면 이를 로깅하고, 현재 화면을 닫는 Hook을 살펴볼게요.
18
+
19
+ ```typescript
20
+ export const useOpenMaintenanceBottomSheet = () => {
21
+ const maintenanceBottomSheet = useMaintenanceBottomSheet();
22
+ const logger = useLogger();
23
+
24
+ return async (maintainingInfo: TelecomMaintenanceInfo) => {
25
+ logger.log("점검 바텀시트 열림");
26
+ const result = await maintenanceBottomSheet.open(maintainingInfo);
27
+ if (result) {
28
+ logger.log("점검 바텀시트 알림받기 클릭");
29
+ }
30
+ closeView();
31
+ };
32
+ };
33
+ ```
34
+
35
+ 이 코드는 여러 페이지에서 반복적으로 사용되었기에 공통 Hook으로 분리되었어요.
36
+
37
+ ## 👃 코드 냄새 맡아보기
38
+
39
+ ### 결합도
40
+
41
+ 이 Hook은 여러 페이지에서 반복적으로 보이는 로직이기에 공통화되었어요. 그렇지만 앞으로 생길 수 있는 다양한 코드 변경의 가능성을 생각해볼 수 있어요.
42
+
43
+ - 만약에 페이지마다 로깅하는 값이 달라진다면?
44
+ - 만약에 어떤 페이지에서는 점검 바텀시트를 닫더라도 화면을 닫을 필요가 없다면?
45
+ - 바텀시트에서 보여지는 텍스트나 이미지를 다르게 해야 한다면?
46
+
47
+ 위 Hook은 이런 코드 변경사항에 유연하게 대응하기 위해서 복잡하게 인자를 받아야 할 거예요.
48
+ 이 Hook의 구현을 수정할 때마다, 이 Hook을 쓰는 모든 페이지들이 잘 작동하는지 테스트도 해야 할 것이고요.
49
+
50
+ ## ✏️ 개선해보기
51
+
52
+ 다소 반복되어 보이는 코드일지 몰라도, 중복 코드를 허용하는 것이 좋은 방향일 수 있어요.
53
+
54
+ 함께 일하는 동료들과 적극적으로 소통하며 점검 바텀시트의 동작을 정확하게 이해해야 해요.
55
+ 페이지에서 로깅하는 값이 같고, 점검 바텀시트의 동작이 동일하고, 바텀시트의 모양이 동일하다면, 그리고 앞으로도 그럴 예정이라면, 공통화를 통해 코드의 응집도를 높일 수 있어요.
56
+
57
+ 그렇지만 페이지마다 동작이 달라질 여지가 있다면, 공통화 없이 중복 코드를 허용하는 것이 더 좋은 선택이에요.