最近の記事

git worktree

ポイント

  • gitは相対パス利用 : Git 2.48+の–relative-paths/worktree.useRelativePaths
    • ホストとDevContainerそれぞれでgit操作をするため
  • WorkTreeは/.worktrees/ 以下に作成
    • /をDev Containerにマウントするため、よく見る同階層に配置する方式がとれない

ディレクトリ構成はこのようになる

<repository name>/
 ├─ .claude/
 ├─ .devcontainer/
 ├─ .git/
 ├─ src/
 └─ .worktrees/
     ├─ feature-example/
     └─ bugfix-example/

事前作業

worktree.useRelativePathsを有効化

git config worktree.useRelativePaths true

# useRelativePaths = true があるはず
cat .git/config

.worktrees/ を.gitignoreにいれておく。

ワークツリーの作成

新規のブランチを切る場合

git worktree add -b feature/example .worktrees/feature-example origin/main

すでにあるブランチを使う場合

git worktree add .worktrees/feature-example feature/example

Claude Codeの起動

作成したワークツリーまで移動した後でClaude Codeを立ち上げる。

cd .worktrees/feature-example
claude

ワークツリーの削除

git worktree remove .worktrees/feature-example

Policies

Dev Container

Why Dev Container?

  • AIを隔離してセキュリティを維持(権限最小化)
    • AIが閲覧できるのはマウントしたディレクトリだけ
    • 利用できるツールやネットワークも限定
    • GithubやAWSなどの権限もAI専用に
  • 環境統一が容易
    • 設定ファイルをリポジトリに保存しておけばワンクリックで環境再現
      • 仮想環境だとこうはいかない
    • たとえWindows/Mac混在チームでもDevContainerなら対応可能
    • CI/CD(Linux)との環境統一も視野に入れられる
  • エディタ連携もある
    • Visual Studio Codeとの連携も便利
  • VS ホスト起動
    • AIがユーザーの権限を利用できてしまう。これは通常広すぎるし強すぎる
      • e.g., 機密情報を含んだファイルが閲覧できる権限、データを消せる権限、AWSの重要リソースを停止・破壊できる権限など
    • チーム間の環境統一も手間がかかる
      • ホストのOS自体WindowsだったりMacだったり
      • CI/CDとの連携も加えるとさらに難儀に
    • ホスト環境も汚れるしセットアップも面倒

Setup

設定ファイルの保存場所

  • <Gitのルートリポジトリ>/.devcontainer/

単一コンテナ VS docker compose

  • 開発のためミドルウェアなどの別コンテナが必要ならdocker-compose
  • それ以外は単一コンテナで十分(YAGNIの精神)
    • あとからdocker-compose対応にするのもAIを使えばさほど手間ではない

バージョン指定

  • 環境統一のため、ベースイメージや主要ツールはバージョンを明記する
    • 原則フルバージョン(X.Y.Z)を記載する
    • マイナーバージョンまで(X.Y)でも可
    • 固定ではなく指定。バージョンアップは積極的に
    • latest運用は避ける

ベースイメージ管理

  • ビルド済イメージを活用
    • ゴールデンイメージを作って主要部分は確実に共通化
    • 開発用とCI/CD用の違いはそれぞれのDockerfileで吸収
    • 管理の手間がかかるため、これ自体も自動化
      • 定期的な自動ビルド
      • 脆弱性スキャン

セキュリティ維持の取り組み

  • AIエージェントは100%安全なシステムではない
    • prompt injection、クラウドAIなら運営側の事故による漏洩などなど
    • リスクマネジメントが必要
  • 権限付与の基本は短命トークン + 最小権限
    • ExpireなしのAPIキーは原則として利用させない
      • 強すぎる権限を持つAPIキーは決して利用させない
    • AWSならIdentity Center + 妥当な範囲の権限 + Service control policies (SCPs)
      • 妥当な範囲の権限 ≒ 機密情報を含まない範囲の読取権限 + 最小限の更新権限
      • 完璧主義で最小化を目指すと実用性が損なわれるため、「妥当な」権限縮小を
      • 重要リソースの更新など責任を伴う業務は原則として人間を絡めることを推奨(ヒューマンインザループ)
    • GitHubのghはExpireを短めにしたFine-grained PATで妥協(最短のExpireでも長いので期待しすぎないこと)
      • 権限管理の仕組みが今のところAWSほどうまくかみ合わせられない
      • 読み取り禁止設定もやらないより良い
      • これでは利用できないほど重要なリポジトリでは、DevContainer + AIでリポジトリごと預けるのを潔く避ける
  • リポジトリ内の設定ファイル(e.g., .envなど)に機密情報を平文で保存しておかない
    • たとえ動作設定でDenyしても閲覧されるリスクがゼロにはならない
    • SSM Parameter Storeなど外部に出す
    • リポジトリへの保存がどうしても必要なら暗号化する
      • ただし、この場合AIが復号できてしまわないようにする
      • 復号した後のファイルを放置してうっかりAIに読まれてしまわないようにもする
      • 環境変数で受け渡す方式はリポジトリに保存しないで済むが、AIエージェント相手の隠ぺいとしては不徹底
        • 普通にアクセスできてしまう
    • そもそも論、AIが関係なくともリポジトリに平文で機密情報を保存すべきでない

