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)
이 한 줄이 하는 일:
(*MySQLUserRepository)(nil)- nil 포인터를*MySQLUserRepository타입으로 캐스팅var _ UserRepository = ...- 인터페이스 타입 변수에 할당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인 개발이라도 사용하면 좋음
인터페이스를 구현할 때 습관적으로 추가해두면, 나중에 인터페이스가 변경되었을 때 빠르게 문제를 발견할 수 있습니다.
반응형
댓글