Go 패키지와 모듈로 코드 조직화하기
Go 패키지와 모듈로 코드 조직화하기
Go에서 코드는 패키지(package) 단위로 묶여요. 같은 디렉터리에 있는 소스 파일들이 모여 하나의 패키지를 이루고, 그 안에서 정의한 함수·타입·변수·상수는 패키지 내부의 다른 파일들끼리 서로 공유할 수 있어요. 그리고 그보다 더 큰 묶음인 **모듈(module)**은 관련된 여러 패키지를 함께 배포하는 단위예요. 코드를 만들기 시작할 때 모듈 경로를 정하고 패키지를 그 아래에 배치하는 방식부터 이해하면, 파일이 늘어나도 구조가 흐트러지지 않아요.
Go 공식 문서는 모듈 루트에 go.mod 파일을 두고 그 경로를 앞부분에 붙여 import 경로를 만든다고 안내해요. 이 페이지는 공식 "How to Write Go Code" 문서의 핵심을 따라가며, 패키지와 모듈을 어떻게 잡아야 하는지 차근차근 설명할게요.
코드를 담는 단위: 패키지와 모듈
Go 프로그램은 패키지로 조직됩니다. 패키지는 같은 디렉터리에 있는 소스 파일의 모임이고, 그 파일들이 함께 컴파일돼요. 한 파일에서 정의한 함수·타입·변수·상수는 같은 패키지 안의 다른 파일에서도 그대로 보여요.
그 위에 모듈이 있어요. 모듈은 함께 배포되는 관련 패키지들의 묶음이고, 저장소(repository)는 보통 하나의 모듈을 담아요. 저장소 루트에 있는 go.mod 파일이 모듈 경로를 선언하고, 이 경로가 그 모듈 안 모든 패키지의 import 경로 접두사가 돼요.
$ go mod init example/user/hello
go: creating new go.mod: module example/user/hello
module example/user/hello
go 1.16
모든 소스 파일의 첫 줄은 패키지 선언이어야 해요. 그리고 실행 프로그램(executable)은 반드시 package main으로 시작해야 하죠.
import 경로가 어디를 가리키나
모듈 경로는 import 경로의 접두사일 뿐 아니라, go 명령이 그 모듈을 어디서 내려받을지를 알려주는 단서가 돼요. 가령 github.com/google/go-cmp 모듈을 가져온다면 go 명령은 그 모듈이 위치한 저장소를 참고해 소스를 가져와요.
패키지의 import 경로는 모듈 경로 + 그 모듈 안에서의 하위 디렉터리로 정해져요. 예를 들어 모듈 github.com/google/go-cmp 안에 cmp/ 디렉터리의 패키지가 있다면, 그 패키지의 import 경로는 github.com/google/go-cmp/cmp가 돼요. 표준 라이브러리의 패키지에는 모듈 경로 접두사가 붙지 않아요.
첫 프로그램과 패키지 만들기
hello 프로그램 안에서 morestrings라는 작은 패키지를 직접 만들어 import해 볼게요. 먼저 morestrings 디렉터리에 reverse.go 파일을 만들고 함수를 정의해요.
// Package morestrings implements additional functions to manipulate UTF-8
// encoded strings, beyond what is provided in the standard "strings" package.
package morestrings
// ReverseRunes returns its argument string reversed rune-wise left to right.
func ReverseRunes(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
ReverseRunes는 대문자로 시작하니까 외부로 내보내진(exported) 함수예요. 그래서 이 패키지를 import한 다른 패키지에서 호출할 수 있어요. go build로 컴파일이 되는지 확인하면 별도 출력 파일 없이 컴파일 결과만 로컬 빌드 캐시에 저장돼요.
이제 hello.go에서 그 패키지를 가져와 사용해볼게요.
package main
import (
"fmt"
"example/user/hello/morestrings"
)
func main() {
fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}
원격 모듈의 패키지 가져오기
import 경로는 Git 같은 버전 관리 시스템으로 소스를 얻는 방법까지 설명할 수 있어요. go 도구는 이 성질을 이용해 원격 저장소에서 패키지를 자동으로 내려받죠. 외부 의존성이 생기면 go.mod에 버전을 기록해야 해요. go mod tidy는 import한 패키지에 필요한 모듈 요구사항을 추가하고, 더 이상 쓰지 않는 모듈의 요구사항은 제거해요.
$ go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.4
module example/user/hello
go 1.16
require github.com/google/go-cmp v0.5.4
다운로드한 모듈은 GOPATH가 가리키는 디렉터리의 pkg/mod 하위에 저장되고, 같은 버전을 요구하는 모듈끼리 공유할 수 있어서 읽기 전용으로 표시돼요. go clean -modcache로 내려받은 모듈 전부를 지울 수도 있어요.
테스트 추가하기
Go는 go test 명령과 testing 패키지로 이뤄진 가벼운 테스트 프레임워크를 갖고 있어요. 파일 이름이 _test.go로 끝나는 파일에 func (t *testing.T) 시그니처의 TestXXX 함수를 만들면 그게 테스트가 돼요. 그 안에서 t.Error 같은 실패 함수를 부르면 테스트가 실패한 것으로 간주돼요.
package morestrings
import "testing"
func TestReverseRunes(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := ReverseRunes(c.in)
if got != c.want {
t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
}
}
}
$ go test
PASS
ok example/user/hello/morestrings 0.165s
더 알아보기 (Learn more)
- Effective Go: 명확하고 관용적인 Go 코드를 쓰는 팁
- A Tour of Go: 언어 자체를 배우는 대화형 학습
- go 명령어 문서: 모듈·패키지·명령을 내려받고 빌드하는 표준 도구