Apexのベストプラクティス: トリガーコンテキスト変数の適切な使用

## **1. このベストプラクティスのポイント**
Apexトリガーでは、`Trigger.new` や `Trigger.old` などの **コンテキスト変数** を使って、レコードの作成・更新・削除時のデータにアクセスできます。

**ベストプラクティスとして、トリガーでコンテキスト変数をハンドラクラスに渡す際、適切なデータ型にキャストする必要があります。**

---

## **2. トリガーコンテキスト変数とは？**
### **📌 主なコンテキスト変数**
| 変数 | 説明 | 使えるトリガー |
|------|---------------------|----------------|
| `Trigger.new` | **新しいデータ**（リスト） | `before insert`, `after insert`, `before update`, `after update` |
| `Trigger.old` | **変更前のデータ**（リスト） | `before update`, `after update`, `before delete`, `after delete` |
| `Trigger.newMap` | **新しいデータ（Map）** | `after insert`, `before update`, `after update` |
| `Trigger.oldMap` | **変更前のデータ（Map）** | `before update`, `after update`, `before delete`, `after delete` |

---

## **3. ❌ 悪い例（キャストしないまま処理する）**
### **📌 問題のあるコード**
```apex
public class AccountTriggerHandler {
    public static void processAccounts(List<sObject> records) {
        for (sObject obj : records) {
            System.debug('Account Name: ' + obj.get('Name')); // ❌ 正しいデータ型ではない
        }
    }
}

trigger AccountTrigger on Account (before insert) {
    AccountTriggerHandler.processAccounts(Trigger.new);
}
```
⛔ **問題点**
- `Trigger.new` は **`List<Account>`** だが、ハンドラメソッドでは `List<sObject>` として受け取っている。
- `sObject.get('Name')` は使えるが、**データ型が適切でないため、型チェックが必要になる**。
- **`Account` のプロパティ（例えば `Industry`）にアクセスする際に問題が発生する可能性がある**。

---

## **4. ✅ 良い例（適切にキャストする）**
### **📌 修正コード**
```apex
public class AccountTriggerHandler {
    public static void processAccounts(List<Account> accounts) {
        for (Account acc : accounts) {
            System.debug('Account Name: ' + acc.Name); // ✅ 型が適切
        }
    }
}

trigger AccountTrigger on Account (before insert) {
    AccountTriggerHandler.processAccounts((List<Account>) Trigger.new); // ✅ キャストして渡す
}
```
✅ **修正のポイント**
- `Trigger.new` を `List<Account>` にキャストして渡すことで、**型チェックの手間を減らし、コードの可読性を向上**。
- `acc.Name` などのプロパティに**型変換なしで直接アクセス可能**。
- **コンパイル時に型チェックされるため、エラーを未然に防ぐことができる**。

---

## **5. `Trigger.newMap` / `Trigger.oldMap` を使用する場合**
### **📌 `Trigger.newMap` を使う場合**
```apex
public class AccountTriggerHandler {
    public static void processAccountMap(Map<Id, Account> accountMap) {
        for (Account acc : accountMap.values()) {
            System.debug('Processing Account: ' + acc.Name);
        }
    }
}

trigger AccountTrigger on Account (before update) {
    AccountTriggerHandler.processAccountMap((Map<Id, Account>) Trigger.newMap);
}
```
✅ **修正のポイント**
- **`Trigger.newMap` を `Map<Id, Account>` にキャストすることで、`Account` 型のプロパティに直接アクセス可能。**
- 例えば、**特定のAccountの更新があった場合に、Mapを使ってすばやくチェックできる**。

---

### **📌 `Trigger.oldMap` を使う場合**
```apex
public class AccountTriggerHandler {
    public static void compareAccountNames(Map<Id, Account> oldAccounts, Map<Id, Account> newAccounts) {
        for (Id accId : oldAccounts.keySet()) {
            if (oldAccounts.get(accId).Name != newAccounts.get(accId).Name) {
                System.debug('Account Name changed from ' + oldAccounts.get(accId).Name + ' to ' + newAccounts.get(accId).Name);
            }
        }
    }
}

trigger AccountTrigger on Account (before update) {
    AccountTriggerHandler.compareAccountNames((Map<Id, Account>) Trigger.oldMap, (Map<Id, Account>) Trigger.newMap);
}
```
✅ **修正のポイント**
- **`Trigger.oldMap` と `Trigger.newMap` を `Map<Id, Account>` にキャスト** することで、**以前と現在のデータを比較しやすくなる**。
- **`oldAccounts.get(accId).Name` のように、安全にアクセスできる**。

---

## **6. まとめ**
| **ポイント** | **悪い例（NG）** | **良い例（OK）** |
|-------------|---------------|---------------|
| **キャストをしない** | `List<sObject>` のまま処理 | **`(List<Account>) Trigger.new` にキャスト** |
| **型チェックの手間** | `sObject.get('Name')` を使用 | **`acc.Name` のように直接アクセス** |
| **可読性** | コードが複雑になる | **型が明確で、わかりやすい** |

✅ **このベストプラクティスを守ることで、コードが安全かつ分かりやすくなり、エラーを未然に防げる！** 🚀