Kim Seogyu
Backend

Go 인터페이스 설계 원칙 - Accept Interfaces, Return Structs

Go 인터페이스를 작고 명확하게 설계하는 기준과 포인터/값 수신자 선택법을 정리합니다.

Published 2026년 1월 2일2 min read267 words

Go 인터페이스 설계 원칙

핵심 규칙

  • Accept interfaces: 입력은 추상화에 의존합니다.
  • Return structs: 반환은 구체 타입으로 명확히 전달합니다.
  • 인터페이스는 구현자가 아니라 소비자 패키지에서 정의합니다.

1) 입력은 인터페이스

type UserRepo interface {
	FindByID(ctx context.Context, id string) (*User, error)
}

type Service struct {
	repo UserRepo
}

func NewService(repo UserRepo) *Service {
	return &Service{repo: repo}
}

이 구조면 테스트에서 mock 주입이 쉽고 구현체 교체 비용이 작습니다.

2) 반환은 구조체

func NewPostgresRepo(db *sql.DB) *PostgresRepo {
	return &PostgresRepo{db: db}
}

반환 타입을 인터페이스로 감추면 호출자가 실제 기능을 알기 어렵고 타입 추론도 약해집니다.

3) 인터페이스는 작게

type UserFinder interface {
	FindByID(ctx context.Context, id string) (*User, error)
}

type UserSaver interface {
	Save(ctx context.Context, u *User) error
}

큰 인터페이스는 구현 부담과 결합도를 동시에 키웁니다.

4) 포인터 vs 값 수신자

선택 기준:

  • 상태 변경 필요: 포인터 수신자
  • 큰 구조체: 포인터 수신자
  • 작은 불변 값: 값 수신자 가능

중요한 점은 혼용 최소화입니다. 타입 하나에서는 가능하면 한 스타일로 통일하세요.

5) 인터페이스 만족 조건 주의

포인터 수신자 메서드만 있으면 값 타입은 인터페이스를 만족하지 못합니다.

type S interface{ String() string }

type T struct{ v string }
func (t *T) String() string { return t.v }

var s S
// s = T{v:"x"}    // 불가
s = &T{v: "x"}      // 가능

실무 체크리스트

  • 인터페이스가 3개 메서드를 넘어가면 분리 검토
  • 생성자 반환 타입이 인터페이스인지 이유 확인
  • 테스트 더블(mock/fake) 구현 난이도가 과도한지 확인

요약

Go 인터페이스 설계는 "추상화의 위치"가 핵심입니다. 소비자 기준의 작은 인터페이스와 명확한 구체 반환을 지키면 테스트성과 확장성이 동시에 확보됩니다.

Share

Related Articles

Comments

이 블로그는 제가 알고 있는 것들을 잊지 않기 위해 기록하는 공간입니다.
직접 작성한 글도 있고, AI의 도움을 받아 정리한 글도 있습니다.
정확하지 않은 내용이 있을 수 있으니 참고용으로 봐주세요.

© 2026 Seogyu Kim