Goのプロジェクト構成の基本
Goのプロジェクトをどの様なファイル構成で配置すれば良いか読み物が少ないという指摘を見たのでまとめてみようと思う。
GOPATHについて
Go1.16がリリースされたことでGo-Moduleによるプロジェクト構成が標準で推奨されることになりました。(Go1.11までさかのぼってGo-Moduleは使える様になってます)
Go-Moduleモードでは「GOPATH配下にプロジェクトを置かなければならない」という制約からは解放されています。なので、実質GOPATHはどこを指していても構わないし設定されていなくても「ユーザーホーム/go」というデフォルトの場所が決まっているので開発できます。
おすすめの環境変数設定は以下の2つだけ。
- 「GOPATH=~/.go」(WindowsはGOPATH=%USERPROFILE%\.go)
- 「PATH=$GOPATH/bin:$PATH」(WindowsはPATH=%GOPATH%\bin;%PATH%)
モジュールとパッケージとは
- モジュール=パッケージを一つまたは複数のサブパッケージを取りまとめたカタマリ。
- パッケージ=フォルダ単位で単一ファイルまたは複数ファイルのカタマリ。
- サブパッケージ=サブフォルダにおくだけで扱いはパッケージと同等。
GOPATHモードの時にはモジュールという概念はなかったんだけど、パッケージとそのサブパッケージをまとめて「モジュール」として扱う概念が追加されたということ。
あるフォルダにてモジュールだと宣言するとそのフォルダ以下がモジュールになります。
宣言方法は以下の通り
> go mod init <モジュール名>
<モジュール名>
はgithub.com/nobonobo/moduleA
というような表記になります。
(モジュールを公開しない場合はmoduleA
という名前でも良い)
こうするとカレントフォルダにgo.modファイルが作成され、このカレントフォルダ以下がまるっと「モジュール」の扱いになります。
パッケージ名とはパッケージに置かれるGoコードの先頭に宣言される名称で、これは極力そのパッケージのフォルダ名と合致させることが推奨されます。モジュール名も、最後の部分パス名(前述の例であればmoduleA
)とモジュールのフォルダ名は合致している方が良いと思います。
ファイル構成例:GitHub公開版
- project1/ :プロジェクトルート=モジュールルート=リポジトリルート
- subpkg1/
- sub1.go
- subpkg2/
- sub2.go
- app.go
- go.mod
- subpkg1/
module github.com/nobonobo/project1
go 1.16
package subpkg1
package subpkg2
以上のような構成の場合、
subpkg1やsubpkg2は"<モジュール名>/subpkg1"
や"<モジュール名>/subpkg2"
という記述でインポートできます。
package main
import (
"github.com/nobonobo/project1/subpkg1"
"github.com/nobonobo/project1/subpkg2"
)
ファイル構成例:非公開版
ルールは何も変わらない。
- project1/ :プロジェクトルート=モジュールルート=リポジトリルート
- subpkg1/
- sub1.go
- subpkg2/
- sub2.go
- app.go
- go.mod
- subpkg1/
module project1
go 1.16
package subpkg1
package subpkg2
package main
import (
"project1/subpkg1"
"project1/subpkg2"
)
複雑な事例
モジュール配下にサブモジュールを置いた場合などもっと複雑な事例はあるけれど、あまり有効なものとは思えないです。基本は「リポジトリ1つ」に「モジュール1つ」です。
相対パスインポートについて
以下の様に相対パスによるインポートはGOPATHモードで利用できていました。
import "./subpkg1"
もともと非推奨だったのですが、Go-Moduleモードでは禁止されエラーになる様になりました。
なので、「相対パスインポート」を見たら「古いコードだな」と思ってあまり参考にしない様にしてください。
マルチバージョン標準の方法
GOPATHは以前はプロジェクトソース置き場としての役割もありましたが、Go-ModuleモードでのGOPATHは依存コードとコンパイル済みキャッシュ、バイナリ置き場としての役割に徹します。
> go get golang.org/dl/go1.15.8
go: downloading golang.org/dl v0.0.0-20210220033039-562909534da3
go: found golang.org/dl/go1.15.8 in golang.org/dl v0.0.0-20210220033039-562909534da3
> go1.15.8 download
Downloaded 0.0% ( 15247 / 122353388 bytes) ...
Downloaded 6.9% ( 8421376 / 122353388 bytes) ...
Downloaded 27.4% ( 33570816 / 122353388 bytes) ...
Downloaded 53.1% ( 65011712 / 122353388 bytes) ...
Downloaded 68.6% ( 83902464 / 122353388 bytes) ...
Downloaded 100.0% (122353388 / 122353388 bytes)
Unpacking /Users/nobo/sdk/go1.15.8/go1.15.8.darwin-amd64.tar.gz ...
Success. You may now run 'go1.15.8'
以上の操作でgo1.15.8
というコマンドでそのバージョンのGoを利用できる様になります。
このとき、どのGoのバージョンを使う場合でもGOPATHの場所を変えていなければ、依存コードキャッシュの効果は最大限発揮されます。gvmやgoenv等はGoバージョンごとにGOPATHを切り替えてしまうので依存はダウンロードし直しになってしまいますし、エディタのLanguageServer(コード補完などの支援)にGOPATHの変更を再設定しなければなりません。
まとめ
- 結構古い読み物を参考にGOPATH配下にプロジェクトを置かなければという誤解が後を絶たない。
- Go-Moduleモードを基本に使っていこう。
- 初心者は環境変数いじらなくてもいいと思う。
- Go-ModuleモードではGOPATHは依存キャッシュとして使われるだけになった。
- gvmやgoenvなどは使わない方がいいよ(DX悪化しちゃう。特にエディタ連携が辛くなる)
- 相対パスインポートを見たらそのコードは古い可能性が高いので参考にしない様に。
- 標準のGoのマルチバージョン利用方法があるよ!
Discussion
有用な記事をありがとうございます。
一度マルチモジュール化してしまってからここにたどり着く人へ。
サブモジュールにおいてしまったgo.modを削除しましょう。
これをやらないとここに書いたとおりにならないです!