Go 패키지(package) – 업무에 사용하는 Go 언어 14

Go 언어 패키지(package)
Go 언어 패키지(package)

Go 언어는 소프트웨어의 유지보수성과 확장성을 극대화하기 위해 패키지(package) 개념을 핵심적인 구조 요소로 채택하고 있습니다.
이 글에서는 go 패키지(package)라는 주제를 중심으로, 패키지의 개념적 기반부터 실질적인 활용법, 외부와의 인터페이스 설계, 초기화 함수의 작동 방식까지 깊이 있게 다루고자 합니다.
필자는 다양한 규모의 프로젝트에 Go 언어를 도입해본 경험을 바탕으로, 실제 개발 환경에서의 활용 사례와 전략적 고려 사항을 포함하여 본 내용을 정리하였습니다.


go 패키지(Package)

패키지는 Go 언어에서 코드를 논리적으로 구성하는 기본 단위입니다.
여러 개의 관련된 함수, 타입, 변수 등을 하나의 폴더로 묶어주는 역할을 하며, 프로젝트의 구조를 명확하게 정리할 수 있습니다.
하나의 패키지는 하나 이상의 소스 파일로 구성될 수 있고, 이 파일들은 모두 동일한 패키지명으로 선언되어야 합니다.

Go 코드 파일의 맨 위에는 항상 package 키워드를 사용하여 해당 파일이 속한 패키지를 명시합니다. 예를 들어, 실행 가능한 프로그램의 진입점 파일은 package main으로 선언되어야 하며, 이 외의 일반적인 기능을 제공하는 파일들은 보통 해당 기능에 맞는 이름의 패키지를 사용합니다.
이전 글에서 main.go의 가장 윗 줄에 package main이라고 항상 적혀있던 이유입니다.

필자는 이 구조가 개발자 간의 협업 시 코드의 역할을 명확히 해준다고 생각한다.
유지/보수를 할 때에도 빠르게 원하는 위치를 파악할 수 있어 매우 유용하다고 느꼈다.
아무런 의미 없이 폴더 안에 관련 없는 파일들을 다 때려박는 경우를 막을 수 있다고 생각한다.


패키지 사용하기

다른 패키지를 사용하려면 import 키워드를 활용합니다. 표준 라이브러리의 경우에는 간단히 패키지명을 지정하면 되고, 외부 패키지는 Go 모듈 환경에서 자동으로 다운로드되고 관리됩니다.

예를 들어, 가장 기본적인 출력 기능을 제공하는 fmt 패키지를 사용하려면 다음과 같이 작성합니다:

Go
import "fmt"

그 후에는 fmt.Println() 형태로 함수에 접근할 수 있습니다.

Go
fmt.Println("Hello, world")

여러 패키지를 동시에 import할 경우 괄호로 묶어 다음과 같이 표현할 수 있습니다.

Go
import (
    "fmt"
    "math"
    "strings"
)

모듈(module)

Go 1.11 버전부터 공식적으로 도입된 Go 모듈은 Go 언어에서 패키지를 효율적으로 관리하기 위한 의존성 관리 시스템입니다.
이 시스템은 외부 패키지의 버전을 명시적으로 지정하고, 프로젝트 간 충돌 없이 각기 다른 버전을 사용할 수 있게 해줍니다. 파이썬으로 따지면 가상 환경이라고 연상시켜 보면 좋을 것 같습니다.
이전에는 모든 Go 프로젝트가 GOPATH라는 고정된 디렉토리 내에서 관리되어야 했기 때문에 다양한 프로젝트를 동시에 다루는 데 불편함이 있었습니다.
그러나 Go 모듈의 도입 이후, 각 프로젝트 디렉토리 안에 자체적인 모듈 환경을 구성할 수 있게 되면서 이러한 불편함이 크게 해소되었습니다.
따라서 개발자는 더 유연하고 안정적인 방식으로 go언어 package를 관리할 수 있으며, 이로 인해 협업이나 오픈소스 프로젝트 참여 시에도 일관된 개발 환경을 유지하는 것이 훨씬 수월해졌습니다.

Go 모듈은 아래 명령어로 시작할 수 있습니다.

Bash
go mod init example.com/myproject

이후 외부 패키지를 import하면 go get 명령 없이도 필요한 의존성이 자동으로 설치되며, go.modgo.sum 파일에 관련 정보가 저장됩니다.

필자는 Go 모듈을 통해 외부 패키지 의존성을 명확하게 관리할 수 있게 되었고,
특히 협업 중 다른 팀원의 환경에서도 동일하게 작동한다는 점에서 큰 장점을 느꼈다.
go언어 package를 관리하는 데 있어 Go 모듈은 필수적인 구성 요소라 생각한다.


패키지명과 패키지 외부 공개

Go 언어에서는 패키지 내부에서 선언된 식별자가 대문자로 시작하면 외부에 공개(public)되고,
소문자로 시작하면 해당 패키지 내부에서만 접근할 수 있는 비공개(private) 요소로 취급됩니다.

아래의 예시를 보겠습니다.

Go
package mathutil

func Add(a int, b int) int {
    return a + b
}

func subtract(a int, b int) int {
    return a - b
}

위 코드에서 Add 함수는 외부에서도 사용할 수 있지만, subtract 함수는 같은 패키지 내에서만 사용할 수 있습니다.

이러한 방식은 접근 권한을 단순한 네이밍 규칙으로 제어한다는 점에서 직관적이며,
명시적인 접근 제한자가 없는 Go의 철학이 깔끔하고 간결하다고 느꼈습니다.
실제 프로젝트에서도 기능을 외부에 공개할 것인지 아닌지를 이름을 통해 명확히 정할 수 있어 실수가 줄어드는 장점이 있습니다.


패키지 초기화

Go에서는 init() 함수라는 특별한 함수가 존재합니다.
각 패키지는 하나 이상의 init() 함수를 가질 수 있으며, 해당 패키지가 import될 때 자동으로 호출됩니다.
이 함수는 보통 설정 초기화, 데이터 파일 로딩, DB 연결 등의 작업을 처리하는 데 사용됩니다.

Go
package mathutil

func init() {
    fmt.Println("mathutil 패키지 import 됨")
}

init() 함수는 명시적으로 호출하지 않아도 자동으로 실행되며,
여러 개가 존재할 경우 파일 내 선언 순서대로 실행됩니다.
이 함수는 return 값을 가질 수 없으며, 패키지 로드 시점에 필요한 초기화 작업을 수행하기 위한 목적으로 설계되었습니다.

그렇다면 다음 코드의 실행 결과는 어떻게 될까요?

Go
package main

import "utils/mathutil"

func init() {
    fmt.Println("func init()")
}

func main() {
    fmt.Println("func main()")
}
terminal> go run main.go

mathutil 패키지 import 됨
func init()
func main()

init() 함수는 main() 함수보다 먼저 실행된다는 것이 핵심이 되겠습니다.


Go 언어의 패키지 시스템은 단순하지만 매우 강력합니다.
go언어 package에 대한 이해는 체계적인 프로젝트 구조를 설계하는 데 있어 핵심적인 요소입니다.
위에서 설명한 내용들을 바탕으로 자신만의 패키지 구조를 설계하고,
이를 통해 더욱 깔끔하고 효율적인 Go 애플리케이션을 만들어가시기 바랍니다.