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

## 命名

※参考: [Choose Naming Conventions
Learning Objectives | TRAILHEAD](https://trailhead.salesforce.com/ja/content/learn/modules/success-cloud-coding-conventions/choose-naming-conventions-sc)

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


## スタイル

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


## パフォーマンス

### 繰り返し処理

* ループ内でDML操作、クエリを行わない。
    * データをリストにまとめて、ループ外で操作を行う。
    * ただし、sObject リスト形式のSOQL For ループでは使用可能（後述）。
    * 内部でDML操作、クエリを行うロジックの例
        * Messaging.renderEmailTemplate
        * Messaging.renderStoredEmailTemplate
        * SObject.recalculateFormulas;
        * Formula.recalculateFormulas;
* ループ内で非同期処理をコールしない。

> sObject リスト形式は for ループの <code_block> を 200 件の sObject のリストごとに 1 回実行します。そのため、多少理解しにくく、使用が難しくなりますが、for ループの本文内で DML ステートメントを使用する必要がある場合に最適です。DML ステートメントは、sObject のリストを一括処理します。(参考: [SOQL For ループ | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_loops_for_SOQL.htm))

### クエリ

* クエリは極力セレクティブにする。
    * 特にトリガから呼ばれる可能性がある場合、非セレクティブなクエリでクエリ対象が100万件件超える場合にはエラーが発生する。(セレクティブなクエリについてはこちらに解説記事を作成しました→[SOQLの高速化 | Qiita](https://qiita.com/TaaaZyyy/items/be0c95bedfcbb4f05e77))
* クエリで取得したレコードを繰り返し処理を行うときはSOQL For ループを使用する。リストに詰めるよりヒープサイズを削減することができる。(参考: [SOQL For ループ | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_loops_for_SOQL.htm))
* 大量の子レコード(200件以上)を取得する可能性がある場合は、直接アクセスせずにforループで子レコードを反復処理する。(参考: [SOQL For ループ | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_loops_for_SOQL.htm))
    ```java:子レコードで反復処理を行う
    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行としてカウントされるようになった([リリースノート](https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_soql_count_limits.htm&type=5&release=214))。なお、`COUNT_DISTINCT(fieldname)`は他の集計関数と同様、対象件数すべてがカウントされる。

### VFコントローラ

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

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

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

### （補足）Apexのパフォーマンスの検証方法

Apexのパフォーマンスに問題がある場合、Apex Log Analyzerを使用して問題箇所を特定することが可能です。

https://marketplace.visualstudio.com/items?itemName=financialforce.lana


## セキュリティ

### ハードコーディング

* 秘密鍵、初期化ベクトルをハードコードしない。
* 資格情報をハードコードしない。指定ログイン情報を使用する。
    * 参考: [コールアウトエンドポイントとしての指定ログイン情報 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.228.0.apexcode.meta/apexcode/apex_callouts_named_credentials.htm)
* RESTコールアウトでhttpを使用しない。httpsを使用する(または指定ログイン情報を使用する)。

### CRUD権限

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

* SOQL/SOSL/DML操作の前にアクセス許可をチェックを行う(参考: [オブジェクト権限と項目権限の適用 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_perms_enforcing.htm))。

### 攻撃対策

* リクエストパラメータなど外部から取得したURLにリダイレクトさせない。
* 動的SOQLの使用は極力避ける。
    * 動的SOQLの使用が避けられない場合は、`escapeSingleQuotes`メソッドを使用してSOQLインジェクションを防ぐ(参考: [SOQL インジェクション | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/pages_security_tips_soql_injection.htm))。
* エスケープされない`addError`メソッド(第2引数がfalse)を使用しない。


## Error Prone コード

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

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

## 複雑さ
※参考: [Apex Rules | PMD Source Code Analyzer Project](https://pmd.github.io/pmd-6.29.0/pmd_rules_apex.html)

* if文の深いネスト(3つ以上)は使用しない。
* メソッドの認知的複雑度(Cognitive Complexity)が15を下回るようにする。
* クラスの認知的複雑度(Cognitive Complexity)が50を下回るようにする。
    * 認知的複雑度とはコードの理解しにくさを定量的に表したもの。こちらの記事が詳しいです→[Cognitive Complexity、コードの読みやすさを定量的に計測しよう | Qiita](https://qiita.com/suzuki_sh/items/824c36b8d53dd2f1efcb)
    * [Salesforce CLI Scanner](https://forcedotcom.github.io/sfdx-scanner/)で検出できる。
* メソッドの循環的複雑度(Cyclomatic Complexity)が10を下回るようにする。
* クラスの循環的複雑度()が40を下回るようにする。
    * 循環的複雑度もコードの理解しにくさを定量的に表したもの。こちらの記事が詳しいです→[循環的複雑度について | Qiita](https://qiita.com/yut_arrows/items/16749e02313109071338)
    * [Salesforce CLI Scanner](https://forcedotcom.github.io/sfdx-scanner/)で検出できる。
* クラスの行数は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
](https://github.com/kevinohara80/sfdc-trigger-framework))。
* SOQLはセレクティブにする。Apexトリガでクエリ対象が200,000件を超えるオブジェクトに対し、セレクティブではないクエリを発行するとエラーになる。
* バルク処理を前提とする。一度に複数レコードが適切に処理されるようにする。
* 非同期処理でDML処理が実行されるオブジェクトのトリガに`@Future`メソッドは使用しない。
    * トリガで同期処理でコールアウトはできないことに注意（[ヘルプ記事](https://help.salesforce.com/s/articleView?id=000386018&type=1)）。
* 主従関係のカスケード削除では削除トリガは実行されない。

### VFコントローラクラス

* サイトで使用しているVisualforceのアクションメソッドは必ず例外をキャッチして処理を行う。
    * サイトのVisualforceで例外が発生すると認証エラーページに遷移してしまう。
    * アクションメソッド内でDML操作している場合は[ロールバック](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_transaction_control.htm)を行う

### バッチクラス

* `Database.Stateful`は処理上必要なときのみ使用する。
    * `Database.stateful`を使用すると、バッチのトランザクション間で更新したインスタンスメンバ変数の値が引き継がれるが、各プロセスが直列で実施されるため、パフォーマンスが低下する。
* startメソッドでgetQueryLocatorを使用する場合は、サブクエリを使用しない。
    * サブクエリを使用しているとgetQueryLocatorの処理の負荷が高くなり、`Apex CPU time limit exceed`のガバナ制限に抵触しやすくなる。
    * サブクエリを使用していると`INVALID_QUERY_LOCATOR`が発生ことがある（[参考](https://help.salesforce.com/articleView?id=000323582&type=1&mode=1)）。
    * 代替として、executeメソッド内で子レコードを取得する。
* バッチのエラー・例外はプラットフォームイベントで処理する。
    * try-catchできないガバナ制限エラーも処理することができる。
    * バッチApexに`Database.RaisesPlatformEvents`インターフェースを実装し、BatchApexErrorEventトリガを作成する。
    * [Apex 一括処理からのプラットフォームイベントの起動 | Apex 開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_batch_platformevents.htm)


### テストクラス

* assertionメソッドにはメッセージを含める。
* テストメソッドには`assertion`メソッドを含める。
* テストメソッドには`@IsTest`アノテーションを付ける(`testMethod`キーワードは非推奨となっている)。
* `seeAllData=true`を使用しない。
* テストデータ作成にはテストデータファクトリを使用する（[参考](https://www.apexhours.com/apex-test-class-best-practices/)）。


## ドキュメンテーションコメント(ApexDoc)

※参考: [ApexDoc | PMD Source Code Analyzer Project](https://pmd.github.io/pmd/pmd_rules_apex_documentation.html#apexdoc)

* ApexDocコメントを、オーバーライドとテストクラスを除く、public、globalのクラス、メソッド、プロパティに記述する。
* ApexDocコメントは`@description`を含める。
* 戻り値を返すメソッドのApexDocコメントは`@return`を記述する。
* 戻り値を返さないメソッド、コンストラクタに`@return`を含めない。
* 引数をもつメソッドのApexDocには、シグニチャと同じ順で各パラメータの`@param`を記述する。

```apex:例
/**
 * @description Hello World
 */
public class HelloWorld {
    /**
     * @description Bar
     * @return Bar
     */
    public Object bar() { return null; }
}
```

## その他留意事項

* 非同期処理のコンテキストで`@Future`メソッドを呼び出すことはできない。バッチでDML操作を行うとき、トリガで`@Future`メソッドが呼ばれないようにする。
* `@AuraEnabled`アノテーションを使用し、Auraコンポーネント、LWCからコールする場合、Apexクラスの参照権限が必要。
* トリガが実行されない処理がある。カスケード削除でレコードが削除される時、削除の起因となったレコード以外では削除トリガが実行されない。Apexトリガだけでなく、トリガフローも同様。
    * [トリガーを呼び出さない操作 | Apex 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_triggers_ignoring_operations.htm)

## 参考

### 命名

*  [Choose Naming Conventions
Learning Objectives | TRAILHEAD](https://trailhead.salesforce.com/ja/content/learn/modules/success-cloud-coding-conventions/choose-naming-conventions-sc)
### Salesforce CLI Scanner (静的解析ツール)
* [Salesforce CLI Scanner Plug-In](https://forcedotcom.github.io/sfdx-scanner/)
* [Improve Code Quality with Salesforce CLI | YouTube](https://www.youtube.com/watch?v=ayftLJYw4Aw)
* [Improve Your Code Quality with the Salesforce CLI Scanner | Salesforce Developers Blog](https://developer.salesforce.com/blogs/2020/10/improve-your-code-quality-with-the-salesforce-cli-scanner.html)

### パフォーマンス

* [ビューステートの最適化 | Visualforceのパフォーマンスのベストプラクティス](https://developer.salesforce.com/docs/atlas.ja-jp.salesforce_visualforce_best_practices.meta/salesforce_visualforce_best_practices/pages_best_practices_perf_code_view_state.htm)

### セキュリティ

* [ApexおよびVisualforce開発のセキュリティガイドライン | SALESFORCEヘルプ](https://help.salesforce.com/articleView?id=review_and_certification.htm&type=5)
* [コールアウトエンドポイントとしての指定ログイン情報 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.228.0.apexcode.meta/apexcode/apex_callouts_named_credentials.htm)
* [オブジェクト権限と項目権限の適用 | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_perms_enforcing.htm)
* [SOQL インジェクション | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/pages_security_tips_soql_injection.htm)
* [セキュアな Web アプリケーションの開発 | TRAILHEAD](https://trailhead.salesforce.com/ja/content/learn/trails/security_developer)

### テストクラス

*  [Apexテストクラス チートシート - Qiita](https://qiita.com/TaaaZyyy/items/bc9604faf0fa563f8c2e)
* [ベストプラクティスのテスト | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.234.0.apexcode.meta/apexcode/apex_testing_best_practices.htm)
* [Apex Test Class Best Practices | ApexHours](https://www.apexhours.com/apex-test-class-best-practices/)
* [Apex Best Practices: The 15 Apex Commandments | Salesforce Developers Blog](https://developer.salesforce.com/blogs/developer-relations/2015/01/apex-best-practices-15-apex-commandments)

### ベストプラクティス

* [Apex Design Best Practices | salesforce](https://developer.salesforce.com/ja/wiki/apex_code_best_practices)
* [SOQL For ループ | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/langCon_apex_loops_for_SOQL.htm)
* [Apex Rules | PMD Source Code Analyzer Project](https://pmd.github.io/pmd-6.29.0/pmd_rules_apex.html)
* [Database.Statefulインタフェースとは | エスパーラボ](https://esp-labo.com/db_stateful/)
* [トリガハンドラフレームワーク kevinohara80/sfdc-trigger-framework](https://github.com/kevinohara80/sfdc-trigger-framework)
* [20 Tips for Salesforce Developers | ApexHours](https://www.apexhours.com/20-tips-for-salesforce-developers/)
* [Apex Code Best Practices | ApexHours](https://www.apexhours.com/apex-code-best-practices/)
* [12 Salesforce Apex Best Practices | SalesforceBen](https://www.salesforceben.com/12-salesforce-apex-best-practices/)

### 参考になるソースコード

* Salesforce公式のサンプルアプリケーション：[SAMPLE GALLERY | salesforce](https://trailhead.salesforce.com/ja/sample-gallery)
    * 特に[Apex Recipes](https://github.com/trailheadapps/apex-recipes)は参考になる。
* Salesforce LabsのAppExchangeのソース：[SFDC Assets | GitHub](https://github.com/SFDC-Assets)
    * AppExchangeは[こちら](https://appexchange.salesforce.com/collection/SalesforceLabs)。