18
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Salesforce PlatformAdvent Calendar 2018

Day 3

Apex開発で押さえておきたいポイント - その3

Last updated at Posted at 2018-12-03

Salesforce Advent Calendar 2018の3日目の記事ということでApex開発で押さえておきたいポイントについて投稿したいと思います。今年は小ネタ系です。

やってはいけないカスタム表示ラベルの使い方

カスタム表示ラベルは設定情報を保持するのにも便利なのでつい、レコードIDのような環境依存する情報を保持したくなります。例えば次の場合です。

■特定の条件で取引先責任者を登録時に、指定の取引先と紐付ける

apex
Contact contact = new Contact(
     AccountId = System.Label.SYSTEM_ACCOUNT_ID
    ,LastName = 'LastName'
    ,FirstName = 'FirstName'
);
insert contact;

一見、コードをいじらずに対象のレコードIDを変更できるので便利そうですが、Sandboxを管理する際に困ったことになります。例えば本番環境とSandboxの設定情報に差分がでてきてSandboxをリフレッシュしたとします。するとカスタム表示ラベルには本番のレコードのIDがセットされるため、処理実行の際にシステムエラーとなります。さらにメッセージは「アクセス権限がありません」となっていました。このエラーメッセージが表示されるとパッとみ権限まわりの設定ミスから疑わなくてはいけないのでけっこうツライことになります。

■メンテしやすい解決方法
もしもシステムレコードと紐つけたい場合はレコードIDではなくフラグ項目を用意するのがいいと思います。フラグがオンのレコードが複数あったらどうするのか?という問題はありますが、それは運用でカバーすれば済むレベルだと思います。また、レコードが存在しない場合にすぐにエラー原因に気づけるように判定処理を入れるのが望ましいです。

細かい部分は雑に書いていますが下記のような感じです。レコード存在判定して正しいエラーメッセージを表示して上げれば設定不足に気づいて対応しやすくなります。

apex
// システム取引先を1件取得
List<Account> accounts = [SELECT Id FROM Account 
                                       WHERE IsSystemAccount__c = true LIMIT 1];
// 存在判定
if (accounts.isEmpty()) {
    /* 〜 <<エラーメッセージ表示処理>> 〜 */
    return;
}
// 取引先責任者登録
Contact contact = new Contact(
     AccountId = accounts[0].Id
    ,LastName = 'LastName'
    ,FirstName = 'FirstName'
);
insert contact;

エラー判定をきちんと行う

上で記載したことと関連しますが、適切なエラー判定の実施とエラーメッセージの表示はメンテしやすい組織づくりには必須だと思います。エラーがあったらすべてExpeptionで拾う方法は結果的に管理が難しくなると思います。

エラー判定は普通にif文とかでやればいいだけなので特別なことはありませんが、sObject型のレコードIDの存在判定は account == null ではなく下記のような形で判定がいいと思います。

// レコードIDの存在判定
if (String.isNotEmpty(account.Id)) {
    // 取引先IDが存在している場合は処理実行
}

SOQLでレコード一件取得する方法

SOQLでレコード一件取得するのは簡単です。下記のような感じで取得できます。

Account a = [SELECT Name FROM Account WHERE Id =: prmAccountId LIMIT 1];

・・・ですが上記記述の場合は取得件数が0の場合にExceptionエラーとなります。。そのためList型で取得したほうが安全です。(0件チェックは別とやる前提。)

List型で受けるのが安全ですが、受け取ったあとに account[0] という記述をするのはイマイチな感じです。そこで下記のようなメソッドを用意するのがいいと思います。

public Account getAccount(String accountId) {
    // 取引先ID存在判定
    if (String.isEmpty(accountId)) {
        return new Account();
    }
    // 取引先IDに紐付く取引先情報取得
    List<Account> accounts = [
        SELECT
             Id
            ,Name
        FROM
            Account
        WHERE
            Id =: accountId
        LIMIT 1
    ];

    return accounts.isEmpty() ? new Account() : accounts[0];
}

これで通常のsObject型でクエリの実行結果を取得できます。クエリ実行前の引数の値チェックですが、省略しても大きな問題はありませんが、無駄なクエリ実行を回避できるのでやっておいた方が良いと思います。
※取得結果が0件の判定は String.isEmpty(account.Id)でチェックできます。

テストクラスをメンテしよう

Apex開発の際にはテストクラスの作成が必須です。テストカバー率が75%未満だと本番環境にリリースできないためです。・・・とこのあたりはよく言われているので別の話をしたいと思います。

社内に開発部分の担当者がいない場合によく起こると思うのですが、リリース時は問題なかったものの入力規則の追加などで動かなくなったテストクラスをそのままにしてしまうことがあると思います。

