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

Apexは**ガバナ制限（Governor Limits）** の影響を受けるため、効率的なデータ処理をしないと **パフォーマンスの低下やエラー** に繋がります。  
今回は、**データセットの最適化、SOQL for ループの活用、SOQLのパフォーマンス向上のヒント** について、具体的なコード例を交えて整理しました。

---

## **4. 原則: データセットを減らす**
### **✅ 理由**
- **不必要なレコードを処理しないことで、メモリ使用量（ヒープサイズ）を削減できる。**
- **`Trigger.oldMap` を使用することで、実際に変更されたレコードのみ処理できる。**
- **SOQLクエリのフィルタリングを活用し、取得するレコードを最小限に抑える。**

---

### **📌 例：Trigger.oldMap を使用して、不要なレコードを処理対象から除外**
```apex
trigger AccountTrigger on Account (before update) {
    List<Account> accountsToUpdate = new List<Account>();

    for (Account acc : Trigger.new) {
        Account oldAcc = Trigger.oldMap.get(acc.Id);

        // ❌ 名前が変更されていない場合は処理しない
        if (acc.Name == oldAcc.Name) {
            continue; // 変更なし → スキップ
        }

        acc.Description = 'Updated';
        accountsToUpdate.add(acc);
    }

    if (!accountsToUpdate.isEmpty()) {
        update accountsToUpdate;
    }
}
```

### **✨ 修正のポイント**
- **`Trigger.oldMap.get(acc.Id)` を使い、変更前後の値を比較**。
- **変更がないレコードはスキップして、不要なDML操作を減らす。**

---

### **📌 例：SOQLクエリをフィルタリング**
```apex
List<Contact> contacts = [SELECT Id, LastName FROM Contact WHERE IsDeleted = FALSE AND LastName != NULL];
```

✅ **不要なデータ（削除済み・NULLの値）を除外し、取得するレコードを最小化**。

---

## **5. 原則: SOQL for ループを使用してヒープサイズを減らす**
### **✅ 理由**
- **大量データを処理するとき、SOQLの結果をすべてメモリに読み込むと「ヒープサイズ制限」に達する可能性がある。**
- **SOQL for ループを使用すると、少量ずつレコードを処理できるため、メモリ使用量を抑えられる。**

---

### **📌 例：❌ 悪い例（ヒープサイズが大きくなる可能性）**
```apex
List<Account> accounts = [SELECT Id, Name FROM Account];
for (Account acc : accounts) {
    System.debug(acc.Name);
}
```
⛔ **問題点**
- 全レコードをリストに格納するため、**ヒープサイズの制限（6MB）を超えるリスクがある**。

---

### **📌 ✅ 良い例（SOQL for ループを使用）**
```apex
for (Account acc : [SELECT Id, Name FROM Account]) {
    System.debug(acc.Name);
}
```
✅ **SOQL for ループを使用することで、Apex が少量ずつレコードを処理し、メモリ使用量を削減できる！**

---

### **📌 例：SOQL for ループを使って、DML操作を最適化**
```apex
List<Contact> contactsToUpdate = new List<Contact>();

for (Contact con : [SELECT Id, LastName FROM Contact WHERE LastName != NULL]) {
    con.LastName = con.LastName + ' Updated';
    contactsToUpdate.add(con);

    // バッチサイズ（200件）ごとにDML実行
    if (contactsToUpdate.size() == 200) {
        update contactsToUpdate;
        contactsToUpdate.clear();
    }
}

// 残りのレコードを更新
if (!contactsToUpdate.isEmpty()) {
    update contactsToUpdate;
}
```
✅ **大量データを処理するときは、バッチサイズを管理しながらDMLを実行すると効率的！**

---

## **6. SOQL のパフォーマンス向上のためのヒント**
### **✅ 理由**
- **SOQLの検索速度を向上させるためには、インデックスを活用するのが重要。**
- **Salesforceのインデックスが設定されている項目（Id, 外部ID）を使うとクエリのパフォーマンスが向上。**

---

### **📌 例：❌ 悪い例（インデックスなしのカラムをWHERE条件に使用）**
```apex
List<Contact> contacts = [SELECT Id FROM Contact WHERE LastName = 'Tanaka'];
```
⛔ **問題点**
- `LastName` は通常インデックスが設定されていないため、**検索速度が遅くなる**。

---

### **📌 ✅ 良い例（インデックスを使用する）**
```apex
List<Contact> contacts = [SELECT Id FROM Contact WHERE Id IN :contactIds];
```
✅ **`Id` は標準でインデックスが付いているため、高速検索が可能！**

---

### **📌 例：外部IDを活用してクエリを最適化**
```apex
List<Account> accounts = [SELECT Id FROM Account WHERE External_Id__c = 'A00123'];
```
✅ **外部ID（`External_Id__c`）にインデックスを設定すると、高速検索が可能！**

---

### **📌 例：カスタムインデックスを活用**
- 標準のインデックスがない項目（`Status__c` など）でも、**Salesforceにインデックスをリクエストしてパフォーマンスを改善できる**。
```apex
List<Case> cases = [SELECT Id FROM Case WHERE Status__c = 'Open']; // カスタムインデックスをリクエスト可能
```

---

## **🎯 まとめ**
| 項目 | ❌ 悪い例 | ✅ 良い例 |
|------|---------|---------|
| **データセットの削減** | すべてのレコードを処理 | `Trigger.oldMap` で変更があるレコードのみ処理 |
| **SOQL for ループ** | リストに全件取得 | `for (record : [SOQL])` を使う |
| **インデックス活用** | `WHERE LastName = 'Tanaka'` | `WHERE Id IN :setIds` を使う |
| **外部IDの活用** | `WHERE Email = 'test@example.com'` | `WHERE External_Id__c = 'A00123'` |