Part 5: ガバナ制限を考慮したエラーハンドリングとバッチ処理の例外処理

## **Part 5: ガバナ制限を考慮したエラーハンドリングとバッチ処理の例外処理**  

前回は **エラーログの記録、ユーザー通知、トランザクション管理** について解説しました。  
今回は **ガバナ制限を考慮したエラーハンドリング** と **バッチ処理の例外処理** について詳しく解説します。

---

## **1. ガバナ制限を考慮したエラーハンドリング**  

Salesforce では、Apex の実行時に「ガバナ制限 (Governor Limits)」が適用されます。  
**特に `LimitException` は `try-catch` でキャッチできないため、事前対策が重要** です。  

### **1.1. LimitException の代表例**
以下のような制限を超えると `LimitException` が発生し、処理が強制終了します。  

| ガバナ制限 | 説明 |
|---|---|
| `SOQL 100 クエリ制限` | 1 トランザクションで `SOQL` を 100 回以上実行するとエラー |
| `DML 150 操作制限` | `insert`, `update`, `delete` などを 150 回以上実行するとエラー |
| `CPU 時間制限` | 処理に 10 秒以上かかるとエラー |
| `ヒープサイズ制限` | メモリ使用量が 6MB (標準) または 12MB (バッチ) を超えるとエラー |

---

### **1.2. `Limits` クラスを使った制限の事前チェック**
ガバナ制限を回避するために、`Limits` クラスを使って現在の使用状況を確認できます。

```apex
if (Limits.getQueries() >= Limits.getLimitQueries() - 5) {
    System.debug('SOQL クエリ制限に近づいています');
}
if (Limits.getDmlStatements() >= Limits.getLimitDmlStatements() - 10) {
    System.debug('DML 操作制限に近づいています');
}
```

`Limits.getQueries()` で現在の SOQL 実行回数を取得し、  
`Limits.getLimitQueries()` で上限（100）を取得できます。

---

### **1.3. `SOQL forループ` を使ってクエリ制限を回避**
通常の `for` ループで `SOQL` を使うと、100 クエリ制限を超える可能性があります。

**NG 例:**
```apex
for (Account acc : [SELECT Id, Name FROM Account]) {
    Contact con = [SELECT Id FROM Contact WHERE AccountId = :acc.Id LIMIT 1]; // クエリがループごとに実行される
}
```
この方法だと **アカウントが 100 件以上あると `SOQL 100 回制限` を超えてエラーになります**。

---

**OK 例:**
```apex
Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Id, Name FROM Account]);
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accountMap.keySet()];
```
この方法なら **SOQL 実行回数は 2 回に抑えられます**。

---

### **1.4. `Database.insert()` を使って DML 操作制限を回避**
`insert` をループの中で実行すると、**DML 150 回制限を超える可能性** があります。

**NG 例:**
```apex
for (Account acc : accounts) {
    insert acc; // 150 件以上のアカウントを処理するとエラー
}
```

**OK 例:**
```apex
insert accounts; // 一括 DML で 1 回の操作で済む
```

---

## **2. バッチ処理 (Batch Apex) の例外処理**
バッチ処理では、数千～数百万件のレコードを処理することがあるため、  
**エラーハンドリングの戦略が重要** になります。

---

### **2.1. バッチで `try-catch` を使う**
バッチ実行中にエラーが発生すると、**そのバッチ内の全レコードがロールバック** されます。  
これを防ぐには `try-catch` を使って **エラーになったレコードだけをスキップ** します。

**NG 例:**
```apex
global void execute(Database.BatchableContext bc, List<Account> scope) {
    insert scope; // 1件でもエラーがあると全件ロールバックされる
}
```

---

**OK 例:**
```apex
global void execute(Database.BatchableContext bc, List<Account> scope) {
    List<Account> successList = new List<Account>();
    
    for (Account acc : scope) {
        try {
            insert acc;
            successList.add(acc);
        } catch (DmlException e) {
            System.debug('エラー: ' + e.getMessage());
        }
    }
}
```
こうすることで、**エラーになったレコードだけをスキップし、成功したレコードはそのまま処理** できます。

---

### **2.2. `Database.insert()` でエラーを個別処理**
`Database.insert()` の第二引数に `false` を指定すると、**部分成功が可能** になります。

```apex
global void execute(Database.BatchableContext bc, List<Account> scope) {
    Database.SaveResult[] results = Database.insert(scope, false);
    
    for (Database.SaveResult sr : results) {
        if (!sr.isSuccess()) {
            for (Database.Error err : sr.getErrors()) {
                System.debug('エラー: ' + err.getMessage());
            }
        }
    }
}
```
この方法では、**エラーが発生したレコードだけを特定し、他のレコードは正常に処理** されます。

---

### **2.3. バッチのエラーを `finish()` でログに記録**
バッチ実行後にエラーログを記録するには `finish()` メソッドを活用します。

```apex
global void finish(Database.BatchableContext bc) {
    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    mail.setSubject('バッチ処理完了');
    mail.setPlainTextBody('バッチ処理が完了しました。エラーログを確認してください。');
    mail.setToAddresses(new String[] {'admin@example.com'});
    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
```
このように `finish()` で **バッチ完了時にメール通知** することで、エラー発生時にすぐ気付けます。

---

## **3. 例外処理のベストプラクティス**
- **ガバナ制限を意識する**  
  - `SOQL forループ` を避ける
  - `Limits` クラスを使って事前チェック
  - 一括 `DML` (`insert list`) を活用
- **バッチ処理では try-catch を使う**  
  - 1 件のエラーで全件ロールバックされないようにする
- **バッチのエラーを `finish()` で記録・通知する**  
  - エラーログを作成し、管理者に通知

---

## **まとめ**
今回は、**ガバナ制限を考慮したエラーハンドリング** と **バッチ処理のエラーハンドリング** について解説しました。

- `SOQL 100 回制限` や `DML 150 回制限` を **事前チェック** で回避
- `try-catch` を使って **バッチ処理でのロールバックを防ぐ**
- `Database.insert(scope, false)` で **部分成功を許可**
- `finish()` で **エラーログを記録し、通知する**

> **次の Part 6** では、**テストクラスでのエラーハンドリング** や **カスタム例外の活用** について解説します！