Switchステートメントのリファクタリングと、Switchステートメントの実際の使用はありますか?


28

私はこの記事を読んでいて、プロジェクトにswitchステートメントがまったくないように、すべてのswitchステートメントをディクショナリーまたはファクトリーに置き換えることで削除するのかと疑問に思っていました。

何かが足りませんでした。

問題は、switchステートメントを実際に使用するか、辞書やファクトリーメソッドに置き換えるか(ファクトリーメソッドを使用する場合は、オブジェクトを作成するためにswitchステートメントを最小限使用することです)工場を使用して...しかしそれはそれについてです)。


10
ファクトリを実装すると仮定すると、どのタイプのオブジェクトを作成するかをどのように決定しますか?
-CodeART

2
非常に関連性が
高い

@CodeWorks:もちろん、どこに具体的な実装を使用するかを決める条件があります。
カニーニ

@CodeWorks:当然、仮想コンストラクターを使用します。(そして、そのようにファクトリパターンを実装できない場合は、より良い言語が必要です。)
メイソンウィーラー

回答:


44

switchステートメントとポリモーフィズムの両方に用途があります。ただし、3番目のオプションも存在することに注意してください(関数ポインター/ラムダ、および高階関数をサポートする言語):問題の識別子をハンドラー関数にマッピングします。これは、例えば、OO言語ではないC、*であるC#で利用可能ですが、Javaでも(まだ)OOではありません*。

いくつかの手続き型言語(何の多型や高次機能を有していない)でswitch/ if-elseステートメントは、問題のクラスを解決する唯一の方法でした。この考え方に慣れている多くの開発者は、switch多態性が多くの場合より優れたソリューションであるオブジェクト指向言語でも使用し続けています。これがswitch、ポリモーフィズムを支持してステートメントを回避/リファクタリングすることがしばしば推奨される理由です。

とにかく、最適なソリューションは常に大文字と小文字に依存します。質問は次のとおりです。長期的に見て、どのオプションがよりクリーンで、簡潔で、保守しやすいコードを提供しますか

多くの場合、Switchステートメントは扱いにくくなり、多数のケースが発生し、メンテナンスが難しくなります。それらを単一の関数に保持する必要があるため、その関数は巨大になる可能性があります。このような場合は、マップベースのソリューションやポリモーフィックソリューションに向けたリファクタリングを検討する必要があります。

同じswitchことが複数の場所でポップアップし始めた場合、多態性はおそらくこれらすべてのケースを統一し、コードを簡素化する最良のオプションです。特に、今後さらにケースが追加されると予想される場合。毎回更新する必要がある場所が多いほど、エラーの可能性が高くなります。ただし、多くの場合、個々のケースハンドラーは非常に単純であるか、非常に多数あるか、相互に関連しているため、それらを完全なポリモーフィッククラス階層にリファクタリングするのはやり過ぎです。クラス階層を維持するのが難しい。この場合、代わりに関数/ラムダを使用する方が簡単かもしれません(言語で許可されている場合)。

ただし、switch単一の場所にいて、ごく少数のケースで簡単なことをしている場合は、そのままにしておくのが最善のソリューションです。

* ここでは「OO」という用語を大まかに使用します。「本当の」オブジェクト指向または「純粋な」オブジェクト指向についての概念的な議論には興味がありません。


4
+1。また、これらの推奨事項は「切り替えよりもポリモーフィズムを好む」という言葉で表現される傾向があり、この答えに非常によく従って、それは良い言葉の選択です。責任あるコーダーが他の選択をする可能性がある状況があることを認めています。
カールマナスター

1
また、コードに膨大な数のコードが含まれている場合でも、切り替えアプローチが必要な場合があります。3D迷路を生成するコードを念頭に置いています。配列内の1バイトセルがクラスに置き換えられた場合、メモリの使用量は大きくなります。
ローレンペクテル

14

ここから恐竜に戻ります...

Switchステートメント自体は悪いことではありません。問題となっているのは、それらのステートメントの使用です。

最も明らかなものは、悪いコード(何度も何度も繰り返される「同じ」switchステートメントです)ポリモーフィズムを使用します。通常、ネストされたケースについてもかなり恐ろしいものがあります(以前は絶対的なモンスターを持っていました-「良い」以外の対処方法は完全にはわかりません)。

スイッチとしての辞書はもっと難しいと思います-スイッチがケースの100%をカバーしている場合は基本的にはありますが、デフォルトまたはアクションケースを持たない場合はもう少し面白くなります。

繰り返しを避け、適切な場所でオブジェクトグラフを作成していることを確認することが問題だと思います。

しかし、理解(保守性)引数もあり、これは両方の方法を削減します-すべてがどのように機能するか(パターンとそれが実装されているアプリ)を理解すると、簡単になります...何か新しいものを追加する必要があるので、追加/変更する必要があるものを見つけるために、場所を飛び越えなければなりません。

開発環境が非常に優れているため、紙に印刷されたコードを(まるで)理解できることが望ましいと感じています。コードを指でたどることができますか?私は実際にはいや、今日の多くの良い習慣ではできませんし、正当な理由でそれを受け入れますが、それはコードを理解するのが始めるのが難しいことを意味します(またはおそらく私は古いです...)


4
理想的には、コードは「プログラミングについて何も知らない人(お母さんなど)が理解できるように」書くべきだと言っている先生がいました。残念ながらこれは理想的ですが、とにかくコードレビューに母親を含める必要があります。
マイケルK

