@su-record/vibe 0.4.3 โ†’ 0.4.5

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.
@@ -0,0 +1,396 @@
1
+ # ๐Ÿน Go ํ’ˆ์งˆ ๊ทœ์น™
2
+
3
+ ## ํ•ต์‹ฌ ์›์น™ (core์—์„œ ์ƒ์†)
4
+
5
+ ```markdown
6
+ โœ… ๋‹จ์ผ ์ฑ…์ž„ (SRP)
7
+ โœ… ์ค‘๋ณต ์ œ๊ฑฐ (DRY)
8
+ โœ… ์žฌ์‚ฌ์šฉ์„ฑ
9
+ โœ… ๋‚ฎ์€ ๋ณต์žก๋„
10
+ โœ… ํ•จ์ˆ˜ โ‰ค 30์ค„
11
+ โœ… ์ค‘์ฒฉ โ‰ค 3๋‹จ๊ณ„
12
+ โœ… Cyclomatic complexity โ‰ค 10
13
+ ```
14
+
15
+ ## Go ํŠนํ™” ๊ทœ์น™
16
+
17
+ ### 1. ์—๋Ÿฌ ์ฒ˜๋ฆฌ
18
+
19
+ ```go
20
+ // โŒ ์—๋Ÿฌ ๋ฌด์‹œ
21
+ data, _ := ioutil.ReadFile("config.json")
22
+
23
+ // โœ… ์—๋Ÿฌ ํ•ญ์ƒ ์ฒ˜๋ฆฌ
24
+ data, err := ioutil.ReadFile("config.json")
25
+ if err != nil {
26
+ return fmt.Errorf("์„ค์ • ํŒŒ์ผ ์ฝ๊ธฐ ์‹คํŒจ: %w", err)
27
+ }
28
+
29
+ // โœ… ์ปค์Šคํ…€ ์—๋Ÿฌ ํƒ€์ž…
30
+ type NotFoundError struct {
31
+ Resource string
32
+ ID string
33
+ }
34
+
35
+ func (e *NotFoundError) Error() string {
36
+ return fmt.Sprintf("%s (ID: %s)๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค", e.Resource, e.ID)
37
+ }
38
+
39
+ // ์‚ฌ์šฉ
40
+ func GetUser(id string) (*User, error) {
41
+ user, err := repo.FindByID(id)
42
+ if err != nil {
43
+ return nil, fmt.Errorf("์‚ฌ์šฉ์ž ์กฐํšŒ ์‹คํŒจ: %w", err)
44
+ }
45
+ if user == nil {
46
+ return nil, &NotFoundError{Resource: "์‚ฌ์šฉ์ž", ID: id}
47
+ }
48
+ return user, nil
49
+ }
50
+ ```
51
+
52
+ ### 2. ๊ตฌ์กฐ์ฒด์™€ ์ธํ„ฐํŽ˜์ด์Šค
53
+
54
+ ```go
55
+ // โœ… ๊ตฌ์กฐ์ฒด ์ •์˜
56
+ type User struct {
57
+ ID string `json:"id"`
58
+ Email string `json:"email"`
59
+ Name string `json:"name"`
60
+ CreatedAt time.Time `json:"created_at"`
61
+ UpdatedAt time.Time `json:"updated_at"`
62
+ }
63
+
64
+ // โœ… ์ƒ์„ฑ์ž ํ•จ์ˆ˜
65
+ func NewUser(email, name string) *User {
66
+ now := time.Now()
67
+ return &User{
68
+ ID: uuid.New().String(),
69
+ Email: email,
70
+ Name: name,
71
+ CreatedAt: now,
72
+ UpdatedAt: now,
73
+ }
74
+ }
75
+
76
+ // โœ… ์ž‘์€ ์ธํ„ฐํŽ˜์ด์Šค (Go์˜ ์ฒ ํ•™)
77
+ type Reader interface {
78
+ Read(p []byte) (n int, err error)
79
+ }
80
+
81
+ type Writer interface {
82
+ Write(p []byte) (n int, err error)
83
+ }
84
+
85
+ // โœ… ์ธํ„ฐํŽ˜์ด์Šค ์กฐํ•ฉ
86
+ type ReadWriter interface {
87
+ Reader
88
+ Writer
89
+ }
90
+
91
+ // โœ… Repository ์ธํ„ฐํŽ˜์ด์Šค
92
+ type UserRepository interface {
93
+ FindByID(ctx context.Context, id string) (*User, error)
94
+ FindByEmail(ctx context.Context, email string) (*User, error)
95
+ Create(ctx context.Context, user *User) error
96
+ Update(ctx context.Context, user *User) error
97
+ Delete(ctx context.Context, id string) error
98
+ }
99
+ ```
100
+
101
+ ### 3. Context ์‚ฌ์šฉ
102
+
103
+ ```go
104
+ // โœ… Context ์ „ํŒŒ
105
+ func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
106
+ // Context๋ฅผ ํ•˜์œ„ ํ•จ์ˆ˜์— ์ „๋‹ฌ
107
+ user, err := s.repo.FindByID(ctx, id)
108
+ if err != nil {
109
+ return nil, err
110
+ }
111
+ return user, nil
112
+ }
113
+
114
+ // โœ… Context ํƒ€์ž„์•„์›ƒ
115
+ func (h *Handler) HandleRequest(w http.ResponseWriter, r *http.Request) {
116
+ ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
117
+ defer cancel()
118
+
119
+ result, err := h.service.Process(ctx)
120
+ if err != nil {
121
+ if errors.Is(err, context.DeadlineExceeded) {
122
+ http.Error(w, "์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ", http.StatusRequestTimeout)
123
+ return
124
+ }
125
+ http.Error(w, err.Error(), http.StatusInternalServerError)
126
+ return
127
+ }
128
+
129
+ json.NewEncoder(w).Encode(result)
130
+ }
131
+ ```
132
+
133
+ ### 4. HTTP ํ•ธ๋“ค๋Ÿฌ (net/http, Gin, Echo)
134
+
135
+ ```go
136
+ // โœ… net/http ํ•ธ๋“ค๋Ÿฌ
137
+ func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
138
+ id := chi.URLParam(r, "id")
139
+
140
+ user, err := h.service.GetUser(r.Context(), id)
141
+ if err != nil {
142
+ var notFound *NotFoundError
143
+ if errors.As(err, &notFound) {
144
+ http.Error(w, err.Error(), http.StatusNotFound)
145
+ return
146
+ }
147
+ http.Error(w, "์„œ๋ฒ„ ์˜ค๋ฅ˜", http.StatusInternalServerError)
148
+ return
149
+ }
150
+
151
+ w.Header().Set("Content-Type", "application/json")
152
+ json.NewEncoder(w).Encode(user)
153
+ }
154
+
155
+ // โœ… Gin ํ•ธ๋“ค๋Ÿฌ
156
+ func (h *UserHandler) GetUser(c *gin.Context) {
157
+ id := c.Param("id")
158
+
159
+ user, err := h.service.GetUser(c.Request.Context(), id)
160
+ if err != nil {
161
+ var notFound *NotFoundError
162
+ if errors.As(err, &notFound) {
163
+ c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
164
+ return
165
+ }
166
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "์„œ๋ฒ„ ์˜ค๋ฅ˜"})
167
+ return
168
+ }
169
+
170
+ c.JSON(http.StatusOK, user)
171
+ }
172
+
173
+ // โœ… Echo ํ•ธ๋“ค๋Ÿฌ
174
+ func (h *UserHandler) GetUser(c echo.Context) error {
175
+ id := c.Param("id")
176
+
177
+ user, err := h.service.GetUser(c.Request().Context(), id)
178
+ if err != nil {
179
+ var notFound *NotFoundError
180
+ if errors.As(err, &notFound) {
181
+ return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
182
+ }
183
+ return c.JSON(http.StatusInternalServerError, map[string]string{"error": "์„œ๋ฒ„ ์˜ค๋ฅ˜"})
184
+ }
185
+
186
+ return c.JSON(http.StatusOK, user)
187
+ }
188
+ ```
189
+
190
+ ### 5. ์˜์กด์„ฑ ์ฃผ์ž…
191
+
192
+ ```go
193
+ // โœ… ๊ตฌ์กฐ์ฒด์— ์˜์กด์„ฑ ์ฃผ์ž…
194
+ type UserService struct {
195
+ repo UserRepository
196
+ cache CacheRepository
197
+ logger *slog.Logger
198
+ }
199
+
200
+ func NewUserService(
201
+ repo UserRepository,
202
+ cache CacheRepository,
203
+ logger *slog.Logger,
204
+ ) *UserService {
205
+ return &UserService{
206
+ repo: repo,
207
+ cache: cache,
208
+ logger: logger,
209
+ }
210
+ }
211
+
212
+ // โœ… ์˜ต์…˜ ํŒจํ„ด
213
+ type ServerOption func(*Server)
214
+
215
+ func WithPort(port int) ServerOption {
216
+ return func(s *Server) {
217
+ s.port = port
218
+ }
219
+ }
220
+
221
+ func WithTimeout(timeout time.Duration) ServerOption {
222
+ return func(s *Server) {
223
+ s.timeout = timeout
224
+ }
225
+ }
226
+
227
+ func NewServer(opts ...ServerOption) *Server {
228
+ s := &Server{
229
+ port: 8080, // ๊ธฐ๋ณธ๊ฐ’
230
+ timeout: 30 * time.Second,
231
+ }
232
+ for _, opt := range opts {
233
+ opt(s)
234
+ }
235
+ return s
236
+ }
237
+
238
+ // ์‚ฌ์šฉ
239
+ server := NewServer(
240
+ WithPort(3000),
241
+ WithTimeout(60*time.Second),
242
+ )
243
+ ```
244
+
245
+ ### 6. ๋™์‹œ์„ฑ
246
+
247
+ ```go
248
+ // โœ… Goroutine + Channel
249
+ func ProcessItems(ctx context.Context, items []Item) ([]Result, error) {
250
+ results := make(chan Result, len(items))
251
+ errs := make(chan error, len(items))
252
+
253
+ var wg sync.WaitGroup
254
+ for _, item := range items {
255
+ wg.Add(1)
256
+ go func(item Item) {
257
+ defer wg.Done()
258
+ result, err := processItem(ctx, item)
259
+ if err != nil {
260
+ errs <- err
261
+ return
262
+ }
263
+ results <- result
264
+ }(item)
265
+ }
266
+
267
+ // ๊ฒฐ๊ณผ ์ˆ˜์ง‘
268
+ go func() {
269
+ wg.Wait()
270
+ close(results)
271
+ close(errs)
272
+ }()
273
+
274
+ var finalResults []Result
275
+ for result := range results {
276
+ finalResults = append(finalResults, result)
277
+ }
278
+
279
+ // ์ฒซ ๋ฒˆ์งธ ์—๋Ÿฌ ๋ฐ˜ํ™˜
280
+ select {
281
+ case err := <-errs:
282
+ return nil, err
283
+ default:
284
+ return finalResults, nil
285
+ }
286
+ }
287
+
288
+ // โœ… errgroup ์‚ฌ์šฉ (๊ถŒ์žฅ)
289
+ import "golang.org/x/sync/errgroup"
290
+
291
+ func ProcessItems(ctx context.Context, items []Item) ([]Result, error) {
292
+ g, ctx := errgroup.WithContext(ctx)
293
+ results := make([]Result, len(items))
294
+
295
+ for i, item := range items {
296
+ i, item := i, item // ํด๋กœ์ € ์บก์ฒ˜
297
+ g.Go(func() error {
298
+ result, err := processItem(ctx, item)
299
+ if err != nil {
300
+ return err
301
+ }
302
+ results[i] = result
303
+ return nil
304
+ })
305
+ }
306
+
307
+ if err := g.Wait(); err != nil {
308
+ return nil, err
309
+ }
310
+ return results, nil
311
+ }
312
+ ```
313
+
314
+ ### 7. ํ…Œ์ŠคํŠธ
315
+
316
+ ```go
317
+ // โœ… ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ
318
+ func TestAdd(t *testing.T) {
319
+ tests := []struct {
320
+ name string
321
+ a, b int
322
+ expected int
323
+ }{
324
+ {"์–‘์ˆ˜ ๋ง์…ˆ", 2, 3, 5},
325
+ {"์Œ์ˆ˜ ๋ง์…ˆ", -1, -2, -3},
326
+ {"์˜๊ณผ ๋ง์…ˆ", 0, 5, 5},
327
+ }
328
+
329
+ for _, tt := range tests {
330
+ t.Run(tt.name, func(t *testing.T) {
331
+ result := Add(tt.a, tt.b)
332
+ if result != tt.expected {
333
+ t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
334
+ }
335
+ })
336
+ }
337
+ }
338
+
339
+ // โœ… Mock ์‚ฌ์šฉ (testify)
340
+ type MockUserRepository struct {
341
+ mock.Mock
342
+ }
343
+
344
+ func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
345
+ args := m.Called(ctx, id)
346
+ if args.Get(0) == nil {
347
+ return nil, args.Error(1)
348
+ }
349
+ return args.Get(0).(*User), args.Error(1)
350
+ }
351
+
352
+ func TestUserService_GetUser(t *testing.T) {
353
+ mockRepo := new(MockUserRepository)
354
+ service := NewUserService(mockRepo, nil, slog.Default())
355
+
356
+ expectedUser := &User{ID: "123", Name: "ํ…Œ์ŠคํŠธ"}
357
+ mockRepo.On("FindByID", mock.Anything, "123").Return(expectedUser, nil)
358
+
359
+ user, err := service.GetUser(context.Background(), "123")
360
+
361
+ assert.NoError(t, err)
362
+ assert.Equal(t, expectedUser, user)
363
+ mockRepo.AssertExpectations(t)
364
+ }
365
+ ```
366
+
367
+ ## ํŒŒ์ผ ๊ตฌ์กฐ
368
+
369
+ ```
370
+ project/
371
+ โ”œโ”€โ”€ cmd/
372
+ โ”‚ โ””โ”€โ”€ server/
373
+ โ”‚ โ””โ”€โ”€ main.go # ์—”ํŠธ๋ฆฌํฌ์ธํŠธ
374
+ โ”œโ”€โ”€ internal/
375
+ โ”‚ โ”œโ”€โ”€ domain/ # ๋„๋ฉ”์ธ ๋ชจ๋ธ
376
+ โ”‚ โ”œโ”€โ”€ handler/ # HTTP ํ•ธ๋“ค๋Ÿฌ
377
+ โ”‚ โ”œโ”€โ”€ service/ # ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
378
+ โ”‚ โ”œโ”€โ”€ repository/ # ๋ฐ์ดํ„ฐ ์•ก์„ธ์Šค
379
+ โ”‚ โ””โ”€โ”€ middleware/ # ๋ฏธ๋“ค์›จ์–ด
380
+ โ”œโ”€โ”€ pkg/ # ์™ธ๋ถ€ ๊ณต๊ฐœ ํŒจํ‚ค์ง€
381
+ โ”œโ”€โ”€ config/ # ์„ค์ •
382
+ โ”œโ”€โ”€ migrations/ # DB ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
383
+ โ”œโ”€โ”€ go.mod
384
+ โ””โ”€โ”€ go.sum
385
+ ```
386
+
387
+ ## ์ฒดํฌ๋ฆฌ์ŠคํŠธ
388
+
389
+ - [ ] ์—๋Ÿฌ ํ•ญ์ƒ ์ฒ˜๋ฆฌ (_, err ๊ธˆ์ง€)
390
+ - [ ] fmt.Errorf("%w", err)๋กœ ์—๋Ÿฌ ๋ž˜ํ•‘
391
+ - [ ] Context ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌ
392
+ - [ ] ์ž‘์€ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜
393
+ - [ ] ์ƒ์„ฑ์ž ํ•จ์ˆ˜ (NewXxx) ์‚ฌ์šฉ
394
+ - [ ] ํ…Œ์ด๋ธ” ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ
395
+ - [ ] gofmt, golint, go vet ํ†ต๊ณผ
396
+ - [ ] ๋™์‹œ์„ฑ์—์„œ race condition ์ฃผ์˜