Go 인터페이스와 덕 타이핑 이해하기
배경
회사에서 마이크로서비스 일부를 Node에서 Go로 마이그레이션하는 작업을 맡았다. Java 경험이 있어서 정적 타입 언어는 익숙했지만, Go의 인터페이스는 처음엔 이해가 안 갔다.
Java vs Go 인터페이스
Java에서는 명시적으로 implements 키워드를 사용한다.
public class Dog implements Animal {
public void Speak() { ... }
}
Go는 다르다. 인터페이스를 만족하는 메서드만 구현하면 자동으로 인터페이스를 구현한 것으로 간주된다.
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
// Dog는 자동으로 Speaker를 구현
실제 활용
HTTP 핸들러를 추상화할 때 유용했다.
type Handler interface {
Handle(w http.ResponseWriter, r *http.Request) error
}
type UserHandler struct {
db *sql.DB
}
func (h UserHandler) Handle(w http.ResponseWriter, r *http.Request) error {
// 실제 로직
return nil
}
func WrapHandler(h Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := h.Handle(w, r); err != nil {
http.Error(w, err.Error(), 500)
}
}
}
장점
- 의존성 역전이 쉽다. 외부 라이브러리 타입에 대해 내 패키지에서 인터페이스를 정의하고 사용할 수 있다.
- 테스트용 목(mock) 작성이 간단하다.
- 작은 인터페이스 조합이 자연스럽다.
io.Reader,io.Writer같은 단일 메서드 인터페이스가 강력한 이유다.
주의점
빈 인터페이스 interface{}는 any 타입처럼 동작한다. 남발하면 타입 안정성을 잃는다. 꼭 필요한 경우만 사용하고, 가능하면 구체적인 인터페이스를 정의하는 게 좋다.