Trace の 4 つのライフサイクルメソッド

Apex Stem ドキュメント
Apex StemApexTraceLoggingSalesforceApex
Usecase に Trace を仕込む基本形。instance field で 1 回だけ生成する理由、start / log / skip / abort / finish の使い分け、3 つの終了パスを解説します。

Trace の 4 つのライフサイクルメソッド

Trace クラスは、Usecase の処理の流れを表現する 4 つのライフサイクルメソッドを持ちます。

instance field に持つ

Trace は Usecase クラスの instance field として 一度だけ 生成します。

APEX
public with sharing class CopyAccountIndustryToOpportunityUsecase {
  private Trace t = Trace.of('商談に親取引先の業種をコピー');
  // ...
}

「なぜ instance field で 1 回だけなのか」は、§「その他の注意点」で詳しく扱います。ここではまず、Usecase 1 つにつき 1 つの Trace を持つ、と覚えれば十分です。

4 つのメソッドと 3 つの終了パス

メソッド用途呼ぶタイミング
t.start()処理開始invoke() の冒頭
t.log(msg)中間ログ任意箇所、複数回 OK
t.skip(msg)スキップ終了早期 return の直前 (処理対象なしなど)
t.abort(msg)異常終了例外で抜ける時、業務エラーで中止する時
t.finish(msg)正常終了invoke() の末尾

log / skip / abort / finish には 引数なしのオーバーロード もあります。メッセージを残す必要がない場面 (とにかく終了させたいだけ、など) では t.finish() のように呼べます。経路 (skip / finish / abort) の区別だけ残せばよく、追加のログは不要、というケースに向きます。

ポイントは 3 つの終了パス です。

  • finish: 処理が正常に完了したことを表す
  • skip: 「対象がない」「条件不一致」などで、何もせず正常に抜けることを表す
  • abort: 例外発生や業務エラーで、正常完了しなかったことを表す

この 3 つを使い分けると、後段の TraceFlow で「どの経路で終わったか」をテストで検証できます。

コード例

CopyAccountIndustryToOpportunityUsecase を、Trace の使い方に焦点を当てて見てみます。

APEX
public with sharing class CopyAccountIndustryToOpportunityUsecase {
  @TestVisible static final String LBL_FETCH = 'oppFetch';
  @TestVisible static final String LBL_UPDATE = 'oppUpdate';
 
  private final Set<Id> opportunityIds;
  private final IEloquent eloquent;
  private Trace t = Trace.of('商談に親取引先の業種をコピー');
 
  // (コンストラクタは省略。詳細は導入ガイドのステップ 3 を参照)
 
  public void invoke() {
    this.t.start();
 
    if(this.opportunityIds == null || this.opportunityIds.isEmpty()) {
      this.t.skip('対象の商談がないため終了。');
      return;
    }
 
    Scribe oppScribe = Scribe.of(Opportunity.class)
      .field('Id')
      .parentField(Scribe.asParent('AccountId').field('Industry'))
      .whereIn('Id', this.opportunityIds);
    List<IEntry> oppEntries = this.eloquent.label(LBL_FETCH).get(oppScribe);
 
    for(IEntry oppEntry : oppEntries) {
      IEntry accountEntry = oppEntry.getParent('AccountId');
      oppEntry.put('Industry__c', accountEntry.get('Industry'));
    }
    this.eloquent.label(LBL_UPDATE).doUpdate(oppEntries);
 
    this.t.finish(oppEntries.size() + ' 件の商談に業種をコピー。');
  }
}

この Usecase は 2 つの経路で終わります。対象が空なら skip、処理を完了したら finish。次の章では、この 2 つの経路をテストで区別する方法を見ます。

関連ドキュメント