@su-record/vibe 0.4.6 โ 0.4.8
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/.vibe/rules/core/communication-guide.md +104 -0
- package/.vibe/rules/core/development-philosophy.md +53 -0
- package/.vibe/rules/core/quick-start.md +121 -0
- package/.vibe/rules/languages/dart-flutter.md +509 -0
- package/.vibe/rules/languages/go.md +396 -0
- package/.vibe/rules/languages/java-spring.md +586 -0
- package/.vibe/rules/languages/kotlin-android.md +491 -0
- package/.vibe/rules/languages/python-django.md +371 -0
- package/.vibe/rules/languages/python-fastapi.md +386 -0
- package/.vibe/rules/languages/rust.md +425 -0
- package/.vibe/rules/languages/swift-ios.md +516 -0
- package/.vibe/rules/languages/typescript-nextjs.md +441 -0
- package/.vibe/rules/languages/typescript-node.md +375 -0
- package/.vibe/rules/languages/typescript-react-native.md +446 -0
- package/.vibe/rules/languages/typescript-react.md +525 -0
- package/.vibe/rules/languages/typescript-vue.md +353 -0
- package/.vibe/rules/quality/bdd-contract-testing.md +388 -0
- package/.vibe/rules/quality/checklist.md +276 -0
- package/.vibe/rules/quality/testing-strategy.md +437 -0
- package/.vibe/rules/standards/anti-patterns.md +369 -0
- package/.vibe/rules/standards/code-structure.md +291 -0
- package/.vibe/rules/standards/complexity-metrics.md +312 -0
- package/.vibe/rules/standards/naming-conventions.md +198 -0
- package/.vibe/rules/tools/mcp-hi-ai-guide.md +665 -0
- package/.vibe/rules/tools/mcp-workflow.md +51 -0
- package/bin/vibe +46 -0
- package/package.json +2 -2
|
@@ -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, ¬Found) {
|
|
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, ¬Found) {
|
|
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, ¬Found) {
|
|
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 ์ฃผ์
|