mcpField
G
go-packages

go-packages

github.com/cxuu開発1861.2k 回インストール19 週間前に更新
リポジトリ

go-packages について

Go のパッケージやインポートの整理、コマンドラインプログラムの構成、初期化パターンを扱う開発スキルです。Go プロジェクトの構造や API 境界を検討する開発者向けです。

主な機能

  1. 01パッケージ名と責務の指針
  2. 02インポートのグループ化と別名
  3. 03init() の使用を抑える助言
  4. 04main 関数とエラー処理のパターン
  5. 05コマンドラインフラグ設計の指針

活用シーン

  1. 01Go プロジェクトのパッケージ境界の検討
  2. 02標準ライブラリと外部インポートの整理
  3. 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() を避けてください。避けられない場合は、次の条件を満たす必要があります。

  1. 完全に決定的である
  2. 他の init() の実行順序に依存しない
  3. 環境の状態(環境変数、作業ディレクトリ、引数)に依存しない
  4. 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 を参照してください。

コメント