web-dev-qa-db-ja.com

プログラムでif..else if..elseツリーを置換または置換する最良の方法は何ですか?

この質問は、最近私があまりにも頻繁に目にし始めたもの、if..else if..else構造。シンプルで用途はありますが、それについての何かが、よりきめ細かく、エレガントで、一般に最新の状態に保つのがより簡単なものに置き換えることができることを何度も繰り返し言います。

できるだけ具体的に言うと、これは私が意味するところです。

if (i == 1) {
    doOne();
} else if (i == 2) {
    doTwo();
} else if (i == 3) {
    doThree();
} else {
    doNone();
}

これを3種類の方法で書き換える簡単な2つの方法を考えることができます(これは、同じ構造を記述するもう1つの方法です)。

(i == 1) ? doOne() : 
(i == 2) ? doTwo() :
(i == 3) ? doThree() : doNone();

またはマップ(Javaで、C#でもそうだと思います)または辞書または次のような他のK/V構造を使用します。

public interface IFunctor() {
    void call();
}

public class OneFunctor implemets IFunctor() {
    void call() {
        ref.doOne();
    }
}

/* etc. */    

Map<Integer, IFunctor> methods = new HashMap<Integer, IFunctor>();
methods.put(1, new OneFunctor());
methods.put(2, new TwoFunctor());
methods.put(3, new ThreeFunctor());
/* .. */
(methods.get(i) != null) ? methods.get(i).call() : doNone();

実際、上記のMapメソッドは前回私がやったことですが、今まさにこの正確な問題にはより良い代替手段が必要だと考えるのをやめられません。

それで、if..else if..elseを置き換える他の-そしておそらくより良い-方法がそこにあり、どれがあなたのお気に入りですか?

この線の下のあなたの考え!


さて、ここにあなたの考えがあります:

まず、最も人気のある答えは、次のようにswitchステートメントでした。

switch (i) {
    case 1:  doOne(); break;
    case 2:  doTwo(); break;
    case 3:  doThree(); break;
    default: doNone(); break;
}

これは、少なくともJavaで、スイッチで使用できる値に対してのみ機能します。ただし、単純なケースでは当然許容されます。

あなたが示唆しているように思われるもう一つのそしておそらく少し奇妙な方法は、ポリモーフィズムを使用してそれを行うことです。 CMSによってリンクされたYoutubeの講義は優れた時計です。こちらをご覧ください。 "The Clean Code Talks-Inheritance、Polymorphism、&Testing" 私が理解している限り、これは次のようになります:

public interface Doer {
    void do();
}

public class OneDoer implements Doer {
    public void do() {
        doOne();
    }
}
/* etc. */

/* some method of dependency injection like Factory: */
public class DoerFactory() {
    public static Doer getDoer(int i) {
        switch (i) {
            case 1: return new OneDoer();
            case 2: return new TwoDoer();
            case 3: return new ThreeDoer();
            default: return new NoneDoer();
        }
    }
}

/* in actual code */

Doer operation = DoerFactory.getDoer(i);
operation.do();

Googleトークからの2つの興味深い点:

  • Nullを返す代わりにnullオブジェクトを使用します(実行時例外のみをスローしてください)
  • If:sなしで小さなプロジェクトを書いてみてください。

また、私の意見で言及する価値のある投稿の1つは、CDRであり、彼の変な癖を私たちに提供してくれました。

(これまでのところ)回答をありがとうございました。今日は何かを学んだと思います。

35
Esko

これらのコンストラクトは、多くの場合、ポリモーフィズムに置き換えることができます。これにより、短くて壊れにくいコードが得られます。

23
Brian Rasmussen

オブジェクト指向言語では、ポリモーフィズムを使用してifを置き換えるのが一般的です。

私はこのテーマをカバーするこのGoogle Clean Code Talkが好きでした:

クリーンコードトーク-継承、ポリモーフィズム、およびテスト

概要

あなたのコードはif文でいっぱいですか?ステートメントを切り替えますか?さまざまな場所で同じswitchステートメントがありますか?複数の場所で同じif/switchに同じ変更を加えていると思いますか?忘れたことがありますか?

この講演では、オブジェクト指向の手法を使用してこれらの条件の多くを削除する方法について説明します。その結果、テスト、理解、保守が容易な、よりクリーンで、タイトで、優れた設計のコードが得られます。

13
CMS

Switchステートメント:

switch(i)
{
  case 1:
    doOne();
    break;

  case 2:
    doTwo();
    break;

  case 3:
    doThree();
    break;

  default:
    doNone();
    break;
}
11
Andrew Kennan

その質問には2つの部分があります。

方法値に基づいてディスパッチするには? switchステートメントを使用します。それはあなたの意図を最も明確に表示します。

いつ値に基づいてディスパッチしますか?値ごとに1か所のみ:値に期待される動作を提供する方法を知っているポリモーフィックオブジェクトを作成します。

5
Iraimbilanja

If..else'ingのタイプに応じて、オブジェクトの階層を作成し、ポリモーフィズムを使用することを検討してください。そのようです:

class iBase
{
   virtual void Foo() = 0;
};

class SpecialCase1 : public iBase
{
   void Foo () {do your magic here}
};

class SpecialCase2 : public iBase
{
   void Foo () {do other magic here}
};

次に、コードでp-> Foo()を呼び出すだけで、正しいことが起こります。

5
Steve Rowe

よりきれいなスイッチ/ケースを使用してください:p

3
fmsf

switch ステートメントはもちろん、ifやelseの方がはるかにきれいです。

3
colithium

この単純なケースでは、スイッチを使用できます。

それ以外の場合、テーブルベースのアプローチは問題なく見えます。特に、ケースの数が多い場合は、条件を適用できるほど十分に規則的である場合は常に2番目の選択肢になります。

ケースが多すぎず、条件や動作が不規則である場合は、ポリモーフィズムがオプションになります。

3
starblue

高速化できるswitchステートメントを使用する以外は、なし。それ以外の場合は明確で読みやすいです。地図で物事を調べる必要があることで、物事が難読化されます。なぜコードを読みにくくするのですか?

3
WolfmanDragon
switch (i) {
  case 1:  doOne(); break;
  case 2:  doTwo(); break;
  case 3:  doThree(); break;
  default: doNone(); break;
}

これをタイプしたので、あなたのifステートメントにはそれほど問題はないと言っておく必要があります。アインシュタインが言ったように:「それを可能な限り単純にしてください。

3
Rolf

私は以下の短い手を使って楽しんでいます!入力した文字数よりもコードの明確さが問題になる場合は、これらを試さないでください。

DoX()が常にtrueを返す場合。

i==1 && doOne() || i==2 && doTwo() || i==3 && doThree()

もちろん、これらのショートハンドが可能であることを確認するために、ほとんどのvoid関数が1を返すようにしています。

割り当てを指定することもできます。

i==1 && (ret=1) || i==2 && (ret=2) || i==3 && (ret=3)

執筆のインスタッドのように

if(a==2 && b==3 && c==4){
    doSomething();
else{
    doOtherThings();
}

書く

a==2 && b==3 && c==4 && doSomething() || doOtherThings();

また、関数が何を返すかわからない場合は、|| 1 :-)を追加します。

a==2 && b==3 && c==4 && (doSomething()||1) || doOtherThings();

私はまだそれらすべてのif-elseを使用するよりもタイプする方が速く、新しいnoobをすべて怖がらせます。 5レベルのインデント付きのこのようなステートメントの全ページを想像してみてください。

一部のコードでは「if」はまれであり、「if-lessプログラミング」と名付けました:-)

