Apex実装のベストプラクティス（前編）

Apexは **ガバナ制限（Governor Limits）** が厳しく、効率の悪いコードを書くと **SOQLクエリの上限（1回のトランザクションで最大100回）** に達してしまい、エラーが発生します。  
ここでは、**ベストプラクティスとアンチパターン** を初心者にもわかるように、具体的なコード例を使って整理しました。

後編：

https://qiita.com/nmatsuo63/items/4aa4c28a46ba694e00ed

---

## **❌ 1. アンチパターン: ループ内のクエリ**
### **問題点**
- **ループ内でSOQLクエリを実行すると、短時間でクエリ制限（100回）に達してしまう。**
- **大量データ処理に向いていない（ガバナ制限違反）。**

### **❌ 悪い例（NGコード）**
```apex
public void updateAccountContacts() {
    List<Account> accounts = [SELECT Id, Name FROM Account]; // 1回目のクエリ

    for (Account acc : accounts) {
        List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // ❌ ループ内クエリ
        for (Contact con : contacts) {
            con.LastName = acc.Name + ' Customer';
        }
        update contacts; // DML操作もループ内にあるとさらに悪化
    }
}
```

### **💥 何が問題か？**
- 取引先（Account）の数だけ**SOQLが実行される**。（100件以上ならエラー）
- **非効率なDML（データ更新）** でガバナ制限に引っかかる。

### **✅ 良い例（修正コード）**
```apex
public void updateAccountContacts() {
    // 1回のクエリでアカウント情報を取得
    List<Account> accounts = [SELECT Id, Name FROM Account];

    // 取得したアカウントのIDをリスト化
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : accounts) {
        accountIds.add(acc.Id);
    }

    // 1回のクエリで対象のコンタクトを取得（親のIDでフィルタ）
    List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];

    // まとめてデータを更新
    for (Contact con : contacts) {
        con.LastName = 'Updated Customer';
    }
    update contacts; // 1回のDMLで処理
}
```

### **✨ 修正のポイント**
- **1回のSOQLで必要なデータをまとめて取得**
- **ループ内ではなく、1回のDMLで一括更新**
- **クエリ回数とDML回数を最小化し、ガバナ制限を回避**

---

## **✅ 2. 原則: コレクションの使用によるクエリ数の最小化**
**コレクション（SetやMap）を活用することで、クエリの回数を削減できる。**

### **✅ 良い例（Setを活用）**
```apex
public void updateContactsUsingCollections() {
    List<Account> accounts = [SELECT Id, Name FROM Account];
    
    // Setを使ってアカウントIDを一括管理
    Set<Id> accountIds = new Set<Id>();
    for (Account acc : accounts) {
        accountIds.add(acc.Id);
    }

    // アカウントIDを一括検索してコンタクトを取得
    List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountIds];

    // Mapを使ってアカウント名を取得
    Map<Id, String> accountNameMap = new Map<Id, String>();
    for (Account acc : accounts) {
        accountNameMap.put(acc.Id, acc.Name);
    }

    // 一括更新
    for (Contact con : contacts) {
        con.LastName = accountNameMap.get(con.AccountId) + ' Customer';
    }
    update contacts;
}
```

### **✨ 修正のポイント**
- **Setを使い、対象のIDリストを作成（重複なし）**
- **Mapを使い、関連情報（Account名）を取得**
- **最小限のSOQLで最大限のデータを取得し、効率化**

---

## **⚠️ 3. 注意: 親-子クエリのカウントは異なる**
### **親オブジェクトのレコード数 vs 子オブジェクトのレコード数**
```apex
List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account];
```
**この場合、クエリの回数は1回だが、実際に取得するレコード数は「親の件数×子の件数」になる。**

---

## **✅ 4. 原則: 必要なものを単一のクエリで取得**
**1回のSOQLで、できるだけ多くの情報を取得することで、クエリの回数を減らす。**

### **❌ 悪い例（複数回のクエリ）**
```apex
public void processAccounts() {
    List<Account> accounts = [SELECT Id FROM Account];

    for (Account acc : accounts) {
        List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :acc.Id]; // ❌ ループ内クエリ
        System.debug('Contacts: ' + contacts.size());
    }
}
```

### **✅ 良い例（1回のクエリで済ませる）**
```apex
public void processAccounts() {
    List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account];

    for (Account acc : accounts) {
        System.debug('Contacts: ' + acc.Contacts.size()); // ✅ ループ内でクエリなし
    }
}
```

---

## **🎯 まとめ**
| 項目 | ❌ 悪い例 | ✅ 良い例 |
|------|---------|---------|
| **ループ内のクエリ** | ループの中でSOQL実行 | 1回のクエリでデータ取得 |
| **コレクションの活用** | 都度クエリで取得 | Set/Mapでデータ管理 |
| **親-子のレコード数** | 親と子を個別に取得 | 関連オブジェクトをネスト取得 |
| **単一クエリで取得** | 必要なデータを何度も取得 | 1回のクエリで最大限の情報を取得 |