Go 패키지 생성 및 import 방법

GolangBeginner
지금 연습하기

소개

이전 섹션에서 다음과 같은 코드 줄을 포함하는 기본적인 Go 프로그램을 완성했습니다.

package main
import "fmt"

이 두 줄의 코드를 어떻게 이해해야 할까요? packageimport 문을 효과적으로 사용하는 방법은 무엇일까요?

이 Lab 에서는 Go 에서 패키지를 생성하고 가져오는 방법을 배우게 됩니다. 이를 통해 코드를 재사용 가능한 모듈로 구성하여 Go 프로젝트의 유지 관리 및 확장성을 향상시킬 수 있습니다.

지식 포인트:

  • 패키지의 정의 및 선언
  • export 된 (public) 및 unexported 된 (private) 식별자 이해
  • 패키지 가져오기의 다양한 형태: 단일, 그룹, dot, alias, 익명 import
이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 82%입니다.학습자들로부터 88%의 긍정적인 리뷰율을 받았습니다.

패키지 선언 및 정의

Go 에서 **패키지 (package)**는 Python 의 모듈 또는 C 의 라이브러리와 유사합니다. 코드를 구성하고 재사용하기 위해 사용되는 소스 코드 파일의 모음입니다. 모든 Go 파일은 파일 시작 부분에서 패키지를 선언해야 합니다.

참고: Go 프로그램은 실행 진입점 역할을 하는 main이라는 이름의 패키지를 하나만 가져야 합니다. 이것이 없으면 프로그램은 실행 파일을 생성할 수 없습니다.

핵심 포인트:

  1. Exported (Public) 식별자: 대문자로 시작하는 식별자 (변수, 함수, 타입 등) 는 다른 패키지에서 접근할 수 있습니다. 이것을 패키지의 public 인터페이스라고 생각하십시오.
  2. Unexported (Private) 식별자: 소문자로 시작하는 식별자는 동일한 패키지 내에서만 접근할 수 있습니다. 이것은 패키지의 내부 구현 세부 사항으로 간주됩니다.
  3. 패키지 응집성: 동일한 폴더의 모든 파일은 동일한 패키지에 속해야 합니다. 이렇게 하면 관련 코드가 함께 유지됩니다.
  4. 패키지 명명 규칙: 패키지 이름은 소문자, 짧고 설명적이어야 하며 밑줄 또는 대문자를 사용하지 않아야 합니다.

자신만의 사용자 정의 패키지를 만들어 봅시다:

  1. propagandist라는 폴더와 그 안에 propagandist.go 파일을 만듭니다:

    mkdir ~/project/propagandist
    touch ~/project/propagandist/propagandist.go
  2. propagandist.go에 다음 코드를 작성합니다:

    package propagandist
    
    var Shout = "I Love LabEx" // Public variable
    var secret = "I love the dress" // Private variable
    
    func Hit() string {
        return "Don't hit me, please!"
    }
    • Shoutpublic입니다. 대문자로 시작하기 때문입니다. 즉, propagandist를 import 하는 다른 패키지에서 접근할 수 있습니다.
    • secretprivate입니다. 소문자로 시작하기 때문입니다. propagandist 패키지 내에서만 사용할 수 있습니다.
    • Hit는 다른 패키지에서 접근할 수 있는 public 함수입니다.
  3. 패키지에 대한 Go 모듈을 초기화합니다:

    cd ~/project/propagandist
    go mod init propagandist

    이 명령은 propagandist 디렉토리에 새 Go 모듈을 초기화하여 패키지의 종속성을 관리하는 데 도움이 됩니다.

단일 항목 Import

