Skip to content

投稿

Switch文の事例を検証する

2015年12月6日 • 17 分で読める

Switch文の事例を検証する

ほぼ50年間、switch文(case文としても知られている)はプログラミングの不可欠な部分でした。しかし近年、switch文はその有用性を失ったと主張する人もいます。さらに、switch文をコードスメルとしてラベル付けする人もいます。

1952年、Stephen Kleeneは彼の論文Introduction to Metamathematicsの中でswitch文を考案しました。最初の注目すべき実装は1958年のALGOL 58でした。その後、switch文は不朽のC言語に含まれました。ご存知の通り、C言語はほとんどの現代プログラミング言語に影響を与えています。

現在に至るまで、ほぼすべての言語がswitch文を持っています。しかし、switch文を省略した言語もいくつかあります。最も注目すべきはSmalltalkです。

これが私の好奇心をかき立てました。なぜswitch文はSmalltalkから除外されたのでしょうか?

Andy Bowerは、Dolphin Smalltalkの作成者/提唱者の一人で、Smalltalkがswitch文を除外した理由について彼の考えを共有しました:

C++からSmalltalkに初めて来たとき、いわゆる完全に成熟した言語がswitch/case構文をサポートしていないことが理解できませんでした。結局のところ、BASICから「構造化プログラミング」に初めて移行したとき、switchは最高のものだと思いました。しかし、Smalltalkがswitchをサポートしていなかったため、この欠陥を克服する方法を探して理解する必要がありました。正しい答えは、もちろんポリモーフィズムを使用し、オブジェクト自体が正しいコードにディスパッチするようにすることです。その後、それが「欠陥」ではなく、Smalltalkが私にC++で慣れていたよりもはるかに細粒度のOOP設計を強制していることに気付きました。switch文が利用可能だったなら、これを学ぶのに非常に長い時間がかかったか、さらに悪いことに、私はまだSmalltalkでC++/Javaの疑似オブジェクトスタイルでプログラミングしているかもしれません。
通常のOOPではswitch文の実際の必要性はないと主張します。非OOP世界とのインターフェース時(オブジェクトではなく単なる整数であるWM_XXXXウィンドウメッセージを受け取ってディスパッチする場合など)、switch文は有用です。これらの状況では、代替案があります(辞書からのディスパッチなど)。それらが発生する回数は、追加の構文の包含を保証しません。

Andy は正しかったのでしょうか?switch文がない方が良いのでしょうか?他の言語もswitch文を除外することで利益を得るでしょうか?

この質問に光を当てるために、switch文、辞書、およびポリモーフィズムの比較をまとめました。これをスマックダウンと呼びましょう。最高の実装が勝つことを願っています!

各実装には1つのパラメータ(整数)を取り、文字列を返すメソッドがあります。循環的複雑度保守性指数を使用して各実装を検証します。その後、3つの実装すべての全体的なビューを取ります。

コードです。

Switch文

保守性指数72
循環的複雑度6
    public class SwitchWithFourCases
    {
        public string SwitchStatment(int color)
        {
            var colorString = "Red";

            switch (color)
            {
                case 1:
                    colorString = "Green";
                    break;

                case 2:
                    colorString = "Blue";
                    break;

                case 3:
                    colorString = "Violet";
                    break;

                case 4:
                    colorString = "Orange";
                    break;

            }

            return colorString;
        }
    }

辞書

保守性指数73
循環的複雑度3
public class DictionaryWithFourItems
{ 
    public string Dictionary(int color)
    {
        var colorString = "Red";
        var colors = new Dictionary<int, string> {{1, "Green"}, {2, "Blue"}, {3, "Violet"}, {4, "Orange"}};
        var containsKey = colors.ContainsKey(color);
        if (containsKey)
        {
            colorString = colors[color];
        }

        return colorString;
    }
}

ポリモーフィズム

総保守性指数94
総循環的複雑度15

インターフェース

保守性指数100
循環的複雑度1
public interface IColor
{
    string ColorName { get; }
}

ファクトリ