Claude Codeの動作設定

  • 権限設定
    • まず危険な内容をDeny
      • 機密情報を含むファイルの閲覧(e.g., .env, ~/.aws/credentials)
      • 危険なコマンドの利用(e.g., sudorm -rf)
    • 次に効率優先でよい内容をAllow
      • コードを保存したディレクトリ(e.g., src/)でのファイル編集
    • それ以外は手動確認
  • ネットワーク設定
    • 通常必要なドメインは許可
      • Claude、GitHubなど
    • 任意ドメインは禁止
      • アタッカーのドメインにつなげられてしまうので
      • もし任意ドメインの調査がしたいなら、リポジトリに紐づいたDev Containerを使わないで別のコンテナを使う
        • コンテキストが欲しいならそこだけ複製する

Policies

仕様に必要のない変数や関数を追加しない

Contents

Do not add variables or functions that the specification does not require (YAGNI principle). (仕様に必要のない変数や関数を追加しない(YAGNI原則))

解説

YAGNI(You Aren’t Gonna Need It)原則は、現時点で必要のない機能を実装しないという考え方です。将来必要になるかもしれないと予測して実装した機能の多くは、実際には使われず、コードの複雑性を増すだけです。未使用のコードはメンテナンスコストを増加させ、バグの温床となります。仕様に明記された要件のみを実装することで、シンプルで保守しやすいコードベースを維持できます。

具体例

// 悪い例(仕様にない機能を実装)
type User struct {
    Name  string
    Email string
    Age   int           // 仕様に不要
    Phone string        // 仕様に不要
}

func (u *User) GetFullProfile() map[string]interface{} {
    // 仕様に不要な複雑な処理
    return map[string]interface{}{
        "name":  u.Name,
        "email": u.Email,
        "age":   u.Age,
        "phone": u.Phone,
    }
}

// 良い例(仕様に必要な要素のみ)
type User struct {
    Name  string
    Email string
}

func (u *User) GetEmail() string {
    return u.Email
}

参考リンク

core coding-standard

関数名は動詞または動詞+目的語で命名する

Contents

Name functions with a verb or verb + object. (関数名は動詞または動詞+目的語で命名する)

解説

関数は動作を表すため、動詞で始まる名前が自然で理解しやすくなります。動詞+目的語の形式は、関数が何を行うかを明確に伝え、コードの可読性を向上させます。名詞だけの関数名は、その関数が何をするのか不明確であり、混乱を招きます。一貫した命名規則に従うことで、チーム全体でのコードの理解が容易になります。

具体例

// 悪い例(名詞のみ、または不明瞭)
func User(id string) User {
    // Userは名詞のみで、何をする関数か不明
}

func Data() []byte {
    // 何のデータを取得するのか不明
}

func Process() {
    // 何を処理するのか不明
}

func Order(items []Item) {
    // 注文を作成?取得?
}

// 良い例(動詞+目的語)
func GetUser(id string) User {
    // ユーザーを取得することが明確
}

func FetchUserData() []byte {
    // ユーザーデータを取得することが明確
}

func ProcessOrder(order Order) error {
    // 注文を処理することが明確
}

func CreateOrder(items []Item) (Order, error) {
    // 注文を作成することが明確
}

func ValidateEmail(email string) bool {
    // メールアドレスを検証することが明確
}

func CalculateTotalPrice(items []Item) float64 {
    // 合計金額を計算することが明確
}

func SendNotification(user User, message string) error {
    // 通知を送信することが明確
}

func UpdateUserProfile(userID string, profile Profile) error {
    // ユーザープロフィールを更新することが明確
}

func DeleteExpiredSessions() error {
    // 期限切れセッションを削除することが明確
}

参考リンク

core coding-standard

引数と戻り値にはできるだけValue Objectを使う

Contents

Use Value Objects for both arguments and return values when possible. (可能な限り、引数と戻り値の両方にValue Objectを使用する)

解説

