본문 바로가기
프로그래밍/Golang

Golang Package(패키지) 종속성 관리와 모듈화

by slowin 2024. 7. 17.

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 패키지 기본 개념: 구조, 사용법 및 모범 사례 가이드