go-packages について
Go のパッケージやインポートの整理、コマンドラインプログラムの構成、初期化パターンを扱う開発スキルです。Go プロジェクトの構造や API 境界を検討する開発者向けです。
主な機能
- 01パッケージ名と責務の指針
- 02インポートのグループ化と別名
- 03init() の使用を抑える助言
- 04main 関数とエラー処理のパターン
- 05コマンドラインフラグ設計の指針
活用シーン
- 01Go プロジェクトのパッケージ境界の検討
- 02標準ライブラリと外部インポートの整理
- 03テスト可能なコマンドラインプログラムの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-packagesインストール後、Claude Code で/go-packagesを入力して読み込まれたか確認します。
同梱ファイルreferences/IMPORTS.mdreferences/PACKAGE-SIZE.md
詳細
Go のパッケージとインポート
リソースの振り分け
references/IMPORTS.md— インポートのグループ化、ブランクインポート、ドットインポート、インポートエイリアスを使用する場合に参照してください。references/PACKAGE-SIZE.md— パッケージの分割、initの回避、mainの構成、CLI フラグ/サブコマンドの設計を行う場合に参照してください。
このスキルが適用されない場合:パッケージ内の個々の識別子の命名については、go-naming を参照してください。単一ファイル内の関数の整理については、go-functions を参照してください。インポート規則を強制するリンターの設定については、go-linting を参照してください。
パッケージの構成
util パッケージを避ける
パッケージ名は、そのパッケージが提供するものを表す必要があります。
util、helper、common のような汎用的な名前は、意味を曖昧にし、インポートの競合を引き起こすため避けてください。
// Good: Meaningful package names
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
// Bad: Vague names obscure meaning
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
汎用的な名前を名前の一部として使うことはできます(例:stringutil)が、パッケージ名全体にはしないでください。
パッケージの規模
| 質問 | 対応 |
|---|---|
| 目的を一文で説明できますか? | いいえ → 責任ごとに分割する |
| ファイル間で非公開シンボルを共有していませんか? | それらのファイルは別々のパッケージにできる可能性があります |
| 異なるユーザーグループが異なる部分を使っていますか? | ユーザーの境界に沿って分割する |
| Godoc ページが情報過多ですか? | 見つけやすさを高めるために分割する |
ファイルが長いという理由だけで分割したり、単一型のパッケージを作ったり、循環依存が生じる場合は、分割しないでください。
インポート
インポートは空行で区切ったグループに整理します。標準ライブラリのパッケージを必ず最初に配置してください。 goimports を使うと、これを自動で管理できます。
import (
"fmt"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
簡単なルール:
| ルール | 指針 |
|---|---|
| グループ化 | 標準ライブラリを最初にし、その後に外部パッケージを配置します。拡張形:標準ライブラリ → その他 → proto → 副作用 |
| 名前の変更 | 競合する場合を除き避けてください。最もローカルなインポート名を変更します。Proto パッケージには pb サフィックスを付けます |
ブランクインポート(import _) | main パッケージまたはテストでのみ使用します |
ドットインポート(import .) | 循環依存のテストファイルを除き、使用しないでください |
init() を避ける
可能な限り init() を避けてください。避けられない場合は、次の条件を満たす必要があります。
- 完全に決定的である
- 他の
init()の実行順序に依存しない - 環境の状態(環境変数、作業ディレクトリ、引数)に依存しない
- I/O(ファイルシステム、ネットワーク、システムコール)を行わない
許容される用途:単一の代入式では表現できない複雑な式、プラグイン可能なフック(例:database/sql の方言)、決定的な事前計算。
Main での終了処理
os.Exit または log.Fatal* を呼び出すのは、main() 内だけにしてください。それ以外の関数はエラーを返す必要があります。
理由:制御フローが分かりにくくなり、テストできず、defer 文がスキップされます。
ベストプラクティス:run() パターンを使います。ロジックを func run() error に切り出し、終了箇所を一つにして main() から呼び出します。
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
コマンドラインフラグ
推奨事項:フラグは
package mainでのみ定義してください。
- フラグ名には
snake_caseを使います。--outputDirではなく--output_dirとします。 - ライブラリはフラグを直接読み取らず、パラメーターとして設定を受け取るようにしてください。これによりテストや再利用が容易になります。
- 標準の
flagパッケージを優先してください。POSIX の規則(二重ダッシュ、1 文字のショートカット)が必要な場合に限り、pflagを使います。
// Good: Flag in main, passed as parameter to library
func main() {
outputDir := flag.String("output_dir", ".", "directory for output files")
flag.Parse()
if err := mylib.Generate(*outputDir); err != nil {
log.Fatal(err)
}
}
関連スキル
- パッケージの命名:パッケージ名の選択、名前の重複の回避、エクスポートされるシンボルの命名については、go-naming を参照してください。
- パッケージ間のエラー処理:パッケージ境界で
%wと%vのどちらを使ってエラーをラップするかについては、go-error-handling を参照してください。 - インポートのリンティング:goimports のローカルプレフィックスの設定やインポートグループの強制については、go-linting を参照してください。
- グローバル状態:
init()を明示的な初期化に置き換える方法や、変更可能なグローバル変数を避ける方法については、go-defensive を参照してください。