保守性指数76
循環的複雑度4
public class ColorFactory
{
    public string GetColor(int color)
    {
        IColor defaultColor = new RedColor();
        var colors = GetColors();
        var containsKey = colors.ContainsKey(color);
        if (containsKey)
        {
            var c = colors[color];
            return c.ColorName;
        }

        return defaultColor.ColorName;
    }

    private static IDictionary<int, IColor> GetColors()
    {
        return new Dictionary<int, IColor>
        {
            {1, new GreenColor()}, 
            {2, new BlueColor()}, 
            {3, new VioletColor()}, 
            {4, new OrangeColor()}, 
            {5, new MagentaColor()}
        };
    }
}

実装

保守性指数97
循環的複雑度2
public class BlueColor : IColor
{
    public string ColorName => "Blue";
}

public class RedColor : IColor
{
    public string ColorName => "Red";
}

public class GreenColor : IColor
{
    public string ColorName => "Green";
}

public class MagentaColor : IColor
{
    public string ColorName => "Magenta";
}

public class VioletColor : IColor
{
    public string ColorName => "Violet";
}

結果

結果に入る前に、循環的複雑度と保守性指数を定義しましょう:

  • 循環的複雑度は論理分岐の尺度です。数値が低いほど良いです。
  • 保守性指数はコードの保守性を測定します。0から100のスケールです。数値が高いほど良いです。
循環的複雑度保守性指数
Switch文672
辞書373
ポリモーフィズム1594

まず循環的複雑度を検証します。

循環的複雑度の結果は明確です。辞書の実装が最もシンプルです。これが最良のソリューションを意味しますか?いいえ。保守性指数を評価するときに見るように。

ほとんどの人は私がそうしたように、最も低い循環的複雑度を持つ実装が最も保守しやすいと考えるでしょう。どうしてそうでなくてもいいのでしょうか?

私たちのシナリオでは、最も低い循環的複雑度を持つ実装が最も保守しやすいわけではありません。実際、私たちのシナリオでは、その反対です。最も複雑な実装が最も保守しやすいのです!マインドブロー!

思い出してください。保守性指数スコアが高いほど良いです。要点を言うと、ポリモーフィズムが最高の保守性指数スコアを持っています。しかし、最も高い循環的複雑度も持っています。何が起こっているのでしょうか?それは正しくないようです。

最も複雑な実装が最も保守しやすいのはなぜですか?これに答えるには、保守性指数を理解する必要があります。

保守性指数は4つのメトリクスで構成されています:循環的複雑度、コード行数、コメント数、およびHalstead体積です。最初の3つのメトリクスは比較的よく知られていますが、最後のメトリクスであるHalstead体積は比較的未知です。循環的複雑度と同様に、Halstead体積はコード複雑度を客観的に測定しようとします。

簡単に言うと、Halstead体積はコード内の可動部品(変数、システムコール、算術、コーディング構成など)の数を測定します。可動部品の数が多いほど複雑さが増します。可動部品の数が少ないほど複雑さが低くなります。これは、ポリモーフィック実装が保守性指数で高いスコアを獲得する理由を説明しています。クラスはほとんど可動部品がありません。Halstead体積を見る別の方法は、「可動部品」密度を測定することです。

ソフトウェアが変わらないとしたら、それは何ですか?現実の世界を反映するために、変更を導入しています。各実装に新しい色を追加しました。

以下は改訂された結果です。

循環的複雑度保守性指数
Switch文770
辞書373
ポリモーフィズム1795

switch文とポリモーフィックアプローチの両方は循環的複雑度が1単位増加しましたが、興味深いことに、辞書は増加しませんでした。最初は困惑しましたが、その後、辞書は色をデータと見なし、他の2つの実装は色をコードとして扱うことに気付きました。本質的なことに入ります。

保守性指数に注意を向けると、1つだけ、switch文が保守性で低下しました。ポリモーフィズムの保守性スコアは改善され、複雑度も増加しています(複雑度が低下することを望みます)。上記で述べたように、これは直感に反しています。

