프로그래밍 언어 & 프레임워크/Golang

Go: 패키지 (package, import)

Go 의 패키지는 자바의 패키지와 유사하다. 모든 Go 코드는 특정 패키지에 포함되어야 한다. 특수 패키지인 main 을 제외하고는 모두 일반적인 패키지라도 봐도 무방하다.

Hello, Go

다시 한 번, 또 보고 또 본다. 기본적인 코드에 많은 내용이 들어있다.

package main

import "fmt"

func main() {
	fmt.Println("Hello, Go!")
}

위의 코드는 main 패키지에 소속되어 있음을 볼 수 있다. main특수 패키지이며 프로그램의 메인 코드인 main() 함수가 정의되어 있으며 실행가능한 패키지임을 의미한다. 이 패키지는 다른 코드에 의해 포함되는 용도로 존재하는 것이 아니라 프로그램을 실행하기 위해 존재한다. 이렇듯, 우리가 가장 처음 작성했던 코드마저도 패키지에 소속되어 있는 것을 볼 수 있는데, Go 의 모든 코드는 어떠한 패키지 내부에 포함되어 있다.

내장 패키지는 어디에 있을까?

Hello, Go 의 코드를 보면 import 키워드를 사용하여 fmt 패키지를 포함하고 있는 것을 볼 수 있다. 이 패키지는 내장 패키지이며 go 의 바이너리 파일이 있는 디렉토리의 상위 폴더를 보면 pkg, src 폴더가 있을 텐데, src 폴더에 내장 패키지의 go 파일들이 있는 것을 볼 수 있다.

├── $GOROOT/
│ ├── pkg/
│ ├── src/
│ │ ├── fmt/
│ │ ├── crypto/
│ │ ├── os/
│ │ ├── strings/
│ ├── bin/
│ │ ├── go.exe

src 폴더에 내장 패키지가 있는 것을 알았으면, pkg 폴더는 무엇이란 말인가? 여기에는 내장 패키지들이 컴파일 된 것들이 들어있다. 따라서 우리가 주목해야 하는 것은 src 폴더이다.

패키지 만들기

import 키워드를 사용하여 패키지를 포함할 때 go 가 찾는 폴더에는 GOROOT/src 가 포함된다. 여기서 GOROOT 는 환경 변수의 값으로 대체된다. Go 의 환경변수에는 GOROOTGOPATH 가 있는데, 우리가 go get 을 통해 임의로 설치한 패키지는 일반적으로 GOPATH 로 가며 내장 패키지의 경우 GOROOT 에 있지만, GOROOT 에 임의로 패키지를 추가해도 제대로 동작한다. GOROOT/src 폴더에 hello 패키지를 만들어서 실험을 해보자.  폴더 구조는 아래와 같다.

├── $GOROOT/
│ ├── src
│ │ ├── example.com/
│ │ │ ├── hello
│ │ │ │ ├── hello.go

hello.go 의 코드는 다음과 같다. 패키지의 경로가 example.com/hello 인 것을 주목하자. 또한 함수의 이름이 대문자인 것도 기억하자. 이것은 Go 언어가 가진 아주 중요한 특징이다.

package hello

import "fmt"

func SayHello() {
	fmt.Println("Hello, Go!")
}

그 다음, 이제 어느 곳에서든 main 패키지를 작성하고 hello 패키지를 불러온 뒤, 함수를 실행하자.

package main

import "example.com/hello"

func main() {
	hello.SayHello()
}

아주 놀랍게도 Go 에서는 함수, 변수, 상수, 구조체 등의 이름이 대문자가 아니라면 외부 패키지로 노출하지 않는다. 즉, 소문자의 경우 캡슐화되어 패키지 내부에서만 사용할 수 있고 외부에서는 접근할 수 없다. 이것이 우리가 패키지를 작성할때 함수의 이름을 대문자로 지은 이유다.

 

같은 패키지 내부에 있다면 파일이 여러 개로 분리되어있는 것은 아무런 상관없다.

 

