Learning Go Chapter 9. Modules, Packages, and Imports
モジュール、パッケージ、インポート
リポジトリとモジュール、パッケージ
Go のライブラリ管理は3つの概念から成り立つ
- リポジトリ: github のようなバージョン管理システムにおけるプロジェクト
- モジュール: リポジトリに格納された Go ライブラリやアプリケーション
- パッケージ: モジュールを構成するコード群
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ファイルには、同一のパッケージ句が必要
- パッケージの名前がインポートパスではなく、パッケージ句によって決定される
- 原則として、パッケージの名前は、パッケージを含むディレクトリの名前と一致させる必要がある
- ただし、パッケージにディレクトリとは異なる名前を使用する場合がいくつかある
パッケージの命名
util
みたいなパッケージ名はやめよう
モジュールを構成する方法
- モジュールが小さい場合一つのモジュールのみで構成しよう
- モジュールが1つ以上のアプリケーションで構成されている場合は、モジュールのルートに
cmd
というディレクトリを作成するcmd
内で、モジュールから構築されたバイナリごとに1つのディレクトリを作成する
- モジュールのルートディレクトリに、プロジェクトのテストとデプロイを管理するための多くのファイル(シェルスクリプト、継続的インテグレーション構成ファイル、Dockerfilesなど)が含まれている場合は、すべてのGoコード(cmdの下のメインパッケージを除く)をpkgというディレクトリに入れる
- 機能のスライスごとにコードを整理する
- 例
- 顧客管理をサポートするすべてのコードを1つのパッケージに配置
- 在庫を管理するすべてのコードを別のパッケージに配置
- 参考: GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps
- 例
パッケージ名のオーバライド
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、モジュールの依存関係、およびモジュールに依存するオープンソースプロジェクトを公開