Part 4: 実践的なエラーハンドリングとトランザクション管理

## **Part 4: 実践的なエラーハンドリングとトランザクション管理**

前回は `try-catch` を使った基本的なエラーハンドリングの実装例を解説しました。  
今回は **より実践的なエラーハンドリングのテクニック** と **トランザクション管理（`Savepoint` と `rollback`）** について詳しく解説します。

---

## **1. 実践的なエラーハンドリングのテクニック**

### **1.1. エラーメッセージを詳細に記録する**
エラーメッセージを `System.debug()` だけで出力するのではなく、**カスタムエラーログオブジェクト** に保存することで、後から問題を追跡しやすくなります。

#### **(1) エラーログオブジェクトの作成**
まず、Salesforce に「`Error_Log__c`」オブジェクトを作成し、以下のカスタム項目を追加します。

| 項目名             | API名                   | 型           |
|------------------|----------------------|------------|
| エラーメッセージ | `Error_Message__c`   | 長いテキスト（255 文字） |
| クラス名         | `Class_Name__c`      | テキスト（255 文字） |
| 行番号           | `Line_Number__c`     | 数値        |
| スタックトレース | `Stack_Trace__c`     | 長いテキスト（32,000 文字） |

#### **(2) 例外をログに記録するコード**
```apex
public class ErrorLogger {
    public static void logException(Exception e, String className) {
        Error_Log__c errorLog = new Error_Log__c(
            Error_Message__c = e.getMessage(),
            Class_Name__c = className,
            Line_Number__c = e.getLineNumber(),
            Stack_Trace__c = e.getStackTraceString()
        );
        insert errorLog;
    }
}
```

#### **(3) エラーハンドリングでログを記録**
```apex
try {
    Account acc = new Account();
    insert acc; // Name がないので DmlException が発生
} catch (DmlException e) {
    ErrorLogger.logException(e, 'MyClass');
}
```
これにより、エラー情報が `Error_Log__c` に記録され、管理者が後で確認できるようになります。

---

### **1.2. 例外をカスタムエラーとしてユーザーに通知する**
標準のエラーメッセージではなく、**より分かりやすいメッセージを表示** することで、ユーザーの混乱を防ぐことができます。

#### **(1) `addError()` を使用してユーザーにエラーを通知**
```apex
public class AccountService {
    public static void createAccount(String name) {
        try {
            if (name == null || name == '') {
                throw new MyCustomException('アカウント名を入力してください。');
            }
            Account acc = new Account(Name = name);
            insert acc;
        } catch (Exception e) {
            throw new MyCustomException('アカウントの作成中にエラーが発生しました: ' + e.getMessage());
        }
    }
}

public class MyCustomException extends Exception {}
```
このコードでは、`MyCustomException` を使って、ユーザーに対して分かりやすいエラーメッセージを表示します。

#### **(2) Lightning コンポーネントやフローでエラーメッセージを表示**
```apex
public class AccountService {
    @AuraEnabled
    public static void createAccount(String name) {
        try {
            if (name == null || name == '') {
                throw new MyCustomException('アカウント名を入力してください。');
            }
            Account acc = new Account(Name = name);
            insert acc;
        } catch (Exception e) {
            throw new AuraHandledException('エラー: ' + e.getMessage());
        }
    }
}
```
`AuraHandledException` を使用すると、Lightning コンポーネントや LWC でエラーメッセージを表示できます。

---

## **2. トランザクション管理（Savepoint と Rollback）**
Salesforce では、DML 操作が失敗すると、その時点で **すべての処理がロールバック** されます。  
しかし、一部の DML 操作だけを **手動でロールバック** したい場合は、`Savepoint` を使用します。

### **2.1. Savepoint と Rollback の基本**
```apex
Savepoint sp = Database.setSavepoint();
try {
    Account acc = new Account(Name = 'Test Account');
    insert acc;
    
    Contact con = new Contact(LastName = 'Test Contact');
    insert con;
    
    // 意図的にエラーを発生
    Opportunity opp = new Opportunity(Name = null);
    insert opp;
    
} catch (DmlException e) {
    Database.rollback(sp);
    System.debug('ロールバックしました: ' + e.getMessage());
}
```
この例では、`Opportunity` の作成でエラーが発生すると、`Account` と `Contact` も **ロールバックされる** ため、データの整合性が保たれます。

---

### **2.2. `Database.insert()` を使用した部分的なロールバック**
`Database.insert()` を使うと、DML 操作の実行結果をリストで受け取ることができ、  
**成功したレコードのみをコミット** できます。

```apex
List<Account> accList = new List<Account>{
    new Account(Name = 'Account 1'),
    new Account(Name = 'Account 2'),
    new Account() // Name がないためエラー
};

Database.SaveResult[] results = Database.insert(accList, false); // false = 部分成功を許可

for (Database.SaveResult sr : results) {
    if (!sr.isSuccess()) {
        for (Database.Error err : sr.getErrors()) {
            System.debug('エラー: ' + err.getMessage());
        }
    }
}
```
ここでは、`Database.insert()` の第二引数に `false` を指定することで、  
**エラーが発生したレコードのみをスキップし、成功したレコードはそのまま挿入** できます。

---

## **3. 例外処理のベストプラクティス**
- **例外をキャッチする前に防ぐ:** `null` チェックや `LIMIT 1` を使って、発生しそうなエラーを事前に防ぐ。
- **エラーログを記録する:** `Error_Log__c` を活用し、エラーの原因を後から分析できるようにする。
- **カスタムエラーをユーザーに表示:** `addError()` や `AuraHandledException` を使用して、ユーザーに分かりやすいメッセージを提供する。
- **`Savepoint` と `rollback` を活用:** 一部の DML 操作だけをロールバックできるようにする。
- **`Database.insert()` を使い分ける:** `false` を指定することで、部分成功を許可し、エラーがあっても実行可能な処理を進める。

---

## **まとめ**
今回は、Salesforce の **実践的なエラーハンドリングのテクニック** と **トランザクション管理** について解説しました。

- エラーログ (`Error_Log__c`) を記録することで、問題を追跡しやすくする。
- `addError()` や `AuraHandledException` を使って、ユーザーに適切なエラーメッセージを表示する。
- `Savepoint` と `rollback` を活用し、DML 操作を部分的にロールバックする。

> **次の Part 5** では、「ガバナ制限を考慮したエラーハンドリング」や「バッチ処理での例外処理」について解説します！