プログラマ38の日記

主にプログラムメモです。

Salesforce: Apex開発で気をつけていること

自分がApexでの開発で気をつけていることのメモです。

  1. Salesforce IDの項目はID型で定義する。
  2. トリガでの他オブジェクトの作成/更新処理を作り過ぎない
  3. 項目自動更新とトリガを混在させない
  4. そこでしか使わない処理は無名ブロックで囲み変数のスコープを限定する
  5. insert upsert update delete を try catch で制御するならロールバックは忘れずに行う
  6. トリガのテンプレートは必ず使う

1. Salesforce IDの項目はID型で定義する。

ID型とString型は互換性があり意識しなくても問題はないのですが、ID型で定義することで、後でプログラムを読む際にSalesforce IDが入る項目ということは明確になります。(特に関数の引数をID型で定義するとわかりやすくなります)
Stringで定義すると、Salesforce IDなのかそれとも別のユニークキーなのか明確にならないので、複数人数で開発する際に認識が違ってしまうこともあります。

 

気をつけたいのがID型で定義すると、文字列がIDとして正しくないとエラーになってしまいますが、後でエラーになるよりは最初でエラーとなった方が苦労しなくてすむと思います。


2. トリガでの他オブジェクトの作成/更新処理を作り過ぎない

トリガで、ある条件によって他のオブジェクトの作成/更新を行いすぎると、後々ガバナ制限の問題がでたり、データパッチを当てるときに注意が必要になったりします。
(気をつけて作成/更新をする分にはいいのですが、知らなくて勝手に他のオブジェクトが作成されたり、更新されたりすることで障害になったりするのが問題です)
利用者の理解が得られるなら、項目の作成/更新時に自動で動くのではなく、項目を更新後にボタンをクリックしてもらいボタンの処理として実装するのが安全だと考えています。
※もちろん時と場合によるので、その時その時で判断は必要ですが。


3. 項目自動更新とトリガを混在させない

トリガの作成が見えているオブジェクトならば、項目自動更新は設定せずに、トリガの中にロジックを全て持っていくのが良いと考えています。
というのは、新規作成時でも、項目自動更新で updateも走るので、トランザクションの中の処理量が増え、ガバナ制限や、性能への影響が出てくる可能性があります。
また、項目自動更新だと実行される順番が制御できないので、トリガの方が実装が簡単な場合があります。

 

4. そこでしか使わない処理は無名ブロックで囲み変数のスコープを限定する

一時的な変数を使って制御をしたい時があります。例えば次のようなコードがあります。

    List<Case> caselist = Trigger.New;
    Map<Id, Contact> contactMap = new Map<Id, Contact>();
    Set<Id> contactset = new Set<Id>();
    for(Case c : caselist){
      if( c.ContactId != null ){
        contactset.add(c.ContactId);
      }
    }
    contactMap = new MapMap<Id, Contact>([Select Id,Name,Email From  Contact where Id =:contactset]);

上記の「contactset」は「contactMap」を作成するためだけの一時的なSetで、後続では使わないものとします。
その場合、次のように無名ブロックで囲むのはどうでしょうか。

    List<Case> caselist = Trigger.New;
    Map<Id, Contact> contactMap = new Map<Id, Contact>();
    {
        Set<Id> contactset = new Set<Id>();
        for(Case c : caselist){
          if( c.ContactId != null ){
            contactset.add(c.ContactId);
          }
        }
        contactMap = new MapMap<Id, Contact>([Select Id,Name,Email From  Contact where Id =:contactset]);
    }

こうすることで、処理で使いたい変数と、一時的な変数のインデントが分かれるのと、ブロック内で変数のスコープが
限定されるので、後続の処理でうっかり「contactset」を使ってしまうことがなくなります。


5. insert upsert update delete を try catch で制御するならロールバックは忘れずに行う

処理の中で、2つ以上のオブジェクトに対してレコードを作成/更新することはよくありますが、その処理を
try catchで囲ってしまうと、最初の作成/更新は正常で、次の作成/更新でエラーの場合、最初の作成/更新が実行されてしまいます。そのため、きちんとcatchの中でロールバックする処理を忘れないようにします。
(結構よく忘れてしまうため備忘としてアウトプットしておこうと思います)

 

6. トリガのテンプレートは必ず使う

ちょっとしたトリガだとトリガ内にロジック書きたくなるのですが、一律テンプレートを使うことで統一した方が後々楽になります。

[トリガのテンプレートサンプル]

trigger SampleTrigger on Sample__c (
     before insert
    ,before update
//  ,before delete
    ,after insert
    ,after update
//  ,after delete
//  ,after undelete
){
    
    if( Trigger.isInsert ){

        if(Trigger.isBefore ){
            SampleTriggerHandler.OnBeforeInsert(Trigger.new);
        }
        
        if (Trigger.isUpdate) {
            SampleTriggerHandler.OnBeforeUpdate(Trigger.new, Trigger.old);
        } 
        
        //if (Trigger.isDelete) {
        //  SampleTriggerHandler.OnBeforeDelete(Trigger.old);
        //}
    
    } else if( Trigger.isAfter ){

        if(Trigger.isBefore ){
            SampleTriggerHandler.OnAfterInsert(Trigger.new);
        }
        
        if (Trigger.isUpdate) {
            SampleTriggerHandler.OnAfterUpdate(Trigger.new, Trigger.old);
        }
        
        //if (Trigger.isDelete) {
        //  SampleTriggerHandler.OnAfterDelete(Trigger.old);
        //}
        
        //if (Trigger.isUnDelete ){
        //  SampleTriggerHandler.OnUndelete(Trigger.new);
        //}
    }
}

1つつのオブジェクトに1つのトリガが推奨されていますが、1つのトリガに1つのハンドラクラスである必要はありません。例えば、トリガのロジックが膨大で複数人で開発する際には、最初に人数分のハンドラクラスを作れば効率はあがります。

 たくさんクラスを作ることでSOQLの発行回数が気になる場合は、static変数を使ってSOQLの発行回数を下げるやり方が効果的です。(開発を始める前にstatic変数でキャッシュするレコードを決め、あらかじめstaticメソッドを準備しておけば開発は楽になります)

 

static変数を使う方法は次の記事に書いています。

crmprogrammer38.hatenablog.com