web-dev-qa-db-ja.com

ボブおじさんのクリーンコード原則に準拠するようにif-else ifステートメントのチェーンを編集するにはどうすればよいですか?

ボブおじさんのすっきりしたコードの提案に従い、特にメソッドを短く保つようにしています。

私はこのロジックを短くすることができないことに気づきました:

if (checkCondition()) {addAlert(1);}
else if (checkCondition2()) {addAlert(2);}
else if (checkCondition3()) {addAlert(3);}
else if (checkCondition4()) {addAlert(4);}

Elseを削除して全体を小さなビットに分離できず、「else if」の「else」がパフォーマンスに役立つ-これらの条件の評価はコストがかかり、以下の条件の評価を回避できる場合は、最初の条件の1つを引き起こします本当です、私はそれらを避けたいです。

意味的にも、前の条件が満たされた場合に次の条件を評価することは、ビジネスの観点からは意味がありません。


編集:この質問は if(if else)elseを処理するためのエレガントな方法else の重複の可能性があると識別されました。

これは別の質問だと思います(これらの質問の回答を比較することでも確認できます)。

  • 私の質問は、最初の受け入れ条件をチェックして、すばやく終了することです
  • リンクされた質問は、何かをするためにすべての条件を受け入れようとしています。 (その質問に対するこの回答でよく見られます: https://softwareengineering.stackexchange.com/a/122625/96955
45
Ev0oD

理想的には、アラートコード/番号を独自のメソッドに取り込むためのロジックを抽出する必要があると思います。したがって、既存のコードは、

{
    addAlert(GetConditionCode());
}

getConditionCode()で、条件をチェックするロジックをカプセル化します。マジックナンバーよりもEnumを使用する方が良いかもしれません。

private AlertCode GetConditionCode() {
    if (CheckCondition1()) return AlertCode.OnFire;
    if (CheckCondition2()) return AlertCode.PlagueOfBees;
    if (CheckCondition3()) return AlertCode.Godzilla;
    if (CheckCondition4()) return AlertCode.ZombieSharkNado;
    return AlertCode.None;
}
81
Steven Eccles

重要な測定は、絶対サイズではなく、コードの複雑さです。さまざまな条件が実際には単一の関数呼び出しであると仮定すると、アクションがこれまでに示したものよりも複雑ではないのと同じように、コードには何も問題はないと思います。それはできる限り簡単です。

さらに「単純化」しようとすると、実際に事態が複雑になります。

もちろん、他の人が示唆しているように、elseキーワードをreturnに置き換えることができますが、それはスタイルの問題であり、複雑さの変更ではありません。


余談:

私の一般的なアドバイスは、クリーンなコードのルールについて決して信仰しないことです:インターネットで見られるコーディングアドバイスのほとんどは、適切なコンテキストで適用されれば良いですが、同じアドバイスをどこにでも根本的に適用すると、 [〜#〜] ioccc [〜#〜] 。トリックは常に、人間がコードについて簡単に推論できるバランスをとることです。

大きすぎる方法を使用すると、ねじ込まれます。小さすぎる関数を使用すると、ねじ込まれます。三項式は避けてください。どこでも三項式を使用してください。 1行の関数を呼び出す場所と50行の関数を呼び出す場所があることを理解してください(そうです、それらは存在します!)。 if()ステートメントを呼び出す場所があり、?:演算子を呼び出す場所があることを理解してください。自由に使える武器をすべて使い、見つけられる最も適切なツールを常に使用するようにしてください。また、このアドバイスについても信仰を持たないでください。

これが特定のケースでプレーンなif..elseよりも「優れている」かどうかは、議論の余地があります。 Butもしあなたが欲しい何か他のことを試みたいなら、これはそれを行うための一般的な方法です。

条件をオブジェクトに入れ、それらのオブジェクトをリストに入れます

foreach(var condition in Conditions.OrderBy(i=>i.OrderToRunIn))
{
    if(condition.EvaluatesToTrue())
    {
        addAlert(condition.Alert);
        break;
    }
}

条件付きで複数のアクションが必要な場合、いくつかのクレイジーな再帰を行うことができます

void RunConditionalAction(ConditionalActionSet conditions)
{
    foreach(var condition in conditions.OrderBy(i=>i.OrderToRunIn))
    {
        if(condition.EvaluatesToTrue())
        {
            RunConditionalAction(condition);
            break;
        }
    }
}

明らかにはい。これは、ロジックにパターンがある場合にのみ機能します。非常に一般的な再帰的な条件付きアクションを作成しようとすると、オブジェクトの設定は元のifステートメントと同じくらい複雑になります。あなたはあなた自身の新しい言語/フレームワークを発明するでしょう。

しかし、あなたの例doesはパターンを持っています

このパターンの一般的な使用例は検証です。の代わりに :

bool IsValid()
{
    if(condition1 == false)
    {
        throw new ValidationException("condition1 is wrong!");
    }
    elseif(condition2 == false)
    {
    ....

}

なる

[MustHaveCondition1]
[MustHaveCondition2]
public myObject()
{
    [MustMatchRegExCondition("xyz")]
    public string myProperty {get;set;}
    public bool IsValid()
    {
        conditions = getConditionsFromReflection()
        //loop through conditions
    }
}
22
Ewan

1つの条件が成功した後、_return;_を使用することを検討してください。これにより、elsesがすべて節約されます。メソッドに戻り値がある場合は、直接return addAlert(1)を実行することもできます。

7
Kilian Foth

私は時々このような構造がよりきれいであると考えたのを見ました:

switch(true) {
    case cond1(): 
        statement1; break;
    case cond2():
        statement2; break;
    case cond3():
        statement3; break;
    // .. etc
}

正しい間隔のTernaryも、きちんとした代替手段になります。

cond1() ? statement1 :
cond2() ? statement2 :
cond3() ? statement3 : (null);

また、条件と関数を含むペアで配列を作成し、最初の条件が満たされるまでそれを繰り返してみることもできると思います。

5
zworek

@Ewanの回答の変形として、次のような条件のchain(「フラットリスト」の代わりに)を作成できます。

abstract class Condition {
  private static final  Condition LAST = new Condition(){
     public void alertOrPropagate(DisplayInterface display){
        // do nothing;
     }
  }
  private Condition next = Last;

  public Condition setNext(Condition next){
    this.next = next;
    return this; // fluent API
  }

  public void alertOrPropagate(DisplayInterface display){
     if(isConditionMeet()){
         display.alert(getMessage());
     } else {
       next.alertOrPropagate(display);
     }
  }
  protected abstract boolean isConditionMeet();
  protected abstract String getMessage();  
}

このようにして、定義された順序で条件を適用し、インフラストラクチャ(示されている抽象クラス)は、最初のチェックが満たされた後、残りのチェックをスキップします。

これは、条件を適用するループで「スキップ」を実装する必要がある「フラットリスト」アプローチよりも優れているところです。

条件チェーンを設定するだけです。

Condition c1 = new Condition1().setNext(
  new Condition2().setNext(
   new Condition3()
 )
);

そして、簡単な呼び出しで評価を開始します。

c1.alertOrPropagate(display);
1
Timothy Truckle

コードが具体的ではないので、私はあなたの特定の状況について話すことはできませんが...

そのようなコードは、しばしばOOモデルの欠如の匂いです。実際には4つのタイプのものがあり、それぞれが独自のアラートタイプに関連付けられていますが、これらのエンティティを認識してクラスインスタンスを作成するのではなくそれぞれ、あなたはそれらを一つのものとして扱い、後でそれを補おうとします。同時に、あなたは本当に進むためにあなたが何を扱っているかを知る必要があるのです。

ポリモーフィズムの方が適しているかもしれません。

長いまたは複雑なif-then構成を含む長いメソッドを含むコードには疑わしい。いくつかの仮想メソッドを持つクラスツリーが必要になることがよくあります。

0
Martin Maat

まず、元のコードはひどいIMOではありません。それはかなり理解可能であり、本質的に悪いことは何もありません。

次に、それが気に入らない場合は、@ Ewanのリストを使用するという考えを基にして、多少不自然な_foreach break_パターンを削除します。

_public class conditions
{
    private List<Condition> cList;
    private int position;

    public Condition Head
    {
        get { return cList[position];}
    }

    public bool Next()
    {
        return (position++ < cList.Count);
    }
}


while not conditions.head.check() {
  conditions.next()
}
conditions.head.alert()
_

これを選択した言語に適合させ、リストの各要素をオブジェクト、タプルなどにします。

編集:私が思ったほど明確ではないようですので、さらに説明しましょう。 conditionsは、ある種の順序付きリストです。 headは調査中の現在の要素です。最初はリストの最初の要素であり、next()が呼び出されるたびに次の要素になります。 check()およびalert()は、OPからのcheckConditionX()およびaddAlert(X)です。

0
Nico

すべての関数が同じコンポーネントに実装されているとすると、フロー内の複数のブランチを取り除くために、関数にいくつかの状態を保持させることができます。

EG:checkCondition1()evaluateCondition1()になり、前の条件が満たされているかどうかをチェックします。もしそうなら、それはgetConditionNumber()によって取得されるいくつかの値をキャッシュします。

checkCondition2()evaluateCondition2()になり、前の条件が満たされているかどうかをチェックします。前の条件が満たされていない場合は、条件シナリオ2をチェックし、getConditionNumber()によって取得される値をキャッシュします。等々。

clearConditions();
evaluateCondition1();
evaluateCondition2();
evaluateCondition3();
evaluateCondition4();
if (anyCondition()) { addAlert(getConditionNumber()); }

編集:

このアプローチを機能させるために、高価な条件のチェックを実装する必要がある方法を次に示します。

bool evaluateCondition34() {
    if (!anyCondition() && A && B && C) {
        conditionNumber = 5693;
        return true;
    }
    return false;
}

...

bool evaluateCondition76() {
    if (!anyCondition() && !B && C && D) {
        conditionNumber = 7658;
        return true;
    }
    return false;
}

したがって、実行する高額なチェックが多すぎて、このコードの内容がプライベートのままである場合、このアプローチはそれを維持し、必要に応じてチェックの順序を変更できるようにします。

clearConditions();
evaluateCondition10();
evaluateCondition9();
evaluateCondition8();
evaluateCondition7();
...
evaluateCondition34();
...
evaluateCondition76();

if (anyCondition()) { addAlert(getConditionNumber()); }

この回答は他の回答からのいくつかの代替提案を提供するだけであり、4行のコードのみを考慮した場合、おそらく元のコードよりも優れていません。ただし、これはひどいアプローチではありません(そして、他の人が言ったようにどちらもメンテナンスを難しくしません)私が述べたシナリオを考えると(チェックが多すぎる、メイン関数のみが公開され、すべての関数は同じクラスの実装の詳細です)。

0
Emerson Cardoso

質問にはいくつかの詳細が欠けています。条件が次の場合:

  • 変更される場合があります
  • アプリケーションまたはシステムの他の部分で繰り返される、または
  • 特定のケースで変更(異なるビルド、テスト、デプロイメントなど)

または、addAlertの内容がより複雑な場合は、たとえばc#のおそらくより良い解決策は次のようになります。

//in some central spot
IEnumerable<Tuple<Func<bool>, int>> Conditions = new ... {
  Tuple.Create(CheckCondition1, 1),
  Tuple.Create(CheckCondition2, 2),
  ...
}

//at the original place
var matchingCondition = Conditions.Where(c=>c.Item1()).FirstOrDefault();
if(matchingCondition != null) 
  addAlert(matchingCondition.Item2)

タプルはc#<8ではそれほど美しくありませんが、便利さのために選択されました。

この方法の利点は、上記のいずれのオプションも適用されない場合でも、構造が静的に型付けされることです。たとえば、elseがないために誤って失敗することはありません。

0
NiklasJ

if->then statementsが多い場合に 循環的複雑度 を削減する最良の方法は、辞書またはリスト(言語に依存)を使用することですキーの値(ステートメントの値または何らかの値の場合)を保存し、次に値/関数の結果を保存します。

たとえば、(C#)の代わりに:

if (i > 10) { return "Two"; }
else if (i > 8) { return "Four" }
else if (i > 4) { return "Eight" }
return "Ten";  //etc etc say anything after 3 or 4 values

私は簡単にできます

var results = new Dictionary<int, string>
{
  { 10, "Two" },
  { 8, "Four"},
  { 4, "Eight"},
  { 0, "Ten"},
}

foreach(var key in results.Keys)
{
  if (i > results[key]) return results.Values[key];
}

より現代的な言語を使用している場合は、より多くのlogicを格納でき、その後、値(c#)も格納できます。これは実際には単なるインライン関数ですが、ロジックがナリにインラインを配置する場合は、他の関数を指すこともできます。

var results = new Dictionary<Func<int, bool>, Func<int, string>>
{
  { (i) => return i > 10; ,
    (i) => return i.ToString() },
  // etc
};

foreach(var key in results.Keys)
{ 
  if (key(i)) return results.Values[key](i);
}
0
Erik Philips

ボブおじさんのすっきりしたコードの提案に従い、特にメソッドを短く保つようにしています。

しかし、私はこのロジックを短くすることができません。

_if (checkCondition()) {addAlert(1);}
else if (checkCondition2()) {addAlert(2);}
else if (checkCondition3()) {addAlert(3);}
else if (checkCondition4()) {addAlert(4);}
_

コードはすでに短すぎますが、ロジック自体は変更しないでください。一見すると、checkCondition()を4回呼び出して繰り返しているように見えますが、コードを注意深く読んだ後では、それぞれが異なることがわかります。適切なフォーマットと関数名を追加する必要があります。例:

_if (is_an_Apple()) {
  addAlert(1);
}
else if (is_a_banana()) {
  addAlert(2);
}
else if (is_a_cat()) {
  addAlert(3);
}
else if (is_a_dog()) {
  addAlert(4);
}
_

あなたのコードは何よりも読みやすいはずです。ボブおじさんの本を何冊か読んだので、それが彼が一貫して伝えようとしているメッセージだと思います。

0
CJ Dennis

「else」句が3つ以上あると、コードのリーダーはチェーン全体を調べて、目的の句を見つける必要があります。次のようなメソッドを使用します。void AlertUponCondition(Condition condition){switch(condition){case Condition.Con1:... break; case Condition.Con2:... break; etc ...}「条件」は適切な列挙型です。必要に応じて、ブール値または値を返します。次のように呼び出します:AlertOnCondition(GetCondition());

本当に簡単にすることはできません。また、いくつかのケースを超えると、if-elseチェーンよりも高速になります。

0
Jimmy T