3
CDR

質問で与えられた例は、単純なスイッチで動作するのに十分なほど簡単です。問題は、if-elsesが深くネストされている場合に発生します。 (他の誰かが主張したように)それらはもはや「明確または読みやすい」ものではなくなり、新しいコードを追加したり、バグを修正したりすることはますます難しくなり、ロジックが予期した場所に到達しない可能性があるため、確認が困難になります。複雑です。

私はこれが何度も発生するのを見てきました(入れ子にされた4レベルの深さ、何百行もの長さ-維持することは不可能です)、特に、あまりにも多くの異なる無関係な型に対してあまりにも多くのことをしようとしているファクトリークラスの内部。

比較する値が意味のない整数ではなく、ある種の一意の識別子である場合(つまり、列挙型を貧乏人の多態性として使用する場合)、問題を解決するためにクラスを使用する必要があります。それらが本当に数値の場合は、個別の関数を使用してifブロックとelseブロックの内容を置き換え、それらを表すためのある種の人工的なクラス階層を設計しないでください。結局、元のスパゲッティよりも厄介なコードになる可能性があります。

3
Michel

switchステートメントまたは仮想関数を備えたクラスは、ファンシーソリューションです。または、関数へのポインタの配列。それはすべて、条件がどれほど複雑であるかに依存します。場合によっては、それらを回避する方法がないこともあります。繰り返しになりますが、1つのswitchステートメントを回避するために一連のクラスを作成することは明らかに間違っています。コードはできるだけ単純にする必要があります(ただし、単純にすることはできません)。

