🦁

【Apex】IF文を減らす方法

2025/01/12に公開

はじめに

個人的に Apex PMD に怒られる頻度が一番多い CognitiveComplexity を解消する 1 つの策として、IF 文の使用を減らす方法を列挙します。

私は Apex のコード解析ツールとして、業務で Apex PMD を使っています。
新規で Apex クラスを作成する際は有用なのですが、保守改修する際はどうしてもレガシーコードが邪魔をして、大量に警告されてしまいます。
これを GitHub Actions として登録しており、警告を解消するまでプルリクエストが承認できない設定になっているため、警告解消のために余計な時間を割かなくてはいけません。(無理だろ...というやつは SuppressWarnings で無視してます)
そのため、 Apex PMD を通す努力が必要なのです。

IF 文を減らす 3 つの方法

1. 分岐条件をまとめる

こんな感じの処理があったとしましょう。

String fruit = response.get('data');
if (fruit === 'apple' || fruit === 'strawberry') {
    System.debug(fruit + 'is red');
} else if (fruit === 'grapes' || fruit === 'blueberry') {
    System.debug(fruit + 'is purple');
} else {
    System.debug('unexpected fruit: ' + fruit);
}

これは以下のように書けるかと思います。

+ Map<String, String> fruitColor = new Map<String, String>();
+ fruitColor.put('apple', 'red');
+ fruitColor.put('strawberry', 'red');
+ fruitColor.put('grapes', 'purple');
+ fruitColor.put('blueberry', 'purple');