propagandist 패키지를 사용하기 위해 새로운 Go 프로그램을 만들어 봅시다. 이 단계에서는 Go 코드에서 단일 패키지를 import 하고 사용하는 방법을 보여줍니다.

  1. 프로젝트 폴더에 pacExercise.go라는 새 Go 파일을 만듭니다:

    touch ~/project/pacExercise.go
  2. 프로그램에 대한 Go 모듈을 초기화합니다:

    cd ~/project
    go mod init pacExercise
  3. go.mod 파일을 업데이트하여 로컬 패키지 종속성을 포함합니다. 터미널에서 다음 명령을 실행합니다:

    echo "replace propagandist => ./propagandist" >> go.mod

    중요: 이 명령은 go.mod 파일에 replace 지시문을 추가합니다. 이는 Go 에게 propagandist 패키지를 원격 저장소에서 다운로드하는 대신 로컬 디렉토리 ./propagandist에서 가져와야 한다고 알려주기 때문에 중요합니다. 터미널에서 이 명령을 실행해야 하며, 그러면 replace propagandist => ./propagandist 줄이 go.mod 파일에 추가됩니다. 이 줄을 파일에 직접 수동으로 작성하지 마십시오.

  4. propagandist 패키지를 import 하고 사용하기 위해 pacExercise.go에 다음 코드를 작성합니다:

    package main
    
    import (
        "fmt"
        "propagandist"
    )
    
    func main() {
        fmt.Println(propagandist.Shout) // Access the public variable
    }
    • 이 코드는 출력을 위해 fmt 패키지와 propagandist 패키지를 import 합니다.
    • 그런 다음 propagandist.Shout를 사용하여 propagandist 패키지에서 public 변수 Shout에 접근합니다.
  5. 프로그램을 실행합니다:

    go mod tidy
    go run pacExercise.go

    go mod tidy 명령은 go.mod 파일이 새로운 종속성으로 업데이트되었는지 확인합니다. go run pacExercise.go 명령은 프로그램을 컴파일하고 실행합니다.

    예상 출력:

    I Love LabEx

그룹 임포트

여러 패키지를 import 할 때, 가독성과 구성을 위해 그룹화된 import 를 사용할 수 있습니다. 이는 스타일 선택이며 코드의 기능에는 영향을 미치지 않습니다.

  1. pacExercise.go를 수정하여 그룹화된 import 를 사용합니다:

    package main
    
    import (
        "fmt"
        "propagandist"
    )
    
    func main() {
        fmt.Println(propagandist.Shout)
    }

    위 코드 조각에서 fmtpropagandist 패키지는 괄호로 묶인 단일 import 블록 내에서 import 됩니다. 이렇게 하면 여러 패키지 import 를 더 쉽게 읽고 관리할 수 있습니다. 이는 이전 예제와 정확히 동일하며 그룹화된 import 구문을 사용하는 방법을 보여줍니다.

  2. 프로그램을 실행하여 여전히 작동하는지 확인합니다:

    go run pacExercise.go

    프로그램은 오류 없이 실행되어 이전과 동일한 결과를 출력해야 합니다.

Dot Import (점 임포트)

**Dot import (점 import)**를 사용하면 함수나 변수를 호출할 때 패키지 이름 접두사를 생략할 수 있습니다. 이는 네임스페이스 충돌을 일으키고 가독성을 떨어뜨릴 수 있으므로 명시적인 패키지 이름을 사용하는 것이 권장되지 않습니다. 하지만, 이것이 무엇인지 아는 것은 좋습니다.

  1. pacExercise.go를 수정하여 fmt에 dot import 를 사용합니다:

    package main
    
    import . "fmt"
    import "propagandist"
    
    func main() {
        Println(propagandist.Shout) // No `fmt.` prefix needed
    }
    • 여기서 import . "fmt"fmt 패키지의 함수와 변수를 fmt. 접두사 없이 직접 사용할 수 있음을 의미합니다.
    • 예를 들어, fmt.Println 대신 Println을 사용합니다.
  2. 프로그램을 실행합니다:

    go run pacExercise.go

    예상 출력:

    I Love LabEx

Alias Import (별칭 임포트)

명확성을 위해 또는 두 패키지가 유사한 이름을 가질 때 충돌을 피하기 위해 import 된 패키지에 별칭을 지정할 수 있습니다. 이는 코드를 더 읽기 쉽게 만들고 네임스페이스 충돌을 관리하는 데 유용합니다.

  1. pacExercise.go를 수정하여 fmtio로 별칭 지정합니다:

    package main
    
    import io "fmt"
    import "propagandist"
    
    func main() {
        io.Println(propagandist.Shout) // Use the alias `io` instead of `fmt`
    }
    • import io "fmt"fmt 패키지에 대한 별칭 io를 생성합니다.
    • 이제 fmt.Println 대신 io.Println을 사용합니다.
  2. 프로그램을 실행합니다:

    go run pacExercise.go

