| name | golang-patterns |
| description | 함수형 옵션, 작은 인터페이스, 의존성 주입, 동시성 패턴, 에러 처리 및 패키지 구조를 포함한 Go 전용 디자인 패턴 및 모범 사례. 관용적인(idiomatic) Go 패턴을 적용하기 위해 Go 코드를 작업할 때 사용하세요.
|
| metadata | {"origin":"ECC","globs":["**/*.go","**/go.mod","**/go.sum"]} |
Go 패턴 (Go Patterns)
이 스킬은 일반적인 디자인 원칙을 Go 특유의 관용구로 확장한 포괄적인 Go 패턴을 제공합니다.
함수형 옵션 (Functional Options)
유연한 생성자 구성을 위해 함수형 옵션 패턴을 사용하세요:
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) { s.port = port }
}
func NewServer(opts ...Option) *Server {
s := &Server{port: 8080}
for _, opt := range opts {
opt(s)
}
return s
}
이점:
- 하위 호환성을 유지하면서 API 진화 가능
- 기본값을 가진 선택적 매개변수 제공
- 자기 문서화(self-documenting)가 가능한 구성 방식
작은 인터페이스 (Small Interfaces)
인터페이스는 구현되는 곳이 아니라 사용되는 곳에서 정의하세요.
원칙: 인터페이스를 받아들이고, 구조체를 반환하라 (Accept interfaces, return structs)
type UserStore interface {
GetUser(id string) (*User, error)
}
func ProcessUser(store UserStore, id string) error {
user, err := store.GetUser(id)
}
이점:
- 테스트 및 모킹(mocking)이 쉬움
- 느슨한 결합(Loose coupling)
- 명확한 의존성
의존성 주입 (Dependency Injection)
의존성을 주입하기 위해 생성자 함수를 사용하세요:
func NewUserService(repo UserRepository, logger Logger) *UserService {
return &UserService{
repo: repo,
logger: logger,
}
}
패턴:
- 생성자 함수 (New* 접두사)
- 매개변수로 명시적인 의존성 전달
- 구체적인 타입(concrete types) 반환
- 생성자에서 의존성 검증
동시성 패턴 (Concurrency Patterns)
워커 풀 (Worker Pool)
func workerPool(jobs <-chan Job, results chan<- Result, workers int) {
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- processJob(job)
}
}()
}
wg.Wait()
close(results)
}
컨텍스트 전파 (Context Propagation)
항상 context를 첫 번째 매개변수로 전달하세요:
func FetchUser(ctx context.Context, id string) (*User, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
}
에러 처리 (Error Handling)
에러 래핑 (Error Wrapping)
if err != nil {
return fmt.Errorf("사용자 %s 정보를 가져오지 못함: %w", id, err)
}
커스텀 에러
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}
센티넬 에러 (Sentinel Errors)
var (
ErrNotFound = errors.New("찾을 수 없음")
ErrInvalid = errors.New("유효하지 않은 입력")
)
if errors.Is(err, ErrNotFound) {
}
패키지 조직 (Package Organization)
구조
project/
├── cmd/ # 메인 애플리케이션
│ └── server/
│ └── main.go
├── internal/ # 비공개 애플리케이션 코드
│ ├── domain/ # 비즈니스 로직
│ ├── handler/ # HTTP 핸들러
│ └── repository/ # 데이터 접근
└── pkg/ # 공개 라이브러리
명명 규칙
- 패키지 이름: 소문자, 단일 단어
- 중복 명명 피하기:
user.UserModel 대신 user.User 사용
- 비공개 코드에는
internal/ 사용
main 패키지는 최소한으로 유지
테스트 패턴
테이블 기반 테스트
func TestValidate(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
}{
{"유효함", "test@example.com", false},
{"유효하지 않음", "not-an-email", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Validate(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("에러 결과: %v, wantErr %v", err, tt.wantErr)
}
})
}
}
테스트 헬퍼
func testDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("테스트 DB 오픈 실패: %v", err)
}
t.Cleanup(func() { db.Close() })
return db
}
이 스킬을 사용하는 시점
- Go API 및 패키지를 설계할 때
- 동시성 시스템을 구현할 때
- Go 프로젝트 구조를 잡을 때
- 관용적인 Go 코드를 작성할 때
- Go 코드베이스를 리팩터링할 때