私たちの比較は、辞書が複雑さの観点から無限にスケーリングできることを示しています。ポリモーフィックアプローチは圧倒的に最も保守しやすく、より多くのシナリオが追加されるにつれて保守性が向上するようです。switch文は複雑度が増加し、新しいシナリオが追加されたときに保守性が低下します。新しいシナリオを追加する前でさえ、最悪の循環的複雑度と保守性指数の測定値を持っていました。

GoogleのJem Finchはswitch文の欠点について彼の考えを共有しました:

  1. ポリモーフィックメソッド実装は字句的に互いに分離されています。変数は追加、削除、変更などができます。switch文の別のブランチの関連のないコードに影響を与えるリスクはありません。

  2. ポリモーフィックメソッド実装は、終了すると仮定して、正しい場所に戻ることが保証されています。C/C++/Javaのようなフォールスルー言語のswitch文には、switchの後のステートメントではなく次のcaseブロックに戻ることを確認するための誤りやすい「break」ステートメントが必要です。

  3. ポリモーフィックメソッド実装の存在はコンパイラによって強制できます。ポリモーフィックメソッド実装が欠落している場合、コンパイラはプログラムのコンパイルを拒否します。switch文はそのような網羅性チェックを提供しません。

  4. ポリモーフィックメソッドディスパッチは、他のソースコードへのアクセスまたは再コンパイルなしに拡張可能です。switch文に別のcaseを追加するには、元のディスパッチコードへのアクセスが必要です。1つの場所だけでなく、関連する列挙型が切り替えられているすべての場所で。

  5. ポリモーフィックメソッドを切り替え装置とは独立してテストできます。著者が与えた例のようなswitch関数のほとんどは、個別にテストできない他のコードを含みます。一方、仮想メソッド呼び出しはテストできます。

  6. ポリモーフィックメソッド呼び出しは一定時間ディスパッチを保証します。十分にスマートなコンパイラは、自然に線形時間構成(フォールスルーを持つswitch文)を一定時間構成に変換する必要はありません。

残念なことに、または幸いなことに、あなたのキャンプによっては、ほとんどの言語にはswitch文があり、それらはすぐには消えません。これを念頭に置いて、switch文をコンパイルするときに何が起こっているかを知ることは良いことです。

3つのswitch文最適化が発生する可能性があります:

  1. If-elseif文 – switch文が少数のcaseまたはスパースなcase(10、250、1000などの非増分値)を持つ場合、if-elseif文に変換されます。
  2. ジャンプテーブル – 隣接するcaseの大きなセット(1、2、3、4、5)では、コンパイラはswitch文をジャンプテーブルに変換します。ジャンプテーブルは本質的に、メモリ内の関数へのポインタ(goto文と考えてください)を持つハッシュテーブルです。
  3. 二分探索 – スパースなcaseの大きなセットの場合、コンパイラは二分探索を実装してcaseを素早く識別できます。これはデータベースのインデックスがどのように機能するかに似ています。caseが大量のスパースで隣接するcaseである特別な場合、コンパイラは3つの最適化の組み合わせを使用します。

要約

オブジェクト指向の世界では、1952年に考案されたswitch文はソフトウェアエンジニアの主流です。注目すべき例外はSmalltalkです。設計者はswitch文を除外することを選択しました。

同等の代替実装である辞書とポリモーフィズムと比較すると、switch文はそれほど良くありませんでした。

switch文は存在し続けますが、私たちの比較が示したように、switch文にはより良い代替案があります。

実装はGithubで利用可能です。

Author: Chuck Conway is an AI Engineer with nearly 30 years of software engineering experience. He builds practical AI systems—content pipelines, infrastructure agents, and tools that solve real problems—and shares what he’s learning along the way. Connect with him on social media: X (@chuckconway) or visit him on YouTube and on SubStack.

著者: Chuck Conwayは、ソフトウェアエンジニアリングの経験が30年近くあるAIエンジニアです。彼は実用的なAIシステム(コンテンツパイプライン、インフラストラクチャエージェント、実際の問題を解決するツール)を構築し、学んだことを共有しています。ソーシャルメディアで彼とつながってください: X (@chuckconway) または YouTubeSubStack で彼を訪問してください。

↑ トップに戻る

こちらもおすすめ