Anonymous Import (익명 임포트)

익명 import 는 내보낸 식별자를 직접 참조할 필요 없이, init() 함수를 실행하는 것과 같은 부작용을 위해 패키지를 import 하는 데 사용됩니다. 이는 드라이버를 등록하거나 기타 초기화 작업을 수행하는 패키지에 유용합니다.

  1. pacExercise.go를 수정하여 time에 대한 익명 import 를 포함합니다:

    package main
    
    import (
        "fmt"
        "propagandist"
        _ "time" // Anonymous import
    )
    
    func main() {
        fmt.Println(propagandist.Shout)
    }
    • import _ "time"은 익명 import 입니다. 밑줄 _는 빈 식별자로 사용되어, 컴파일러에게 패키지를 부작용을 위해 import 하고 코드에서 직접적으로 패키지의 어떤 것도 참조하지 않을 것임을 알립니다.
    • 이 프로그램이 실행될 때 time 패키지의 init() 함수가 실행됩니다. time 패키지는 여기에서 특별한 부작용을 가지고 있지 않지만, 많은 패키지가 데이터베이스 드라이버 또는 구성 설정을 등록하기 위해 이것을 사용합니다.
  2. 프로그램을 실행합니다:

    go run pacExercise.go

    예상 출력:

    I Love LabEx

init() 함수 이해하기

init() 함수는 Go 에서 패키지 초기화에 중요한 역할을 하는 특별한 함수입니다. main 함수의 다른 코드가 실행되기 전에, 패키지가 import 될 때 Go 에 의해 자동으로 실행됩니다. 이 섹션에서는 init() 함수의 세부 사항과 Go 의 초기화 프로세스 내에서 작동 방식을 설명합니다.

init() 함수에 대한 주요 사항:

  1. 정의 및 목적:

    • init() 함수는 매개변수와 반환 값이 없습니다: func init() {}
    • 초기 상태 설정, 드라이버 등록 또는 필수 조건 검증과 같은 패키지 초기화 작업에 사용됩니다.
  2. 실행 순서:

    • Go 는 패키지가 여러 번 import 되더라도 패키지 초기화가 한 번만 발생하도록 보장합니다.
    • 초기화는 잘 정의된 순서를 따릅니다:
      1. 패키지 수준 변수가 먼저 초기화됩니다.
      2. 그런 다음 init() 함수가 실행됩니다.
      3. 마지막으로 main() 함수가 실행됩니다 (main 패키지에서만).
  3. 여러 init() 함수:

    • 단일 Go 파일은 여러 init() 함수를 포함할 수 있습니다.
    • 동일한 패키지의 여러 파일은 각각 자체 init() 함수를 가질 수 있습니다.
    • 이 모든 init() 함수는 실행되지만, 동일한 패키지 내의 순서는 보장되지 않습니다.
  4. 종속성 체인:

    • 패키지가 다른 패키지를 import 할 때, Go 는 종속성의 init() 함수가 먼저 실행되도록 보장합니다.
    • 이는 하향식 초기화 흐름을 생성합니다: 가장 깊은 종속성이 먼저 초기화됩니다.

