@wooojin/forgen 0.4.7 → 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 (159) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/assets/dev-guide/be/README.md +226 -0
  4. package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
  5. package/assets/dev-guide/be/principles/common.md +433 -0
  6. package/assets/dev-guide/be/principles/go.md +469 -0
  7. package/assets/dev-guide/be/principles/node.md +388 -0
  8. package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
  9. package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
  10. package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
  11. package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
  12. package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
  13. package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
  14. package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
  15. package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
  16. package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
  17. package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
  18. package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
  19. package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
  20. package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
  21. package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
  22. package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
  23. package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
  24. package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
  25. package/assets/dev-guide/fe/README.md +197 -0
  26. package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
  27. package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
  28. package/assets/dev-guide/fe/principles/common.md +160 -0
  29. package/assets/dev-guide/fe/principles/react.md +183 -0
  30. package/assets/dev-guide/fe/principles/vue.md +196 -0
  31. package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
  32. package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
  33. package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
  34. package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
  35. package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
  36. package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
  37. package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
  38. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
  39. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
  40. package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
  41. package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
  42. package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
  43. package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
  44. package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
  45. package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
  46. package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
  47. package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
  48. package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
  49. package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
  50. package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
  51. package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
  52. package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
  53. package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
  54. package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
  55. package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
  56. package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
  57. package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
  58. package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
  59. package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
  60. package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
  61. package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
  62. package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
  63. package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
  64. package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
  65. package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
  66. package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
  67. package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
  68. package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
  69. package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
  70. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
  71. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
  72. package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
  73. package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
  74. package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
  75. package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
  76. package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
  77. package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
  78. package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
  79. package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
  80. package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
  81. package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
  82. package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
  83. package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
  84. package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
  85. package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
  86. package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
  87. package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
  88. package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
  89. package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
  90. package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
  91. package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
  92. package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
  93. package/dist/checks/self-score-deflation.js +6 -4
  94. package/dist/cli.js +47 -2
  95. package/dist/core/auto-compound-runner.js +6 -2
  96. package/dist/core/dashboard-cli.d.ts +12 -0
  97. package/dist/core/dashboard-cli.js +226 -0
  98. package/dist/core/dashboard.js +2 -2
  99. package/dist/core/dev-guide-injector.d.ts +26 -0
  100. package/dist/core/dev-guide-injector.js +137 -0
  101. package/dist/core/doctor.d.ts +10 -0
  102. package/dist/core/doctor.js +49 -8
  103. package/dist/core/harness.js +8 -2
  104. package/dist/core/init.js +53 -0
  105. package/dist/core/inspect-cli.js +4 -4
  106. package/dist/core/lifecycle-classifier.d.ts +23 -0
  107. package/dist/core/lifecycle-classifier.js +104 -0
  108. package/dist/core/migrate-evidence-host.js +1 -1
  109. package/dist/core/notify.js +7 -0
  110. package/dist/core/observability-backfill.d.ts +31 -0
  111. package/dist/core/observability-backfill.js +178 -0
  112. package/dist/core/observability-store.d.ts +58 -0
  113. package/dist/core/observability-store.js +195 -0
  114. package/dist/core/paths.d.ts +16 -2
  115. package/dist/core/paths.js +16 -2
  116. package/dist/core/session-store.d.ts +12 -1
  117. package/dist/core/session-store.js +77 -1
  118. package/dist/core/spawn.d.ts +17 -0
  119. package/dist/core/spawn.js +191 -8
  120. package/dist/core/statusline-cli.js +34 -1
  121. package/dist/core/v1-bootstrap.d.ts +7 -0
  122. package/dist/core/v1-bootstrap.js +28 -6
  123. package/dist/engine/compound-extractor.js +40 -1
  124. package/dist/engine/compound-loop.js +6 -0
  125. package/dist/engine/compound-retire.d.ts +20 -0
  126. package/dist/engine/compound-retire.js +85 -0
  127. package/dist/engine/learn-cli.js +2 -2
  128. package/dist/engine/lifecycle/bypass-detector.js +3 -2
  129. package/dist/engine/lifecycle/meta-reclassifier.js +1 -1
  130. package/dist/engine/lifecycle/signals.js +2 -2
  131. package/dist/engine/lifecycle/trigger-t1-correction.js +1 -1
  132. package/dist/engine/solution-candidate.js +1 -1
  133. package/dist/engine/solution-outcomes.js +1 -1
  134. package/dist/engine/solution-quarantine.js +1 -1
  135. package/dist/engine/solution-weakness.js +8 -2
  136. package/dist/forge/cli.js +1 -1
  137. package/dist/hooks/context-guard.js +25 -1
  138. package/dist/hooks/keyword-detector.js +1 -1
  139. package/dist/hooks/post-tool-use.js +48 -0
  140. package/dist/hooks/secret-filter.js +2 -2
  141. package/dist/hooks/shared/hook-response.js +1 -1
  142. package/dist/hooks/shared/hook-timing.js +3 -3
  143. package/dist/hooks/solution-injector.js +94 -1
  144. package/dist/hooks/stop-guard.js +3 -3
  145. package/dist/host/install-claude.d.ts +6 -2
  146. package/dist/host/install-claude.js +74 -2
  147. package/dist/host/install-codex.d.ts +4 -0
  148. package/dist/host/install-codex.js +72 -1
  149. package/dist/host/install-orchestrator.js +1 -0
  150. package/dist/mcp/tools.js +1 -1
  151. package/dist/preset/facet-catalog.js +2 -2
  152. package/dist/renderer/rule-renderer.js +7 -7
  153. package/dist/store/compound-usage-store.js +1 -1
  154. package/dist/store/implicit-feedback-store.js +2 -2
  155. package/dist/store/profile-store.d.ts +11 -0
  156. package/dist/store/profile-store.js +23 -0
  157. package/package.json +6 -6
  158. package/plugin.json +1 -1
  159. package/scripts/postinstall.js +134 -0
