Go 언어의 창시자 중 한 명인 Rob Pike는 Google에서 대규모 C++ 프로젝트를 개발하면서 정말 답답한 상황에 직면했습니다. 그의 팀은 대규모 컴퓨팅 클러스터를 구축하고 있었는데, 전체 시스템을 컴파일하는 데 무려 1시간이나 걸렸습니다. 이런 긴 컴파일 시간의 주요 원인은 바로 복잡한 종속성 계층 구조였고, 이는 개발자들에게 엄청난 스트레스를 주고 있었습니다.
Rob Pike : "어느 날 보니 이해할 수 없는 종속성 패키지가 빌드되고 있었습니다. 찾아보니 전혀 사용하지 않는 패키지였고, 그 패키지의 빌드를 위해 37,000번이나 컴파일되고 있었습니다. 정말 머리가 아픈 상황이었죠." [Google I/O 2012 - Meet the Go Team ]
이러한 짜증 나고 비효율적인 경험은 롭파이크와 그의 동료들이 새로운 프로그래밍 언어, 즉 Go를 설계하는 데 큰 영향을 미쳤습니다. Go의 핵심 목표 중 하나는 이러한 종속성 문제를 근본적으로 해결하여 개발자들의 스트레스를 줄이고 생산성을 높이는 것이었습니다.
Go의 패키지 시스템
Go의 패키지 시스템은 C++의 문제점을 해결하기 위해 여러 가지 방식을 도입했습니다.
1. 명시적이고 간단한 import 구문
Go는 매우 명확하고 간단한 import 구문을 사용합니다. 각 소스 파일의 시작 부분에 필요한 패키지를 명시적으로 나열합니다.
package main
import "fmt"
...
2. 사용하지 않는 import 금지
Go 컴파일러는 import된 패키지가 실제로 사용되지 않으면 오류를 발생시킵니다.
package main
import (
"fmt"
"net/http" // 사용되지 않음
)
func main() {
fmt.Println("Hello, World!")
}
이 코드는 net/http 패키지가 사용되지 않았기 때문에 컴파일 오류가 발생합니다.
$ go build
$ ./main.go:5:4: "net/http" imported and not used
이런 엄격한 규칙은 불필요한 종속성을 방지하고, 코드의 명확성을 높입니다.
사용되지 않는 종속성 제거는 `go mod tidy`를 실행하여 제거 할 수 있습니다.
go mod tidy
3. 순환 종속성 금지
Go는 패키지 간의 순환 종속성을 허용하지 않습니다. 예를 들어, 패키지 A가 패키지 B를 import하고, 동시에 패키지 B가 패키지 A를 import하는 것은 불가능합니다. [ 참고 : 7. Dependencies in Go ]
// circular_dependency_example/main.go
package main
import (
"circular_dependency_example/packageA"
"fmt"
)
func main() {
fmt.Println(packageA.A())
}
// circular_dependency_example/packageA/a.go
package packageA
import "circular_dependency_example/packageB"
func A() string {
return "Function A " + packageB.B()
}
// circular_dependency_example/packageB/b.go
package packageB
import "circular_dependency_example/packageA"
func B() string {
return "Function B " + packageA.A()
}
위 코드를 빌드하면 에러가 발생합니다.
package circular_dependency_example
imports circular_dependency_example/packageA
imports circular_dependency_example/packageB
imports circular_dependency_example/packageA: import cycle not allowed
이 규칙의 장점:
- 복잡한 종속성 그래프를 단순화합니다.
- 빌드 시간을 크게 줄입니다.
- 코드의 구조를 더 명확하고 모듈화하도록 유도합니다.
4. 표준 패키지 관리: go mod
Go 1.11[Go 1.11 Release Notes]부터 도입된 go mod 명령은 종속성 관리를 더욱 체계화했습니다.
모듈은 루트에 go.mod 파일을 가진 파일 트리에 저장된 Go 패키지 모음입니다. go.mod 파일은 모듈 경로와 빌드를 위해 필요한 종속성을 정의합니다.
> 예시 디렉토리 구조와 코드
myproject/
├── go.mod
├── main.go
└── world/
└── world.go
# 루트_디렉토리/go.mod
module example.com/hello
go 1.16
# main.go
package main
import (
"fmt"
"example.com/hello/world"
)
func main() {
fmt.Println(world.Greet())
}
# world/world.go
package world
func Greet() string {
return "Hello, World!"
}
이 구조에서 `go.mod` 파일은 모듈의 루트에 위치하며, 하위 디렉토리의 패키지들은 모듈 경로와 하위 디렉토리 경로를 결합하여 가져오기 경로를 형성합니다. 예를 들어, `world` 디렉토리의 패키지는 `example.com/hello/world` 경로를 사용합니다. 하위 디렉토리에서 `go mod init`을 실행할 필요는 없으며, 자동으로 모듈의 일부로 인식됩니다.
정리
Go의 패키지 시스템은 C++에서 겪었던 "종속성 지옥"을 효과적으로 해결했습니다. 명확성, 간결성, 그리고 엄격한 규칙을 통해 Go는 개발자들이 대규모 프로젝트에서도 효율적으로 작업할 수 있는 환경을 제공합니다.
Go의 패키지 시스템은 단순히 기술적인 문제를 해결하는 것을 넘어서, 소프트웨어 설계 철학을 반영합니다. "명확함이 복잡함보다 낫다"
관련
Golang 패키지 네이밍 가이드: 효과적인 패키지 이름 짓기 12가지 팁
Golang 모듈 발행 가이드: GitHub에 패키지 배포
'프로그래밍 > Golang' 카테고리의 다른 글
Golang Constants(상수) (0) | 2024.07.20 |
---|---|
Golang 변수와 Scope(스코프) 이해하기 (0) | 2024.07.19 |
Golang Module(모듈) 발행 가이드: GitHub에 패키지 배포 (0) | 2024.07.18 |
Golang Package(패키지) 네이밍 가이드: 효과적인 패키지 이름 짓기 12가지 팁 (0) | 2024.07.16 |
Golang Package(패키지) 기본 개념: 구조, 사용법 및 모범 사례 가이드 (0) | 2024.07.15 |