関数のシグネチャでValue Objectを使用することで、型安全性が向上し、無効な値の受け渡しを防げます。プリミティブ型では、引数の順序を間違えたり、無効な値を渡したりするミスが発生しやすくなります。Value Objectを使用することで、コンパイル時に型チェックが働き、ドメインルール違反を早期に検出できます。これにより、実行時エラーではなくコンパイルエラーとして問題を発見できます。

具体例

// 悪い例(プリミティブ型の引数)
func TransferMoney(fromAccount, toAccount string, amount float64) error {
    // 引数の順序ミスが起きやすい
    // 負の金額が渡される可能性がある
    if amount <= 0 {
        return errors.New("invalid amount")
    }
    // ...
}

func main() {
    // 引数の順序を間違える可能性
    TransferMoney("ACC002", "ACC001", 100.0)
    // 負の値を渡せてしまう
    TransferMoney("ACC001", "ACC002", -50.0)
}

// 良い例(Value Objectの引数と戻り値)
type AccountID struct {
    value string
}

func NewAccountID(id string) (AccountID, error) {
    if id == "" || !strings.HasPrefix(id, "ACC") {
        return AccountID{}, errors.New("invalid account ID")
    }
    return AccountID{value: id}, nil
}

type Amount struct {
    value float64
}

func NewAmount(amount float64) (Amount, error) {
    if amount <= 0 {
        return Amount{}, errors.New("amount must be positive")
    }
    return Amount{value: amount}, nil
}

func (a Amount) Value() float64 {
    return a.value
}

type TransferResult struct {
    TransactionID string
    CompletedAt   time.Time
}

func TransferMoney(from, to AccountID, amount Amount) (TransferResult, error) {
    // Value Objectは既に検証済み
    // 型が異なるため引数の順序ミスも防げる
    result := TransferResult{
        TransactionID: generateID(),
        CompletedAt:   time.Now(),
    }
    return result, nil
}

func main() {
    from, _ := NewAccountID("ACC001")
    to, _ := NewAccountID("ACC002")
    amount, _ := NewAmount(100.0)

    // 型安全性により、引数の順序ミスはコンパイルエラーになる
    result, err := TransferMoney(from, to, amount)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Transfer completed: %s\n", result.TransactionID)
}

参考リンク

core coding-standard

重要なドメイン概念にはValue Objectを活用する

Contents

Actively use Value Objects for variables representing important domain concepts. (重要なドメイン概念を表す変数には、Value Objectを積極的に使用する)

解説

プリミティブ型(文字列、数値など)だけでドメイン概念を表現すると、型安全性が失われ、無効な値の混入リスクが高まります。Value Objectを使用することで、ドメインルールをカプセル化し、型システムの力でバグを防止できます。重要なドメイン概念(メールアドレス、金額、ユーザーIDなど)をValue Objectとして定義することで、コードの表現力と安全性が向上します。

具体例

// 悪い例(プリミティブ型のみ使用)
type User struct {
    ID    string
    Email string
}

func CreateUser(id, email string) (*User, error) {
    // バリデーションが散在
    if id == "" || email == "" {
        return nil, errors.New("invalid input")
    }
    return &User{ID: id, Email: email}, nil
}

func SendEmail(email string) error {
    // emailの妥当性チェックが毎回必要
    if !strings.Contains(email, "@") {
        return errors.New("invalid email")
    }
    return emailClient.Send(email, "Hello")
}

// 良い例(Value Object使用)
type UserID struct {
    value string
}

func NewUserID(id string) (UserID, error) {
    if id == "" {
        return UserID{}, errors.New("user ID cannot be empty")
    }
    return UserID{value: id}, nil
}

func (u UserID) String() string {
    return u.value
}

type Email struct {
    value string
}

func NewEmail(email string) (Email, error) {
    if !strings.Contains(email, "@") {
        return Email{}, errors.New("invalid email format")
    }
    return Email{value: email}, nil
}

func (e Email) String() string {
    return e.value
}

type User struct {
    ID    UserID
    Email Email
}

func CreateUser(id UserID, email Email) *User {
    // Value Objectは既に検証済みなので追加チェック不要
    return &User{ID: id, Email: email}
}

func SendEmail(email Email) error {
    // Emailは既に妥当性が保証されている
    return emailClient.Send(email.String(), "Hello")
}

参考リンク

core coding-standard

構造体のフィールドは非公開にしコンストラクタで初期化する

Contents

Struct fields should be unexported (lowercase) and initialized only via a constructor function. (構造体のフィールドは非公開(小文字)にし、コンストラクタ関数経由でのみ初期化する)

解説

構造体のフィールドを非公開にすることで、不正な状態での初期化を防ぎ、データの整合性を保証できます。コンストラクタ関数を使用することで、必須フィールドの初期化漏れや不正な値の設定を防止し、常に有効な状態のオブジェクトを作成できます。これは、カプセル化の原則に基づく重要な設計パターンであり、バグの混入を防ぎます。

