サイクロマティック複雑度をあげずに分岐の多いswitch~caseを処理する方策


ことのはじまり

コマンドを読み込んで処理を実行するプログラムを作っていたのですが、コマンドを読んで処理を分岐する部分をswitch~caseで実装したところ、分岐が多すぎてVisualStudioでコードメトリクスを算出したときにサイクロマティック複雑度が50を超えてしまいました。
コマンド分岐部分なので無視してもよかったのですが、あまり美しくないのでもう少し良い方策がないか悩んだ結果、コードチェック上はサイクロマティック複雑度を上げずに書けたので、その方法を記載します。
(※あくまでコードチェックツール上のサイクロマティック複雑度を減らせるというだけで、実際的な意味では分岐は減っていないです。)

対応方法

まず、もとのコードは下記のような状態とします。

before

switch(commandString){
  case "commandA":
    stringResult = commandAExec(arg1,arg2);
    break;
  case "commandB":
    doubleResult = commandBExec(arg1);
    break;

//以下同様の分岐が続く...
}
beforeメソッド

string commandAExec (string arg1,double arg2){
  //CommandAの処理
  //・・・
  return stringResult;
}

double commandBExec(string arg1){
  //CommandBの処理
  //・・・
  return doubleResult;
}

//以下同様のメソッド多数...
//・・・

これに対して、コマンドごとに呼び出されるメソッドのデリゲートを作成してディクショナリに格納し、switchのかわりにディクショナリのキーを使って呼び出します。

1.分岐先のメソッドの戻り値と引数を一本化する

DTOを作成して、すべての分岐先のメソッドの引数と戻り値を一本化します。

DTO

class CommandArgDto{
  public string FieldArg1 {get;set;}
  public double FieldArg2 {get;set;}
  //必要なフィールドを定義する
}

class CommandResultDto{
  public string FieldResult1 {get;set;}
  public double FieldResult2 {get;set;}
  //必要なフィールドを定義する
}

メソッド

CommandResultDto commandAExec(CommandArgDto args){
  CommadnResultDto result;
  //コマンドAの処理
  //・・・
  //・・・
  return result;
}

CommandResultDto commandBExec(CommandArgDto args){
  CommadnResultDto result;
  //コマンドBの処理
  //・・・
  //・・・
  return result;
}

2.一本化したメソッドを取り扱うデリゲートを定義する

デリゲート

delegate CommandResultDto CommandDelegate(CommandArgDto args);

3.デリゲートを格納するディクショナリを作成し、中身を詰める。

ディクショナリ

var commandDic = New Dictionary<string,CommandDelegate>();

//コマンドとメソッドの組み合わせをディクショナリに格納する
commandDic.Add("commandA",commandAExec);
commandDic.Add("commandB",commandBExec);
//必要な組み合わせを追加

4.switchの代わりにディクショナリにキーを渡して呼び出す

呼び出し

commandArgDto args; 
CommandResultDto result;

//argsに引数を設定する
//・・・

//キーが存在するかどうかは先にチェックする
if (commandDic.ContainsKey(commandString)){
   CommandDelegate command = commandDic[commandString];
   result = command(args);
}

そうするとあら不思議、switch分岐がないのでサイクロマティック複雑度があがらなくなりました。
実際にデバッグするときにどちらのほうがわかり易いかと言われると素直にswitchで分岐してあったほうがいいような気もするので微妙なところです。