Go CLI のディレクトリ構成(cmd/ と internal/、pkg/ は置かない)
一言まとめ
単一バイナリでも `cmd/<name>/main.go` にして 2 本目の追加コストをゼロにする。実装は既定で `internal/` に置き、公開したい理由があるパッケージだけ外に出す。`pkg/` は意味を持たないディレクトリ階層なので作らない。
目次 5 sections
調査時点: 2026-04。公開 OSS の mask-pipe(github.com/c12o-dev/mask-pipe)の初期レイアウト決定から。
採用した構成
mask-pipe/├── cmd/│ └── mask-pipe/│ └── main.go # エントリポイント、フラグ解析、配線├── patterns/ # 外から見つけてほしいドメインのパッケージ(トップレベル)│ ├── pattern.go│ ├── aws_access_key.go # 1 パターン 1 ファイル│ └── patterns_test.go├── internal/│ └── filter/ # 実装の中核│ ├── filter.go│ └── filter_test.go├── go.mod└── go.sum規則
- 今は 1 バイナリでも
cmd/<name>/を使う。 後からベンチマーク用などの 2 本目を足すときにリファクタが要らない。コストはgo build .がgo build ./cmd/<name>になるだけ。 - 既定は
internal/。 コンパイラが外部からの import を拒否するので、実装詳細への偶発的な依存を防げる。公開する具体的な理由があるパッケージだけ外に出す。 - トップレベルに置くパッケージは、仕様書やドキュメントが参照するパスに合わせる。 読んだ人がそこに見つけられることを優先する。ただしトップレベルに置くと API の安定を暗に約束して見えるので、
doc.goに「v1 では安定 API ではない」と明記する。後で公開ライブラリとして出すときは import パスを変えられない(変えるならメジャーバージョンアップ)点は織り込んでおく。 pkg/は作らない。 意味を持たないディレクトリ階層が 1 段増えるだけで、Go チームや Dave Cheney も推奨していない。
採らなかった構成
- フラット(ルートに
main.goと兄弟パッケージ): 最初の数百行までは最も簡単だが、2 本目のバイナリや内部の隔離を始めた瞬間に rename が要る。最終形を最初に選ぶコストはゼロ。 cmd/+pkg/+internal/(Kubernetes 流):pkg/の階層に意味がない。- ドメインのパッケージも
internal/に入れる: API の約束という点では安全だが、ドキュメントが参照するパスと食い違う。
gh / goreleaser / hugo など評判のよい Go CLI と同じ慣習なので、Go 経験者のオンボーディングコストが下がる。
関連
更新履歴
- 2026-04-16: 初版(mask-pipe ADR-0003)
- 2026-10-17: Wiki へ移植、概念ページとして再構成