String fruit = response.get('data');
+ if (fruitColor.containsKey(fruit)) {
+     System.debug(fruit + 'is ' + fruitColor.get(fruit));
- if (fruit === 'apple' || fruit === 'strawberry') {
-     System.debug(fruit + 'is red');
- } else if (fruit === 'grapes' || fruit === 'blueberry') {
-     System.debug(fruit + 'is purple');
} else {
    System.debug('unexpected fruit: ' + fruit);
}

事前にfruitColor変数を定義したおかげで、分岐条件が統一され、 IF 文が 1 つ消えました。
おまけに前よりコードが読みやすくなっている気がします。

2. 別のメソッドに括り出す

お次はこちらです。

public with sharing class SampleClass {
    public void sampleMethod {
        Map<String, String> fruitColor = new Map<String, String>();
        fruitColor.put('apple', 'red');
        fruitColor.put('strawberry', 'red');
        fruitColor.put('grapes', 'purple');
        fruitColor.put('blueberry', 'purple');

        // 処理1
        String fruit1 = response1.get('data');
        if (fruitColor.containsKey(fruit1)) {
            System.debug(fruit + 'is ' + fruitColor.get(fruit1));
        } else {
            System.debug('unexpected fruit: ' + fruit1);
        }

        ~~~

        // 処理2
        String fruit2 = response2.get('data');
        if (fruitColor.containsKey(fruit1)) {
            System.debug(fruit + 'is ' + fruitColor.get(fruit2));
        } else {
            System.debug('unexpected fruit: ' + fruit2);
        }
    }
}

1 と同じ書き方ですが、2 回同じ処理をしているため、IF 文は 2 倍になってしまいました。
こんな時は共通化できるロジックを括り出しましょう。

public with sharing class SampleClass {
+     private String getColorDescription(String fruit) {
+         Map<String, String> fruitColor = new Map<String, String>();
+         fruitColor.put('apple', 'red');
+         fruitColor.put('strawberry', 'red');
+         fruitColor.put('grapes', 'purple');
+         fruitColor.put('blueberry', 'purple');
+
+         if (fruitColor.containsKey(fruit)) {
+             return fruit + 'is ' + fruitColor.get(fruit);
+         } else {
+             return 'unexpected fruit: ' + fruit;
+         }
+     }

    public void sampleMethod {
-         Map<String, String> fruitColor = new Map<String, String>();
-         fruitColor.put('apple', 'red');
-         fruitColor.put('strawberry', 'red');
-         fruitColor.put('grapes', 'purple');
-         fruitColor.put('blueberry', 'purple');

        // 処理1
        String fruit1 = response1.get('data');
+         System.debug(getColorDescription(fruit1));
-         if (fruitColor.containsKey(fruit1)) {
-             System.debug(fruit + 'is ' + fruitColor.get(fruit1));
-         } else {
-             System.debug('unexpected fruit: ' + fruit1);
-         }

        ~~~

        // 処理2
        String fruit2 = response2.get('data');
+         System.debug(getColorDescription(fruit2));
-         if (fruitColor.containsKey(fruit1)) {
-             System.debug(fruit + 'is ' + fruitColor.get(fruit2));
-         } else {
-             System.debug('unexpected fruit: ' + fruit2);
-         }
    }
}

これでsampleMethod()内の IF 文は 0 個になりました。
括り出した分新しいメソッドgetColorDescription()に IF 文が増えましたが、 CognitiveComplexity はメソッドごとにカウントされるため、特に問題はないでしょう。これ以上同じ処理が増えても大丈夫です。

3. 数式項目を使う

これは SOQL 等で取得した項目を元に、処理を分岐させるパターンですね。

List<Opportunity> oppList = [
    SELECT Id, Name, Amount, StageName
    FROM Opportunity
    WHERE
        AccountId = 'xxxxxxxxxxxxxxxxxx'
        AND IsClosed = true
];

for (opp of oppList) {
    if (opp.StageName === '受注') {
        if (opp.Amount >= 100000000) {
            opp.Name = opp.Name + '(かなり嬉しい)';
        } else {
            opp.Name = opp.Name + '(少し嬉しい)';
        }
    } else {
        if (opp.Amount >= 100000000) {
            opp.Name = opp.Name + '(かなり悔しい)';
        } else {
            opp.Name = opp.Name + '(少し悔しい)';
        }
    }
}
update oppList;

この、項目値による IF 文処理をすべて数式項目に持たせましょう。

Opportunity.closedOppComment__c
+ IF(AND(ISPICKVAL(StageName, "受注"), Amount >= 100000000), "かなり嬉しい",
+ IF(AND(ISPICKVAL(StageName, "受注"), Amount < 100000000), "少し嬉しい",
+ IF(AND(ISPICKVAL(StageName, "失注"), Amount >= 100000000), "かなり悔しい",
+ IF(AND(ISPICKVAL(StageName, "失注"), Amount < 100000000), "少し悔しい",
+ ))))
List<Opportunity> oppList = [
+     SELECT Id, Name, Amount, StageName, closedOppComment__c
-     SELECT Id, Name, Amount, StageName
    FROM Opportunity
    WHERE
        AccountId = 'xxxxxxxxxxxxxxxxxx'
        AND IsClosed = true
];

for (opp of oppList) {
+     opp.Name = opp.Name + '(' + opp.closedOppComment__c + ')';
-     if (opp.StageName === '受注') {
-         if (opp.Amount >= 100000000) {
-             opp.Name = opp.Name + '(かなり嬉しい)';
-         } else {
-             opp.Name = opp.Name + '(少し嬉しい)';
-         }
-     } else {
-         if (opp.Amount >= 100000000) {
-             opp.Name = opp.Name + '(かなり悔しい)';
-         } else {
-             opp.Name = opp.Name + '(少し悔しい)';
-         }
-     }
}
update oppList;

Apex は綺麗さっぱりしました。
ただ、ここだけのために項目を作るのは設計的にスマートではないような気がします。
既存コードの改修ではなく、新規実装のフェーズだったらありかもしれません。

終わりに

個人的によく使う方法を載せました。
1 と 2 さえできていれば、 Apex PMDCognitiveComplexity に引っ掛からなくなるのではないでしょうか。
また、これを意識すれば保守性を高く保つことができると思いました。
他にいい方法があれば教えていただきたいです。

最後まで読んでいただきありがとうございました。

Discussion