Learning Go Chapter 9. Modules, Packages, and Imports

モジュール、パッケージ、インポート

リポジトリとモジュール、パッケージ

  • Go のライブラリ管理は3つの概念から成り立つ

  • Go のモジュールは、リポジトリ(とモジュール)のパスで一意に表される

    • 例えば、github.com/jonbodner/proteus

go.mod

  • ルートディレクトリに有効な go.mod ファイルがあるとモジュールになる
  • go mod init <GO MODULE PATH> でカレントディレクトリをルートディレクトリとしたモジュールとする go.mod ファイルを作成する

    • モジュールパスは大文字小文字を区別する
    • 混乱をさけるため通常は小文字のみを使用する
  • go.mod の例

    • module セクションでモジュールのユニークなパスを指定
    • go セクションで go の最小バージョンを指定
    • require セクションで依存するモジュールとその最小バージョンを指定
    • replace セクションで依存するモジュールのパスを上書きする
    • exclude セクションで依存するモジュールの特定バージョンが使用されないようにする
module github.com/htamakos/ch08

go 1.15

require github.com/pkg/errors v0.9.1 // indirect

インポートとエクスポート

  • Go では他の言語と同じようにパッケージを import できる
    • 公開された変数、定数、関数、型、インターフェースをインポートできる
  • Go で他パッケージから変数等を利用可能にするには?
    • 先頭文字を大文字にすると他パッケージに対して公開される
    • 小文字とか _ で始まる識別子は同一パッケージからのみしか使えない (export されない)

パッケージの作成とアクセス

  • Go でパッケージを作成するのは簡単
package_exmple
|- formatter
|      |__ formatter.go
|_ math
       |__ math.go
// math.go
package math

func Double(a int) int {
  return a * 2
}
// formatter.go
package print

import "fmt"

func Format(num int) string {
  return fmt.Sprintf("The number is %d", num)
}
  • main.go から使う
package main

import (
  github.com/package_exmple/formatter
  github.com/package_exmple/math
)

func main() {
  print.Format(math.Double(3))
}
  • ディレクトリ内のすべてのGoファイルには、同一のパッケージ句が必要
  • パッケージの名前がインポートパスではなく、パッケージ句によって決定される
  • 原則として、パッケージの名前は、パッケージを含むディレクトリの名前と一致させる必要がある
  • ただし、パッケージにディレクトリとは異なる名前を使用する場合がいくつかある
    • main パッケージ: アプリケーションのエントリーポイント
    • ディレクトリ名に Go 識別子で無効な名前が使用されている場合
    • ディレクトリを利用したバージョン管理のため

パッケージの命名

  • util みたいなパッケージ名はやめよう

モジュールを構成する方法

  • モジュールが小さい場合一つのモジュールのみで構成しよう
  • モジュールが1つ以上のアプリケーションで構成されている場合は、モジュールのルートにcmdというディレクトリを作成する
    • cmd内で、モジュールから構築されたバイナリごとに1つのディレクトリを作成する
  • モジュールのルートディレクトリに、プロジェクトのテストとデプロイを管理するための多くのファイル(シェルスクリプト継続的インテグレーション構成ファイル、Dockerfilesなど)が含まれている場合は、すべてのGoコード(cmdの下のメインパッケージを除く)をpkgというディレクトリに入れる
  • 機能のスライスごとにコードを整理する

パッケージ名のオーバライド

import (
  crand "cryipt/rand"
  "math/rand"
)
  • _ でパッケージ名の export を回避する
    • パッケージの init関数のみが実行される

パッケージコメントと godoc

  • パッケージレベルのコメント
// package level comment
package money
  • struct のコメント
// struct comment
type Money struct {
  Value decimal.Decimal
  Currency string
}
  • function のコメント
// function comment
// args
// return
func Hoge() {
}
  • go doc <パッケージパス> でパッケージのドキュメントを生成し、ブラウザで確認できる

internal パッケージ

  • モジュール内のパッケージ間で関数、型、または定数を共有したいが、それをAPIの一部にしたくない場合に internal パッケージを使う
  • internalというパッケージを作成すると、そのパッケージとそのサブパッケージにエクスポートされた識別子は、internalの直接の親パッケージとinternalの兄弟パッケージにのみアクセスできる

init 関数

  • 値を返さないinitという名前の関数を宣言すると、そのパッケージが別のパッケージによって最初に参照されたときに実行される
  • init関数には入力または出力がないため、パッケージレベルの関数および変数と相互作用して、副作用によってのみ機能する
  • のでできれば使うのは避けたほうが良い
    • ただし、データベースドライバなどの一部のパッケージは、init関数を使用してデータベースドライバを登録する

循環依存

  • Go ではパッケージ間で循環依存はできないようになっている

Gracefully Renaming and Reorganizing Your API

  • モジュールをリファクタしたい場合
    • エクスポートされた識別子の一部の名前を変更するか、モジュール内の別のパッケージに移動する
    • 過去にさかのぼる変更を避けるために、元の識別子を削除することを避ける

Minimum Version Selection

  • Go は依存するライブラリのバージョンが複数選択可能である場合、最小のバージョンの依存ライブラリをダウンロードする
    • これは 他の言語において 例えば npm のように依存ライブラリを複数ダウンロードする動作と異なる
    • これにより単一バイナリに組み込むライブラリの数が少なくなり、アプリケーションのバイナリサイズを減らすことができる

vendoring

  • 外部依存ライブラリを vendor ディレクトリいかにダウンロードする方法
    • これにより依存するライブラリのバージョンを固定化できる
    • ただし、Go Module と Proxy Server により支持されなくなった

pkg.go.dev

  • Goモジュールに関するドキュメントをまとめる単一のサービス
  • godocs、使用されているライセンス、README、モジュールの依存関係、およびモジュールに依存するオープンソースプロジェクトを公開

モジュールの公開

  • Goプログラムはソースコードからビルドされ、リポジトリパスを使用して自身を識別するため、Maven Centralやnpmの場合のように、モジュールを中央ライブラリリポジトリに明示的にアップロードする必要はない

www.amazon.co.jp