「しかし、何か新しいものを追加する必要があるコードの1行に来た場合、追加/変更する必要があるものを
見つける

8

スイッチステートメントとサブタイプポリモーフィズムは古い問題であり、FPコミュニティではExpression Problemに関する議論でしばしば言及されています。

基本的に、型(クラス)と関数(メソッド)があります。新しい型や新しいメソッドを簡単に追加できるように、どのようにコードを記述するのですか?

OOスタイルでプログラミングする場合、新しいメソッドを追加するのは難しい(既存のすべてのクラスをリファクタリングすることになるため)が、以前と同じメソッドを使用する新しいクラスを追加するのは非常に簡単です。

一方、switchステートメント(またはオブジェクト指向のオブジェクトオブザーバーパターン)を使用する場合、新しい関数を追加するのは非常に簡単ですが、新しいケース/クラスを追加するのは困難です。

双方向に優れた拡張性を持たせることは容易ではないため、コードを記述するとき、ポリモーフィズムを使用するか、後者を拡張する可能性が高い方向に応じてステートメントを切り替えるかを決定します。


4
私のプロジェクトにswitchステートメントがまったくないように、すべてのswitchステートメントをディクショナリまたはファクトリーに置き換えることによって削除しますか。

いいえ。このような絶対値が良いアイデアになることはめったにありません。

多くの場所で、ディクショナリ/ルックアップ/ファクトリ/ポリモーフィックディスパッチはswitchステートメントよりも優れたデザインを提供しますが、それでもディクショナリを設定する必要があります。場合によっては、実際に何が行われているかがわかりにくくなり、単にswitchステートメントをインラインにすると、より読みやすく保守しやすくなります。


実際、ファクトリとディクショナリとルックアップを展開すると、基本的にswitchステートメントになります:if array [hash(search)] then call [array(hash(search)])。コンパイルされたスイッチではないランタイム拡張可能ですが。
ザンリンクス

@ZanLynx、完全に正反対は、少なくともC#で真です。自明ではないswitchステートメント用に生成されたILを見ると、コンパイラーがそれを辞書に変換していることがわかります。
デビッドアルノ

@DavidArno:「完全に反対」?読み方はまったく同じことを言いました。どうして反対ですか?
ザンリンクス

2

これが言語に依存しないフォールスルーコードがswitchステートメントでどのように最適に機能するかを確認します。

switch(something) {
   case 1:
      foo();
   case 2:
      bar();
      baz();
      break;

   case 3:
      bang();
   default:
      bizzap();
      break;
}

非常に厄介なデフォルトの場合でif、と同等です。そして、フォールスルーが多いほど、条件リストが長くなることに注意してください:

if (1 == something) {
   foo();
}
if (1 == something || 2 == something) {
   bar();
   baz();
}
if (3 == something) {
   bang();
}
if (1 != something && 2 != something) {
   bizzap();
}

(しかし、他の答えのいくつかが言っていることを考えると、私は質問のポイントを逃したように感じます...)


私は、フォールスルー文の使用をa switchと同じレベルにあると見なしgotoます。実行するアルゴリズムが構造化プログラミング構造に適合する制御フローを必要とする場合、そのような構造を使用する必要がありますが、アルゴリズムがそのような構造に適合しない場合は、gotoフラグまたは他の制御ロジックを追加して他の制御構造に適合させるよりも使用する方が良い場合があります。
supercat

1

ケースの区別としてのswitchステートメントには実際の用途があります。関数型プログラミング言語では、パターンマッチングと呼ばれるものを使用して、入力に応じて異なる方法で関数を定義できます。ただし、オブジェクト指向言語では、ポリモーフィズムを使用することにより、同じ目標をよりエレガントに達成できます。メソッドを呼び出すだけで、オブジェクトの実際のタイプに応じて、メソッドの対応する実装が実行されます。これが、switchステートメントがコードの匂いであるという考えの背後にある理由です。ただし、OO言語であっても、抽象ファクトリパターンを実装するのに役立つ場合があります。

tl; dr-それらは有用ですが、非常に頻繁に改善できます。


2
私はそこにいくつかの偏見を嗅いでいます-なぜ多型はパターンマッチングよりもエレガントなのですか?そして、FPにポリモーフィズムが存在しないと思う理由は何ですか?
-tdammers

@tdammersまず第一に、FPには多型が存在しないことを意味するつもりはありませんでした。Haskellは、アドホックおよびパラメーターポリモーフィズムをサポートしています。パターンマッチングは、代数データ型にとって意味があります。ただし、外部モジュールの場合、コンストラクターを公開する必要があります。明らかに、これは(代数データ型で実現される)抽象データ型のカプセル化を破ります。そのため、ポリモーフィズムはよりエレガント(そしてFPで可能)であると感じています。
スカーフリッジ

型クラスとインスタンスによる多態性を忘れています。
tdammers

表現の問題を聞いたことがありますか?OOとFPは、考えることをやめれば、さまざまな状況で優れています。
hugomg

@tdammersアドホックなポリモーフィズムに言及したとき、私はどのようなポリモーフィズムについて話していたと思いますか?アドホックポリモーフィズムは、Haskellで型クラスを通じて実現されます。どうして忘れたと言える?単に真実ではありません。
スカーフリッジ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.