具体例

// 悪い例(フィールドが公開されている)
type User struct {
    Name  string
    Email string
    Age   int
}

func main() {
    user := User{Name: "John"}  // Emailが空で不正な状態
    // ...
}

// 良い例(フィールドが非公開、コンストラクタ使用)
type User struct {
    name  string
    email string
    age   int
}

func NewUser(name, email string, age int) (*User, error) {
    if name == "" {
        return nil, errors.New("name is required")
    }
    if !strings.Contains(email, "@") {
        return nil, errors.New("invalid email")
    }
    if age < 0 {
        return nil, errors.New("age must be positive")
    }

    return &User{
        name:  name,
        email: email,
        age:   age,
    }, nil
}

func (u *User) Name() string {
    return u.name
}

func (u *User) Email() string {
    return u.email
}

参考リンク

core coding-standard

すべての関数にユニットテストを提供する

Contents

Provide unit tests for every function, and ensure all tests pass. (すべての関数にユニットテストを提供し、すべてのテストが合格することを保証する)

解説

ユニットテストは、コードの品質を保証し、リファクタリング時の安全性を提供する重要な要素です。すべての関数にテストがあることで、変更時に既存機能が破壊されていないことを迅速に検証できます。テストが存在しないコードは、変更のリスクが高く、技術的負債となります。MUST項目として、例外なくすべての関数にテストを記述し、継続的に実行して合格状態を維持する必要があります。

具体例

// 実装
func Add(a, b int) int {
    return a + b
}

func ValidateEmail(email string) bool {
    return strings.Contains(email, "@")
}

// テスト
func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

func TestValidateEmail(t *testing.T) {
    if !ValidateEmail("test@example.com") {
        t.Error("Valid email should pass")
    }
    if ValidateEmail("invalid") {
        t.Error("Invalid email should fail")
    }
}

参考リンク

core coding-standard

目的が異なる場合は同じ処理でも関数を分ける

Contents

Even if two functions perform the same process, separate them when their purposes differ. (2つの関数が同じ処理を実行する場合でも、目的が異なる場合は分離する)

解説

同じ処理内容であっても、目的やコンテキストが異なる場合は別々の関数として定義すべきです。これにより、将来的に要件が変わり処理内容が分岐した際に、影響範囲を限定できます。共通化を優先しすぎると、異なる目的の処理が密結合になり、一方の変更が他方に意図しない影響を与えるリスクが生じます。目的に応じた関数分離は、変更に強い設計の基本です。

具体例

// 悪い例(目的が異なるのに共通化)
func calculateDiscount(price float64) float64 {
    return price * 0.9  // 10%割引
}

func processNewUserOrder(price float64) float64 {
    return calculateDiscount(price)  // 新規ユーザー割引
}

func processSeasonalSale(price float64) float64 {
    return calculateDiscount(price)  // シーズンセール割引
}

// 良い例(目的ごとに分離)
func calculateNewUserDiscount(price float64) float64 {
    return price * 0.9  // 新規ユーザー割引
}

func calculateSeasonalDiscount(price float64) float64 {
    return price * 0.9  // シーズンセール割引(将来変更可能性あり)
}

func processNewUserOrder(price float64) float64 {
    return calculateNewUserDiscount(price)
}

func processSeasonalSale(price float64) float64 {
    return calculateSeasonalDiscount(price)
}

参考リンク

core coding-standard

1つの関数には1つの責任のみを割り当てる

Contents

Assign one responsibility to one function. Do not pack multiple responsibilities into a single function. The same applies to classes (or “struct + methods” in Go) — follow the Single Responsibility Principle. (1つの関数には1つの責任のみを割り当てる。複数の責任を1つの関数に詰め込んではいけない。クラス(またはGoの「構造体+メソッド」)にも同様に単一責任原則を適用する)

解説

単一責任原則は、ソフトウェア設計の基本原則の一つです。1つの関数が複数の責任を持つと、変更の影響範囲が広がり、テストが困難になります。責任が明確に分離されていれば、変更時に影響を受ける箇所が限定され、バグの混入リスクが減少します。関数やクラスは「変更する理由が1つだけ」であるべきです。

具体例

// 悪い例
func processUser(id string) {
    user := fetchUserFromDB(id)
    validateUser(user)
    sendEmail(user.Email, "Welcome")
    logActivity("User processed")
}

// 良い例
func processUser(id string) User {
    user := fetchUserFromDB(id)
    validateUser(user)
    return user
}

func notifyUser(user User) {
    sendEmail(user.Email, "Welcome")
}

func logUserActivity(message string) {
    logActivity(message)
}

参考リンク

core coding-standard