또 한 가지 주목해야 하는 점은, 패키지 경로패키지 이름이다. 패키지 경로는 example.com/hello 이며, 패키지 이름은 hello 이다. 내장 패키지 fmt 의 경우에는 패키지 경로와 이름이 같았지만, 우리가 만든 패키지는 일부러 그렇게 하지 않았다.

go get

go get 을 통해서 다른 패키지를 다운받아 사용할 수 있다. 얻어온 패키지는 일반적으로 GOPATH 로 다운로드 된다. GOPATH작업공간이라고도 하며 사용자마다 별도로 관리된다. 폴더 구조자체는 GOROOT 와 같으나 관리 주체가 다르기때문에 이는 알아둘 필요가 있다.

패키지를 만드는 다른 방법은 없을까?

우리가 패키지를 만들 때마다 GOROOT, GOPATH 의 src 폴더에 패키지를 만들고 테스트하는 것은 상당히 비효율적이다. 그래서 우리는 Go 패키지를 다른 방식으로 만들어 볼 것이다. 아래에 소개하는 문서를 봐도 좋고, 현재 포스트의 문서를 따라와도 된다.

 

https://golang.org/doc/tutorial/create-module

 

Tutorial: Create a Go module - The Go Programming Language

Tutorial: Create a Go module This is the first part of a tutorial that introduces a few fundamental features of the Go language. If you're just getting started with Go, be sure to take a look at the getting started tutorial, which introduces the go command

golang.org

우선 폴더구조는 아래와 같다. main.go 코드는 main 패키지이며 위에서 hello 패키지를 불러와서 하는 것과 같다. hello.go 의 코드는 위에서 작성한 hello 패키지의 코드와 동일하니 생략하자.

├── /
│ ├── main.go
│ ├── example.com/
│ │ ├── hello/
│ │ │ ├── hello.go

go mod

사용자 정의 go 모듈을 만들 때 사용한다. example.com 폴더로 진입하여 go mod init example.com 명령어를 통해 모듈을 정의하자. 그렇게되면 go.mod 파일이 다음과 같이 생긴다. go 모듈을 정의할 때는 모듈 루트에 해야하며 example.comhello 패키지를 포함하는 모듈의 루트다.

module example.com

go 1.13

이제 다시 밖으로 나와서 go mod init main 으로 main 패키지에서 사용할 종속성을 만들어야 한다. 패키지 경로에 example.com 을 포함한다면, 동일 디렉토리에 있는 example.com 폴더에서 찾는다.

module main

go 1.13

replace example.com => ./example.com

go run main.go 를 통해 실행시켜보면 go.mod 파일이 변경되어 있음을 알 수 있다. go.mod 파일은 go 패키지의 종속성을 관리하므로 버전과 관련된 정보가 나와있음을 알 수 있다.

module main

go 1.13

replace example.com => ./example.com

require example.com v0.0.0-00010101000000-000000000000 // indirect
├── /
│ ├── main.go
│ ├── go.mod
│ ├── example.com/
│ │ ├── hello/
│ │ │ ├── hello.go
│ │ ├── go.mod

공식 문서에서 패키지 찾기

Go 의 내장 패키지는 공식 홈페이지 패키지 문서쪽에서 찾을 수 있다. 패키지 목록은 외우는 것이 아니다. 어떠한 패키지가 있고 이 패키지가 무엇을 하는 것인지, 어떨 때 써야할지만 살펴보면 된다.

 

https://golang.org/pkg/

 

Packages - The Go Programming Language

Packages

go doc

go doc 명령어는 go 패키지의 사용법을 살펴볼때 사용한다. 문서화와 관련된 도구가 내장되어 있는 것은 아주 좋다고 볼 수 있는데, 예를 들어 fmt 패키지의 Println() 함수에 대한 사용법이 궁금하다면 go doc fmt Println 을 사용하면 다음과 같이 출력된다.

$ go doc fmt Println
package fmt // import "fmt"

func Println(a ...interface{}) (n int, err error)
    Println formats using the default formats for its operands and writes to  
    standard output. Spaces are always added between operands and a newline is
    appended. It returns the number of bytes written and any write error      
    encountered.