LoginSignup
17
19

Apexの実装の考慮事項まとめ

Last updated at Posted at 2021-01-01

命名

※参考: Choose Naming Conventions
Learning Objectives | TRAILHEAD

  • クラス名・インターフェース名・列挙名は大文字で始める(インナークラスも含む)。
  • 列挙子は大文字で宣言する。
  • 定数(staticかつfinalで宣言されたフィールド)は大文字で宣言する。
  • 変数名、メソッド名、プロパティ名はローワーキャメルケースにする。

スタイル

  • クラス内で、フィールド定義はメソッド定義より先に記述する。
  • for文、if文、else文、while文、do-while文の中括弧は省略しない。
  • 一行で複数の変数を宣言しない。
  • インデントのスタイルは統一する。タブとスペースを混ぜない。

パフォーマンス

繰り返し処理

  • ループ内でDML操作、クエリを行わない。
    • データをリストにまとめて、ループ外で操作を行う。
    • ただし、sObject リスト形式のSOQL For ループでは使用可能。(参考: SOQL For ループ | Apex開発者ガイド)
    • 内部でDML操作、クエリを行うロジックの例
      • Messaging.renderEmailTemplate
      • Messaging.renderStoredEmailTemplate
      • SObject.recalculateFormulas;
      • Formula.recalculateFormulas;
  • ループ内で非同期処理をコールしない。

クエリ

  • クエリは極力セレクティブにする。
    • 特にトリガから呼ばれる可能性がある場合、非セレクティブなクエリでクエリ対象が200,000件超える場合にはエラーが発生する。(セレクティブなクエリについてはこちらに解説記事を作成しました→SOQLの高速化 | Qiita)
  • クエリで取得したレコードで繰り返し処理を行うときはSOQL For ループを使用する。リストに詰めるよりヒープサイズを削減することができる。(参考: SOQL For ループ | Apex開発者ガイド)
  • 大量の子レコード(200件以上)を取得する可能性がある場合は、直接アクセスせずにforループで子レコードを反復処理する。(参考: SOQL For ループ | Apex開発者ガイド)
    子レコードで反復処理を行う
    for (Account acc : [SELECT (SELECT Id FROM Contacts) FROM Account WHERE Id IN :accIds]) { 
        // Integer count = acc.Contacts.size(); ← 子レコードが200件以上ある場合は例外が発生
        // System.QueryException: Aggregate query has too many rows for direct assignment, use FOR loop External entry point
        Integer count = 0;
        for (Contact c : acct.Contacts) {
            count++;
        }
    }
    
  • COUNT() および COUNT(fieldname) 以外集計関数を使用するときはガバナ制限(Too many query rows)に注意。集計に含まれるレコード数もカウントされる。
    • COUNT()COUNT(fieldname)はSummer'18で1行としてカウントされるようになった(リリースノート)。なお、COUNT_DISTINCT(fieldname)は他の集計関数と同様、対象件数すべてがカウントされる。

VFコントローラ

  • 状態の維持に不可欠ではなく、ページの更新時にも不要な変数はtransientキーワードを使用する。
  • 使用しない項目はクエリしない。

Lightningコンポーネントコントローラ

  • JSONを返さない。オブジェクトを直接返す。JSでJSON.parseを使用しなくても自動でオブジェクトに変換される。
  • キャッシュが使用できる場面では@AuraEnabled(cacheable=true)を使用する。

セキュリティ

ハードコーディング

CRUD権限

Apexではシステムモードで動作するため、現在のユーザのオブジェクトレベル権限と項目レベルセキュリティは考慮されない。

攻撃対策

  • リクエストパラメータなど外部から取得したURLにリダイレクトさせない。
  • 動的SOQLの使用は極力避ける。
  • エスケープされないaddErrorメソッド(第2引数がfalse)を使用しない。

Error Prone コード

不完全であったり、ランタイムエラーとなりやすい構造を避ける。

  • いかなるSFIDもハードコーディングしない。
  • 空のcatch文を使用しない。
  • 空のtry文、finally文、if文、else文等使用しない。
  • 処理を行わないメソッドを使用しない。
  • コンストラクタでないメソッドで、クラス名と同じ名前を使用しない。
  • Trigger.new、Trigger.oldの配列に直接アクセスしない。繰り返し処理でアクセスする。
  • =+を記述しない(たいてい代入演算子+=の誤り)。
  • SOQLで複数選択リストをフィルタするとき、INを使用しない(たいていINCLUDESの誤り)(参考)。
  • SObjectのSetを使用しない。Mapを使用する(参考)。
  • updateしていないレコードに対してrecalculateFormulasメソッドを使用しない(クロスオブジェクト数式項目を除く)。
  • クエリする時、SalesforceIDでソートしない。SalesforceIDは昇順で採番されるという担保がない。

