본문 바로가기
개발의 기록/Server

Go 인터페이스 컴파일 타임 검증 패턴

by prographer J 2025. 12. 23.
728x90

핵심 요약: var _ Interface = (*Impl)(nil) 패턴은 Go의 암시적 인터페이스 구현을 컴파일 타임에 검증하여, 인터페이스 변경 시 빠르게 오류를 발견할 수 있게 합니다.

 

Go에서 인터페이스를 구현할 때 자주 보이는 이 코드, 왜 쓰는 걸까요?

var _ UserRepository = (*MySQLUserRepository)(nil)

Go의 암시적 인터페이스 구현

Go는 다른 언어와 달리 implements 키워드가 없습니다. 메서드 시그니처만 맞으면 자동으로 인터페이스를 구현한 것으로 인정됩니다.

type UserRepository interface {
    FindByID(id int) (*User, error)
}

// MySQLUserRepository는 UserRepository를 "암시적으로" 구현
type MySQLUserRepository struct{}

func (r *MySQLUserRepository) FindByID(id int) (*User, error) {
    // DB 조회 로직
    return &User{}, nil
}

문제: 에러를 언제 발견하는가?

암시적 구현의 단점은 실제로 인터페이스 타입으로 사용할 때까지 구현 여부를 모른다는 것입니다.

// 메서드 시그니처 오타
func (r *MySQLUserRepository) FindById(id int) (*User, error) { // FindByID → FindById
    return &User{}, nil
}

// 여기서 에러 발생 (사용하는 곳)
var repo UserRepository = &MySQLUserRepository{} // 컴파일 에러!

프로젝트가 커지면 구현체 파일과 사용하는 파일이 멀리 떨어져 있어서 디버깅이 어려워집니다.

해결책: 컴파일 타임 검증

var _ UserRepository = (*MySQLUserRepository)(nil)

이 한 줄이 하는 일:

  1. (*MySQLUserRepository)(nil) - nil 포인터를 *MySQLUserRepository 타입으로 캐스팅
  2. var _ UserRepository = ... - 인터페이스 타입 변수에 할당
  3. var _ - 사용하지 않는 변수이므로 blank identifier 사용

결과: 구현체 파일을 컴파일하는 순간 인터페이스 구현 여부를 검증합니다.

에러 발생 위치 비교

상황 검증 코드 없을 때 검증 코드 있을 때
메서드 시그니처 오타 main.go (사용하는 곳) repository.go (구현하는 곳)
인터페이스 변경 후 여러 파일에서 에러 각 구현체 파일에서 에러

실제 사용 사례

Go 표준 라이브러리와 유명 오픈소스에서 널리 사용됩니다:

// Go 표준 라이브러리 (net/http)
var _ http.Handler = (*ServeMux)(nil)

// Docker
var _ container.Backend = (*Daemon)(nil)

// Kubernetes
var _ cache.Store = (*FakeCustomStore)(nil)

결론

  • 필수는 아니지만 권장되는 패턴
  • 코드 1줄로 안정성 향상
  • Go 생태계의 표준 관행
  • 1인 개발이라도 사용하면 좋음

인터페이스를 구현할 때 습관적으로 추가해두면, 나중에 인터페이스가 변경되었을 때 빠르게 문제를 발견할 수 있습니다.

반응형

댓글