これにより、その組織の品質が保てなくなります。更に、それ以前の問題として次のようなケースが発生してしまいます。
①開発した機能をリリース→Salesforce導入プロジェクトが無事終了
②開発チームが撤収
③社内の管理者が機能を追加→テストクラス壊れる
④次の開発が始まる
⑤既存機能を引き継ぐ形で開発者JOIN
⑥必要な機能を開発
⑦リリースしようとする→テストクラスが壊れていてリリースできない
⑧自分の作業範囲ではないテストクラスを直さなくてはならない。。
⑨想定よりも作業に時間がかかる。
⑩本来やらなくていいことをやった上に無駄な責任を負わされる。

ということでテストクラスをメンテせずに放置すると次に作業を行う開発者が悲しい目にあいます。正しくメンテされていれば品質も向上して引き継ぎした開発者も悲しい目に合わずに済むのできちんとメンテして最低限エラーにはならない状態を保つのがおすすめです。

RemoteActionをつかおう

Visualforceページの開発というとapexタグによる開発が一般的でした。ですが、2014年ごろからJavaScriptベースの開発に切り替わってきていると思います。よくVisualforceはもっさりしているとか独自タグでの開発を強いられてツライという話題になりますが、独自タグを使わなくても開発が可能です。

Name項目は必須項目のため、特に値をセットする必要がない場合にテキスト型を指定してしまうと

取引先検索のデモ
https://www.youtube.com/watch?v=q2KygirmTRk&feature=youtu.be

商談商品登録のデモ
https://www.youtube.com/watch?v=QeGngfcETEc&feature=youtu.be

JavaScriptとRemoteActionをつかって開発すればサクサク動く快適なVisualforceページを用意できます。RemoteActionをつかった開発についてどうやって調べれば良いのか...という方はこちら。

apex-trailblazer-starter-pack
https://github.com/tyoshikawa1106/apex-trailblazer-starter-pack/

Name項目の自動採番とテキスト型

カスタムオブジェクトを作成したとき、Name項目のデータ型には2つの選択肢があります。
「自動採番型」と「テキスト型」です。

「テキスト型」は取引先名や商談名など自由に値をセットしたいときに利用します。
「自動採番型」は契約Noのように入力時に特に値を意識する必要がないときに利用します。

契約Noのような目的の項目にテキスト型をセットしてしまうと、データ入力者が自由に値を入力できるようになるため、Noという表記なのに自由な文字が登録されてしまうなど、本来の命名ルールから外れたデータが作成されていってしまいます。

また、カスタムオブジェクトのテキスト型のName項目には特殊なルールが存在しています。画面上は必須項目ですが、Apexから登録すると値未指定でデータを作成できてしまい、その場合はレコードIDが自動セットされるルールです。

参考
https://tyoshikawa1106.hatenablog.com/entry/2018/09/19/072507

もしも、このルールを意図して運用ルールに組み込もうと考えた場合はおすすめできません。仮にApex処理から自動登録される前提のシステムだとしても、ユーザが手動でデータを登録してしまったタイミングで命名ルールが破綻します。また自由に値を編集できるため、レコードIDという一見ユニークが担保されそうでも、いくらでも重複した値を登録できてしまう問題があります。

自由に値をセットしたい場合はテキスト型、そうでない場合は自動採番型ときちんと使い分けするのが大切だと思います。

Apexトリガと主従オブジェクトに関する裏技

主従オブジェクトは必ず下記の順番でデータが作成されます。
①主オブジェクト作成
②従オブジェクト作成 ※主オブジェクトに紐つける形で登録されます。

ですがApexトリガのBefore処理を使うことで下記手順でのデータ作成が可能となります。
①従オブジェクト作成 ※主オブジェクトには紐付かない
②従オブジェクトのBeforeトリガの処理実行
③Beforeトリガの処理内で主オブジェクトのデータを作成
④作成された主オブジェクトのデータと従オブジェク トのデータを紐付け
⑤Beforeトリガの処理終了

一見処理の実行順がちょっと変更されただけのように思えますが、このときに主オブジェクトと従オブジェクトに対してそれぞれ複雑なApexトリガが実装されると一気にメンテのツラいシステムが誕生します。

データを作成するときは上から下へ、「主」→「従」の順番で作成されるように意識しておくのが良いと思います。
※要件によっては「従」→「主」のほうがいいケースもあるかもしれません。ですがだいたいその手順でのデータ登録が必要になったときは設計をミスしている可能性が高い気がします。

さいごに

Salesforce Advent Calendar 2018の3日目の記事は以上です。今年は小ネタ系となりましたが、何年か前にも「Apex開発で押さえておきたいポイント」シリーズということで投稿しているのでもし時間があればそちらも読んでも貰えると嬉しいです。

Apex開発で押さえておきたいポイント
https://qiita.com/tyoshikawa1106/items/64e1dde75de245018647

Apex開発で押さえておきたいポイント - その2
https://qiita.com/tyoshikawa1106/items/69d4178e39cda318ac02

18
21
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
18
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?