2
vava

私はプログラムが他のものを使用するべきではないと言うまで行きます。もしそうなら、あなたはトラブルを求めています。 Xでない場合はYであると想定しないでください。テストはそれぞれ個別にテストし、そのようなテストでは失敗します。

2
mP.

OOパラダイムでは、古き良きpolymorphismを使用してそれを行うことができます。もし大きすぎる場合-他の構造またはスイッチ構造は、コードのにおいと見なされることがあります。

1
Nazgob

Mapメソッドは、最高の方法です。それはあなたがステートメントをカプセル化することを可能にし、物事をかなりうまく分解します。ポリモーフィズムはそれを補完できますが、その目標は少し異なります。また、不要なクラスツリーも導入します。

スイッチには、breakステートメントが欠落して抜け落ちるという欠点があり、問題を小さな部分に分割しないことを強く推奨します。

言われていること:if..elseの小さなツリーで結構です(実際、最近Mapを使用する代わりに、3つのif..elsesがあることを支持しています)。その中にもっと複雑なロジックを入れ始めると、保守性と読みやすさが原因で問題になります。

1

Pythonでは、コードを次のように記述します。

actions = {
           1: doOne,
           2: doTwo,
           3: doThree,
          }
actions[i]()
1
too much php

これらのif-elseif -...構文を「キーワードノイズ」と見なしています。何をしているのかは明らかかもしれませんが、簡潔さに欠けています。簡潔さは読みやすさの重要な部分と考えています。ほとんどの言語はswitchステートメントのようなものを提供します。マップの作成は、そのようなものがない言語でも同様のものを取得する方法ですが、確かに回避策のように感じられ、少しオーバーヘッドがあります(switchステートメントは、いくつかの単純な比較演算と条件ジャンプに変換されますが、マップは最初にメモリに組み込まれ、次に照会されてから、比較とジャンプが行われます)。

Common LISPには、condcaseの2つのスイッチコンストラクトが組み込まれています。 condは任意の条件を許可しますが、caseは等価かどうかのみをテストしますが、より簡潔です。

(cond((= i 1)
(do-one))
((= i 2)
(do-two))
((= i 3)
(do-three))
(t 
(do-none)))
 

(ケースi (1(do-one)) (2(do-two)) (3(do-three)) (それ以外の場合( do-none)))

もちろん、必要に応じて独自のcaseのようなマクロを作成することもできます。

Perlでは、forステートメントを使用でき、オプションで任意のラベルを付けます(ここではSWITCH)。

SWITCH: for ($i) {
    /1/ && do { do_one; last SWITCH; };
    /2/ && do { do_two; last SWITCH; };
    /3/ && do { do_three; last SWITCH; };
    do_none; };
1
Svante

三項演算子を使用してください!

三項演算子(53文字):

i===1?doOne():i===2?doTwo():i===3?doThree():doNone();

場合(108文字):

if (i === 1) {
doOne();
} else if (i === 2) {
doTwo();
} else if (i === 3) {
doThree();
} else {
doNone();
}

スイッチ((EVEN LONGER THAN IF!?!?)114Characters):

switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}

これで十分です!それはたった1行で、かなりすっきりとしており、スイッチよりもはるかに短いです。

1
Qyther

当然、この質問は言語に依存しますが、多くの場合、switchステートメントの方が適切なオプションになる可能性があります。優れたCまたはC++コンパイラは、 ジャンプテーブル を生成できます。これは、大規模なケースのセットの場合に非常に高速になります。

0
jasonmray

本当にifテストの束が必要で、テストがtrueの場合に別のことを実行したい場合は、ifのみを使用したwhileループを推奨します。各ifがテストを実行し、メソッドを呼び出すと、ループから抜けます。他に、if/else/if/elseなどの積み重ねよりも悪いことはありません。

0
mP.