@@ -0,0 +1,469 @@
1
+ ---
2
+ title: Go 원칙
3
+ version: 2026-05-18
4
+ sources:
5
+ - sources/go-runtime/
6
+ ---
7
+
8
+ # Go 원칙
9
+
10
+ > [공통 원칙](./common.md)을 먼저 따르고, 아래는 Go 특화.
11
+
12
+ ## G0. 의사결정 우선순위
13
+
14
+ 1. **에러는 값** — panic은 진짜 예외(복구 불가)에만
15
+ 2. **context.Context 전파** — cancellation/deadline은 모든 I/O에 전달
16
+ 3. **goroutine 생명주기 명확화** — 시작한 곳이 종료 책임 보유
17
+ 4. **interface는 작게** — consumer 측 정의, 1~3 메서드
18
+ 5. **lint 자동화** — golangci-lint + go vet + staticcheck CI 필수
19
+
20
+ ---
21
+
22
+ ## G1. 에러는 값으로 반환
23
+
24
+ 근거: `sources/go-runtime/` (Effective Go, Rob Pike "Errors are values")
25
+
26
+ **panic은 "진짜 프로그래밍 오류" 또는 "복구 불가 상황"에만. 일반 에러는 반환값.**
27
+
28
+ ### G1.1 에러 반환 패턴
29
+
30
+ ```go
31
+ // 에러를 항상 마지막 반환값으로
32
+ func fetchUser(ctx context.Context, id string) (*User, error) {
33
+ row := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = $1", id)
34
+ var user User
35
+ if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
36
+ if errors.Is(err, sql.ErrNoRows) {
37
+ return nil, ErrUserNotFound // sentinel error
38
+ }
39
+ return nil, fmt.Errorf("fetchUser %s: %w", id, err) // 래핑
40
+ }
41
+ return &user, nil
42
+ }
43
+ ```
44
+
45
+ ### G1.2 에러 래핑과 검사
46
+
47
+ ```go
48
+ // 에러 래핑: %w 사용 (Go 1.13+)
49
+ return fmt.Errorf("processPayment: %w", ErrInsufficientFunds)
50
+
51
+ // 에러 검사: errors.Is (sentinel), errors.As (타입)
52
+ if errors.Is(err, ErrUserNotFound) {
53
+ return http.StatusNotFound, nil
54
+ }
55
+ var validationErr *ValidationError
56
+ if errors.As(err, &validationErr) {
57
+ return http.StatusBadRequest, validationErr.Fields
58
+ }
59
+ ```
60
+
61
+ ### G1.3 Sentinel Error 정의
62
+
63
+ ```go
64
+ // 패키지 레벨 sentinel errors
65
+ var (
66
+ ErrUserNotFound = errors.New("user not found")
67
+ ErrInsufficientFunds = errors.New("insufficient funds")
68
+ ErrDuplicateEmail = errors.New("duplicate email")
69
+ )
70
+
71
+ // 필드 정보가 필요한 구조체 에러
72
+ type ValidationError struct {
73
+ Fields map[string]string
74
+ }
75
+ func (e *ValidationError) Error() string {
76
+ return fmt.Sprintf("validation failed: %v", e.Fields)
77
+ }
78
+ ```
79
+
80
+ ### G1.4 panic 허용 기준
81
+
82
+ ```go
83
+ // OK: 프로그래밍 오류 — nil 포인터, 인덱스 초과 등 (런타임 패닉)
84
+ // OK: 초기화 실패 — 앱이 시작될 수 없는 상태
85
+ func mustParseTemplate(s string) *template.Template {
86
+ t, err := template.New("").Parse(s)
87
+ if err != nil {
88
+ panic(fmt.Sprintf("failed to parse template: %v", err)) // 컴파일 타임 알아야 할 오류
89
+ }
90
+ return t
91
+ }
92
+
93
+ // NOT OK: 일반 런타임 에러 (DB 오류, 네트워크 오류, 검증 실패)
94
+ ```
95
+
96
+ ---
97
+
98
+ ## G2. context.Context 전파
99
+
100
+ **모든 I/O 함수의 첫 번째 인자는 context.Context. cancellation/deadline이 반드시 전파되어야 한다.**
101
+
102
+ ### G2.1 함수 시그니처 규칙
103
+
104
+ ```go
105
+ // WRONG: context 없는 DB 호출
106
+ func (r *UserRepo) FindByID(id string) (*User, error) {
107
+ return r.db.QueryRow("SELECT ...", id).Scan(...)
108
+ }
109
+
110
+ // RIGHT: context 전파
111
+ func (r *UserRepo) FindByID(ctx context.Context, id string) (*User, error) {
112
+ return r.db.QueryRowContext(ctx, "SELECT ...", id).Scan(...)
113
+ }
114
+ ```
115
+
116
+ ### G2.2 context 값 사용 기준
117
+
118
+ ```go
119
+ // context.Value: 요청 범위 메타데이터만 (request ID, auth user, traceID)
120
+ // 비즈니스 로직 파라미터를 context에 넣지 마라
121
+
122
+ // WRONG
123
+ ctx = context.WithValue(ctx, "userID", userID)
124
+
125
+ // RIGHT: 명시적 파라미터
126
+ func processOrder(ctx context.Context, userID string, items []Item) error
127
+ ```
128
+
129
+ ### G2.3 context 취소 처리
130
+
131
+ ```go
132
+ func longOperation(ctx context.Context) error {
133
+ for {
134
+ select {
135
+ case <-ctx.Done():
136
+ return ctx.Err() // context.Canceled 또는 context.DeadlineExceeded
137
+ default:
138
+ // 작업 계속
139
+ }
140
+ // 또는 I/O 함수에 ctx 전달 시 자동 처리
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### G2.4 Timeout 설정
146
+
147
+ ```go
148
+ // HTTP 핸들러: 요청 timeout
149
+ ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
150
+ defer cancel() // 반드시 defer cancel() — goroutine/timer 누수 방지
151
+
152
+ result, err := svc.Process(ctx, input)
153
+ ```
154
+
155
+ ---
156
+
157
+ ## G3. Goroutine 생명주기
158
+
159
+ **goroutine을 시작한 쪽이 종료 책임을 진다. goroutine leak은 메모리 누수와 동일하다.**
160
+
161
+ ### G3.1 goroutine leak 패턴과 픽스
162
+
163
+ ```go
164
+ // WRONG: 종료 조건 없는 goroutine
165
+ func start() {
166
+ go func() {
167
+ for {
168
+ processMessage() // 영원히 실행
169
+ }
170
+ }()
171
+ }
172
+
173
+ // RIGHT: context 기반 종료
174
+ func start(ctx context.Context) {
175
+ go func() {
176
+ for {
177
+ select {
178
+ case <-ctx.Done():
179
+ return
180
+ default:
181
+ processMessage(ctx)
182
+ }
183
+ }
184
+ }()
185
+ }
186
+ ```
187
+
188
+ ### G3.2 WaitGroup으로 완료 대기
189
+
190
+ ```go
191
+ func processAll(ctx context.Context, items []Item) error {
192
+ var wg sync.WaitGroup
193
+ errCh := make(chan error, len(items)) // 버퍼드 채널 (모든 에러 수집)
194
+
195
+ for _, item := range items {
196
+ wg.Add(1)
197
+ go func(item Item) {
198
+ defer wg.Done()
199
+ if err := process(ctx, item); err != nil {
200
+ errCh <- err
201
+ }
202
+ }(item) // 루프 변수 캡처 — Go 1.22부터 자동이지만 명시적 인자가 명확
203
+ }
204
+
205
+ wg.Wait()
206
+ close(errCh)
207
+
208
+ for err := range errCh {
209
+ return err // 첫 번째 에러 반환 (또는 multierr.Combine)
210
+ }
211
+ return nil
212
+ }
213
+ ```
214
+
215
+ ### G3.3 errgroup 패턴
216
+
217
+ ```go
218
+ import "golang.org/x/sync/errgroup"
219
+
220
+ func fanOut(ctx context.Context) error {
221
+ g, ctx := errgroup.WithContext(ctx) // 한 goroutine 실패 시 나머지 ctx 취소
222
+
223
+ g.Go(func() error { return fetchUsers(ctx) })
224
+ g.Go(func() error { return fetchOrders(ctx) })
225
+ g.Go(func() error { return fetchInventory(ctx) })
226
+
227
+ return g.Wait() // 모든 goroutine 완료 대기, 첫 번째 non-nil 에러 반환
228
+ }
229
+ ```
230
+
231
+ ---
232
+
233
+ ## G4. Channel 단일 책임
234
+
235
+ **channel은 한 가지 목적으로만. close는 항상 sender가.**
236
+
237
+ ### G4.1 channel 패턴
238
+
239
+ ```go
240
+ // 생산자-소비자: sender가 close
241
+ func producer(ctx context.Context) <-chan Item {
242
+ ch := make(chan Item)
243
+ go func() {
244
+ defer close(ch) // sender가 close
245
+ for _, item := range items {
246
+ select {
247
+ case ch <- item:
248
+ case <-ctx.Done():
249
+ return
250
+ }
251
+ }
252
+ }()
253
+ return ch
254
+ }
255
+
256
+ func consumer(ctx context.Context, ch <-chan Item) {
257
+ for item := range ch { // close 시 자동 종료
258
+ process(ctx, item)
259
+ }
260
+ }
261
+ ```
262
+
263
+ ### G4.2 channel vs sync.Mutex 선택 기준
264
+
265
+ ```
266
+ goroutine 간 데이터 전달 → channel
267
+ 공유 상태 보호 (캐시, 카운터) → sync.Mutex / sync.RWMutex
268
+ 단발성 신호 (완료 알림) → chan struct{} (또는 context)
269
+ ```
270
+
271
+ ---
272
+
273
+ ## G5. Interface는 작게
274
+
275
+ **interface는 큰 것을 정의하지 마라. consumer 측에서, 1~3 메서드로.**
276
+
277
+ ### G5.1 인터페이스 크기 원칙
278
+
279
+ ```go
280
+ // WRONG: 모든 것을 포함한 큰 인터페이스
281
+ type UserRepository interface {
282
+ Create(ctx context.Context, user *User) error
283
+ Update(ctx context.Context, user *User) error
284
+ Delete(ctx context.Context, id string) error
285
+ FindByID(ctx context.Context, id string) (*User, error)
286
+ FindByEmail(ctx context.Context, email string) (*User, error)
287
+ List(ctx context.Context, filter UserFilter) ([]*User, error)
288
+ Count(ctx context.Context, filter UserFilter) (int, error)
289
+ }
290
+
291
+ // RIGHT: 사용 측이 필요한 것만
292
+ type UserFinder interface {
293
+ FindByID(ctx context.Context, id string) (*User, error)
294
+ }
295
+
296
+ type UserCreator interface {
297
+ Create(ctx context.Context, user *User) error
298
+ }
299
+
300
+ // UserService는 필요한 인터페이스만 조합
301
+ type UserService struct {
302
+ finder UserFinder
303
+ creator UserCreator
304
+ }
305
+ ```
306
+
307
+ ### G5.2 Consumer 측 정의
308
+
309
+ ```go
310
+ // WRONG: 구현 패키지에서 인터페이스 정의 (Go 안티패턴)
311
+ // user/repository.go
312
+ type Repository interface { ... } // 구현 패키지가 자기 인터페이스를 선언
313
+
314
+ // RIGHT: consumer 패키지에서 필요한 것만 정의
315
+ // order/service.go — order 서비스가 필요한 user 기능만 정의
316
+ type userProvider interface {
317
+ FindByID(ctx context.Context, id string) (*User, error)
318
+ }
319
+ ```
320
+
321
+ ### G5.3 표준 라이브러리 인터페이스 우선 활용
322
+
323
+ ```go
324
+ io.Reader, io.Writer, io.Closer — 스트림
325
+ fmt.Stringer — 문자열 표현
326
+ error — 에러
327
+ http.Handler — HTTP 처리
328
+ ```
329
+
330
+ ---
331
+
332
+ ## G6. 메모리 재사용 — sync.Pool
333
+
334
+ **sync.Pool은 신중하게. 올바르게 쓰지 않으면 미묘한 버그 원인.**
335
+
336
+ ### G6.1 sync.Pool 적절한 사용
337
+
338
+ ```go
339
+ // 적합: 버퍼, bytes.Buffer, 인코더 같은 임시 객체
340
+ var bufPool = sync.Pool{
341
+ New: func() interface{} {
342
+ return new(bytes.Buffer)
343
+ },
344
+ }
345
+
346
+ func encode(v interface{}) ([]byte, error) {
347
+ buf := bufPool.Get().(*bytes.Buffer)
348
+ defer func() {
349
+ buf.Reset() // 상태 초기화 필수
350
+ bufPool.Put(buf) // 반환
351
+ }()
352
+ if err := json.NewEncoder(buf).Encode(v); err != nil {
353
+ return nil, err
354
+ }
355
+ return append([]byte{}, buf.Bytes()...), nil // copy — buf는 pool에 반환되므로
356
+ }
357
+ ```
358
+
359
+ ### G6.2 sync.Pool 주의사항
360
+
361
+ - Pool에서 꺼낸 객체의 **이전 상태가 남아있을 수 있다** → `Reset()` / `Zero` 필수.
362
+ - GC가 Pool을 비울 수 있다 — 영구 캐시 용도로 사용 금지.
363
+ - escape analysis 확인: `go build -gcflags='-m'` 으로 heap 할당 여부 확인.
364
+
365
+ ---
366
+
367
+ ## G7. 정적 분석 필수
368
+
369
+ **린터 없이 코드 리뷰는 없다. CI에서 통과 못하면 merge 불가.**
370
+
371
+ ### G7.1 golangci-lint 설정
372
+
373
+ ```yaml
374
+ # .golangci.yml
375
+ linters:
376
+ enable:
377
+ - govet # go vet (공식)
378
+ - staticcheck # SA/S 룰 (버그 탐지)
379
+ - errcheck # 에러 무시 탐지
380
+ - gosimple # 코드 단순화
381
+ - ineffassign # 불필요한 할당
382
+ - unused # 미사용 코드
383
+ - gosec # 보안 룰 (G-시리즈)
384
+ - noctx # context 없는 HTTP 요청 탐지
385
+ - bodyclose # http.Response.Body.Close() 확인
386
+ - nilerr # nil 에러 반환 패턴 탐지
387
+ - wrapcheck # 외부 패키지 에러 wrap 강제
388
+ - cyclop # 순환 복잡도 제한
389
+
390
+ linters-settings:
391
+ cyclop:
392
+ max-complexity: 10
393
+ gosec:
394
+ excludes: ["G304"] # 파일 경로는 의도적으로 제외 가능
395
+
396
+ issues:
397
+ exclude-rules:
398
+ - path: "_test.go"
399
+ linters: [wrapcheck, gosec]
400
+ ```
401
+
402
+ ### G7.2 CI 파이프라인
403
+
404
+ ```yaml
405
+ # .github/workflows/go.yml
406
+ - name: golangci-lint
407
+ uses: golangci/golangci-lint-action@v4
408
+ with:
409
+ version: latest
410
+
411
+ - name: go test
412
+ run: go test -race -coverprofile=coverage.out ./...
413
+
414
+ - name: go vet
415
+ run: go vet ./...
416
+ ```
417
+
418
+ - `-race` 플래그 필수 (data race 탐지).
419
+ - 커버리지: `go tool cover -func=coverage.out` 으로 함수별 커버리지.
420
+
421
+ ---
422
+
423
+ ## G8. 구조화 로그 (slog)
424
+
425
+ ```go
426
+ import "log/slog" // Go 1.21+
427
+
428
+ // 기본 설정
429
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
430
+ Level: slog.LevelInfo,
431
+ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
432
+ // 민감 정보 리댁션
433
+ if a.Key == "password" || a.Key == "token" {
434
+ return slog.String(a.Key, "[REDACTED]")
435
+ }
436
+ return a
437
+ },
438
+ }))
439
+ slog.SetDefault(logger)
440
+
441
+ // 사용
442
+ slog.InfoContext(ctx, "order created",
443
+ slog.String("orderId", order.ID),
444
+ slog.String("userId", order.UserID),
445
+ slog.Float64("amount", order.Amount),
446
+ slog.Duration("duration", time.Since(start)),
447
+ )
448
+ ```
449
+
450
+ ---
451
+
452
+ ## G9. Go 안티패턴 카탈로그
453
+
454
+ | 안티패턴 | 픽스 |
455
+ |----------|------|
456
+ | 에러 무시 (`_ = err`) | 에러 처리 또는 이유 주석 |
457
+ | `panic` in 일반 에러 경로 | `error` 반환값 사용 |
458
+ | context 없는 DB/HTTP 호출 | `xxxContext(ctx, ...)` 버전 사용 |
459
+ | goroutine 종료 조건 없음 | context 취소 또는 done 채널 |
460
+ | `defer cancel()` 누락 | `ctx, cancel := context.WithTimeout(...)` → `defer cancel()` |
461
+ | receiver에서 직접 인터페이스 정의 | consumer 측에서 필요한 것만 |
462
+ | 큰 인터페이스 (5+ 메서드) | 목적별 소형 인터페이스 분리 |
463
+ | `sync.Mutex` Lock 후 defer Unlock 누락 | `defer mu.Unlock()` 패턴 |
464
+ | goroutine 내 루프 변수 캡처 (Go 1.22 미만) | `item := item` 또는 인자로 전달 |
465
+ | `http.Get` (context 없음) | `http.NewRequestWithContext(ctx, ...)` |
466
+ | `json.Unmarshal` 에러 무시 | 에러 처리 |
467
+ | 채널 receiver가 close | sender가 close |
468
+ | `fmt.Println` in 프로덕션 코드 | `slog` 구조화 로그 |
469
+ | `golangci-lint` CI 미설정 | `.golangci.yml` + CI 단계 추가 |