複雑さ

※参考: Apex Rules | PMD Source Code Analyzer Project

  • if文の深いネスト(3つ以上)は使用しない。
  • メソッドの認知的複雑度(Cognitive Complexity)が15を下回るようにする。
  • クラスの認知的複雑度(Cognitive Complexity)が50を下回るようにする。
  • メソッドの循環的複雑度(Cyclomatic Complexity)が10を下回るようにする。
  • クラスの循環的複雑度()が40を下回るようにする。
  • クラスの行数は1000行未満にする。
  • メソッドの仮引数の個数は3つまでにする。
  • publicのメンバは20個を下回るようにする。
  • フィールドは15個以内にする。
  • コンストラクタは20ステップを下回るようにする。
  • メソッドはで40ステップを下回るようにする。
  • クラスはで500ステップを下回るようにする

ベストプラクティス

一般

  • globalは極力避ける。管理パッケージではシグニチャを変更することができなくなるため。Schedulableなどもpublicで宣言できる。
    • webserviceキーワードが定義されているメソッドを含むクラスはglobalにする必要がある。
    • @RestResourceアノテーションのついたクラス、Apex RESTアノテーション(@HttpGetなど)のついたメソッドはglobalにする必要がある。
  • 他のクラスから参照することを想定していないメンバはprivateにする。
  • debugメソッドにはログレベルを設定する。
  • 使用しない変数を宣言・代入しない。
  • コレクションを返却するメソッドでnullを返却しない。空のコレクションを返すようにする。

トリガ

  • オブジェクトごとにトリガは1つまで。
  • ファイル名はオブジェクト名を含む一貫した命名規則を使用する(例: AccountTrigger)。
  • トリガに直接ロジックを書かない。トリガハンドラを使用する(sfdc-trigger-framework
    )。
  • SOQLはセレクティブにする。Apexトリガでクエリ対象が200,000件を超えるオブジェクトに対し、セレクティブではないクエリを発行するとエラーになる。
  • バルク処理を前提とする。一度に複数レコードが適切に処理されるようにする。
  • 非同期処理でDML処理が実行されるオブジェクトのトリガに@Futureメソッドは使用しない。
    • トリガで同期処理でコールアウトはできないことに注意(ヘルプ記事)。

VFコントローラクラス

  • サイトで使用しているVisualforceのアクションメソッドは必ず例外をキャッチして処理を行う。
    • サイトのVisualforceで例外が発生すると認証エラーページに遷移してしまう。
    • アクションメソッド内でDML操作している場合はロールバックを行う

バッチクラス

  • Database.Statefulは処理上必要なときのみ使用する。
    • Database.statefulを使用すると、バッチのトランザクション間で更新したインスタンスメンバ変数の値が引き継がれるが、各プロセスが直列で実施されるため、パフォーマンスが低下する。
  • startメソッドでgetQueryLocatorを使用する場合は、サブクエリを使用しない。
    • サブクエリを使用しているとgetQueryLocatorの処理の負荷が高くなり、Apex CPU time limit exceedのガバナ制限に抵触しやすくなる。
    • サブクエリを使用しているとINVALID_QUERY_LOCATORが発生ことがある(参考)。
    • 代替として、executeメソッド内で子レコードを取得する。
  • バッチのエラー・例外はプラットフォームイベントで処理する。

テストクラス

  • assertionメソッドにはメッセージを含める。
  • テストメソッドにはassertionメソッドを含める。
  • テストメソッドには@IsTestアノテーションを付ける(testMethodキーワードは非推奨となっている)。
  • seeAllData=trueを使用しない。
  • テストデータ作成にはテストデータファクトリを使用する(参考)。

その他留意事項

  • 非同期処理のコンテキストで@Futureメソッドを呼び出すことはできない。バッチでDML操作を行うとき、トリガで@Futureメソッドが呼ばれないようにする。
  • @AuraEnabledアノテーションを使用し、Auraコンポーネント、LWCからコールする場合、Apexクラスの参照権限が必要。
  • トリガが実行されない処理がある。カスケード削除でレコードが削除される時、削除の起因となったレコード以外では削除トリガが実行されない。Apexトリガだけでなく、トリガフローも同様。

参考

命名

Salesforce CLI Scanner (静的解析ツール)

パフォーマンス

セキュリティ

テストクラス

ベストプラクティス

参考になるソースコード

17
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19