init() 함수가 어떻게 작동하는지 보여주는 실용적인 예제를 만들어 보겠습니다:

  1. 먼저, propagandist 패키지를 수정하여 init() 함수를 포함해 보겠습니다. propagandist.go를 업데이트합니다:

    package propagandist
    
    import "fmt"
    
    var Shout = "I Love LabEx" // Public variable
    var secret = "I love the dress" // Private variable
    var initialized bool
    
    func init() {
        fmt.Println("Initializing propagandist package...")
        initialized = true
    }
    
    func Hit() string {
        return "Don't hit me, please!"
    }
    
    func IsInitialized() bool {
        return initialized
    }
  2. 이제, propagandist 패키지에 여러 init() 함수를 보여주기 위해 다른 파일을 만들어 보겠습니다:

    touch ~/project/propagandist/second.go

    파일에 다음 내용을 추가합니다:

    package propagandist
    
    import "fmt"
    
    func init() {
        fmt.Println("Second init function in propagandist package...")
    }
  3. 초기화 순서를 보여주기 위해 새로운 helper 패키지를 생성합니다:

    mkdir -p ~/project/helper
    touch ~/project/helper/helper.go

    파일에 다음 내용을 추가합니다:

    package helper
    
    import "fmt"
    
    var Message = "Helper package is ready"
    
    func init() {
        fmt.Println("Initializing helper package...")
    }
    
    func GetMessage() string {
        return Message
    }
  4. helper 패키지에 대한 모듈 파일을 추가합니다:

    cd ~/project/helper
    go mod init helper
  5. pacExercise.go를 업데이트하여 두 패키지를 모두 사용하고 초기화 순서를 보여줍니다:

    package main
    
    import (
        "fmt"
        "helper"
        "propagandist"
    )
    
    func init() {
        fmt.Println("Initializing main package...")
    }
    
    func main() {
        fmt.Println("Main function is running")
        fmt.Println(propagandist.Shout)
        fmt.Println(helper.Message)
        fmt.Printf("Propagandist initialized: %v\n", propagandist.IsInitialized())
    }
  6. main 프로젝트의 go.mod 파일을 업데이트하여 로컬 helper 패키지를 포함합니다:

    cd ~/project
    echo "replace helper => ./helper" >> go.mod
    go mod tidy
  7. 프로그램을 실행하고 초기화 순서를 관찰합니다:

    go run pacExercise.go

    예상 출력 (두 propagandist init 함수의 정확한 순서는 다를 수 있습니다):

    Initializing helper package...
    Initializing propagandist package...
    Second init function in propagandist package...
    Initializing main package...
    Main function is running
    I Love LabEx
    Helper package is ready
    Propagandist initialized: true

이 출력은 Go 의 초기화 프로세스의 주요 측면을 보여줍니다:

  1. 종속 패키지는 이를 import 하는 패키지보다 먼저 초기화됩니다.
  2. 단일 패키지 내에서 모든 init() 함수가 실행됩니다 (순서는 보장되지 않음).
  3. main() 함수는 모든 패키지 초기화가 완료된 후에만 실행됩니다.
  4. 패키지 수준 변수는 모든 init() 함수가 실행되기 전에 초기화됩니다.

이 초기화 시퀀스는 Go 패키지를 설계할 때, 특히 종속성을 관리하거나 특정 순서로 발생해야 하는 설정 작업을 수행할 때 중요한 고려 사항입니다.

요약

이 랩에서 다음을 배웠습니다:

  1. 재사용 가능한 코드를 캡슐화하여 Go 에서 사용자 정의 패키지를 생성하고 정의하는 방법.
  2. 공개 (export) 식별자와 비공개 (unexport) 식별자의 차이점과 접근성에 미치는 영향.
  3. 각 사용 사례에 맞는 다양한 패키지 import 방법:
    • 단일 항목 import: 한 번에 하나의 패키지를 import 합니다.
    • 그룹 import: 더 나은 구성을 위해 단일 블록에서 여러 패키지를 import 합니다.
    • Dot import: 패키지 이름 접두사 없이 식별자를 직접 사용하는 패키지를 import 합니다. (주의해서 사용)
    • Alias import: 가독성을 높이거나 이름 충돌을 피하기 위해 import 된 패키지의 이름을 변경합니다.
    • Anonymous import: 초기화와 같은 부작용만을 위해 패키지를 import 합니다.
  4. 패키지에서 init() 함수의 역할과 익명 import 가 실행을 트리거하는 방법.
  5. Go 의 초기화 프로세스에 대한 자세한 작동 방식, 다음을 포함합니다:
    • init() 함수 전에 패키지 수준 변수가 초기화되는 방법
    • 종속 패키지 간의 init() 함수의 보장된 실행 순서
    • 패키지 내에서 여러 init() 함수가 작동하는 방식
    • 종속 패키지에서 main 함수까지의 완전한 초기화 흐름

이 랩을 완료함으로써, 이제 패키지를 효과적으로 사용하여 Go 프로젝트를 구조화하고 관리할 수 있습니다. 재사용 가능한 모듈을 생성하고, 식별자에 대한 접근을 제어하며, 코드를 더 잘 구성하고, 초기화 프로세스를 이해하여 더 유지 관리 가능하고 확장 가능한 Go 애플리케이션을 만들 수 있습니다.