条件文のこの使用はアンチパターンですか?


14

作業中のレガシーシステムでこれをよく見ました。次のような機能があります。

bool todo = false;
if(cond1)
{
  ... // lots of code here
  if(cond2)
    todo = true;
  ... // some other code here
}

if(todo)
{
  ...
}

つまり、関数には2つの部分があります。最初の部分は何らかの処理(ループ、副作用などを含む可能性があります)を実行し、その途中で「todo」フラグを設定します。2番目の部分は、「todo」フラグが設定されている場合にのみ実行されます。

物事を行うのはかなりlyい方法のように思えます。実際に時間をかけて理解したほとんどのケースは、フラグの使用を避けるためにリファクタリングできると思います。しかし、これは実際のアンチパターンであるか、悪いアイデアであるか、それとも完全に受け入れられるものですか?

最初の明らかなリファクタリングは、2つの方法に分割することです。ただし、私の質問は、ローカルフラグ変数を作成し、複数の場所に潜在的に設定し、後でそれを使用して次のコードブロックを実行するかどうかを決定する必要があるかどうか(現代のOO言語)についてです。


2
どのようにリファクタリングしますか?
タマスゼレイ

13
いくつかの重要な非排他的な条件に従って、todoが複数の場所に設定されていると仮定すると、少しでも理にかなっているリファクタリングはほとんど考えられません。リファクタリングがない場合、アンチパターンはありません。todo変数の命名を除く。「doSecurityCheck」のように、より表現力豊かな名前を付ける必要があります。
user281377

3
@ammoQ:+1; 物事が複雑な場合は、それがどのようであるかです。フラグ変数は、決定が行われたことを明確にするため、状況によってはより意味があり、その決定が行われた場所を見つけるために検索することができます。
ドナルドフェローズ

1
@Donal Fellows:理由の検索が必要な場合、変数をリストにします。空である限り、「false」です。フラグが設定されると、理由コードがリストに追加されます。そのため["blacklisted-domain","suspicious-characters","too-long"]、いくつかの理由が当てはまることを示すようなリストで終わるかもしれません。
user281377

2
私はそれがアンチパターンだとは思わないが、それは間違いなく匂いだ
バイナリウォーリアー

回答:


23

アンチパターンについては知りませんが、これから3つの方法を抽出します。

前者は何らかの作業を実行し、ブール値を返します。

2番目は「他のコード」によって実行される作業を実行します

返されたブール値がtrueの場合、3番目は補助作業を実行します。

抽出されたメソッドは、最初のメソッドがtrueを返した場合に2番目の(常に)を呼び出すことが重要である場合、おそらくプライベートになります。

メソッドに適切な名前を付けることで、コードがより明確になることを願っています。

このようなもの:

public void originalMethod() {
    bool furtherProcessingRequired = lotsOfCode();
    someOtherCode();
    if (furtherProcessingRequired) {
        doFurtherProcessing();
    }
    return;
}

private boolean lotsOfCode() {
    if (cond1) {
        ... // lots of code here
        if(cond2) {
            return true;
        }
    }
    return false;
}

private void someOtherCode() {
    ... // some other code here
}

private void doFurtherProcessing() {
    // Do whatever is needed
}

早期の返品が受け入れられるかどうかについては議論の余地があることは明らかですが、それは実装の詳細です(コードフォーマット標準と同様)。

ポイントは、コードの意図がより明確になり、それが良いことです...

質問に対するコメントの1つは、このパターンが匂いを表していることを示唆しており、それに同意します。意図を明確にすることができるかどうかを確認する価値があります。


2つの関数に分割するには、まだtodo変数が必要であり、おそらく理解するのが難しいでしょう。
Pubby

ええ、私もそうしますが、私の質問は「todo」フラグの使用に関するものでした。
クリケット

2
で終わる場合if (check_if_needed ()) do_whatever ();、明らかなフラグはありません。ただし、コードがかなり単純な場合、これによりコードが大きく分割され、可読性が損なわれる可能性があると思います。結局のところ、あなたが何をするかの詳細はdo_whateverあなたがテストする方法に影響するかもしれないcheck_if_neededので、同じスクリーンフルですべてのコードを一緒に保つことは有用です。また、これはcheck_if_neededフラグの使用を回避できることを保証するものではありません。フラグを使用する場合は、複数のreturnステートメントを使用して、厳密な単一出口の支持者を混乱させる可能性があります。
Steve314

3
@ Pubby8は、これから2つのメソッドを抽出する」と言い、3つのメソッドになりました。実際の処理を行う2つのメソッドと、ワークフローを調整する元のメソッド。これははるかにクリーンなデザインになります。
MattDavey

これ... // some other code hereにより、早期返還の場合は省略されます
カレス

6

さは、単一のメソッドに多くのコードがあり、および/または変数の名前が不適切であるという事実によるものだと思います(どちらもコード自体が臭いです -アンチパターンは、より抽象的で複雑なIMOです)。

したがって、@ Billが示唆するように、ほとんどのコードを下位レベルのメソッドに抽出すると、残りは(少なくとも私には)きれいになります。例えば

bool registrationNeeded = installSoftware(...);
if (registrationNeeded) {
  registerUser(...)
}

または、2番目のメソッドにフラグチェックを非表示にし、次のようなフォームを使用して、ローカルフラグを完全に削除することもできます。

calculateTaxRefund(isTaxRefundable(...), ...)

全体として、ローカルフラグ変数自体が必ずしも悪いとは思わない。上記のどのオプションがより読みやすいか(=私にとって望ましい)は、メソッドパラメーターの数、選択された名前、およびコードの内部ロジックとの一貫性がより高い形式に依存します。


4

todoは変数の本当に悪い名前ですが、それがすべて間違っているのではないかと思います。コンテキストなしで完全に確信することは困難です。

関数の2番目の部分が、最初の部分によって作成されたリストをソートするとします。これははるかに読みやすいはずです:

bool requiresSorting = false;
if(cond1)
{
    ... // lots of code here
    if(cond2)
        requiresSorting = true;
    ... // some other code here
}

if(requiresSorting)
{
    ...
}

ただし、ビルの提案も正しい。これはさらに読みやすいです:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

なぜさらに一歩進んでいないのか:if(BuildList(list))SortList(list);
フィルNデブラン

2

ステートマシンのパターンは私には問題ありません。そこにあるアンチパターンは、「todo」(悪い名前)と「たくさんのコード」です。


ただし、これは単に説明のためのものだと思います。
ローレンペクテル

1
同意した。私が伝えようとしていたのは、貧弱なコードにdrれている良いパターンがコードの品質のせいにされるべきではないということです。
ptyx

1

それは本当に異なります。によって保護されているコードtodo(完全にニーモニックではないので、実際にその名前を使用していないことを願っています!)using代わりに物事を処理するために構築します。

一方、それが概念的にクリーンアップ段階ではなく、時々必要な追加の処理であり、それを行う決定を早期に行う必要がある場合、書かれていることは問題ありません。もちろん、個々のコードチャンクを独自の機能にリファクタリングする方が良いかどうか、また、フラグ変数の名前の意味をキャプチャしたかどうかを検討しますが、この基本的なコードパターンは問題ありません。特に、他の関数に多すぎることをしようとすると、何が起こっているかが明確になりません。これは間違いなくアンチパターンになります。


これは明らかにクリーンアップではありません。常に実行されるとは限りません。私は以前にこのようなケースに出くわしました-何かを処理している間、何らかの事前計算された結果を無効にしてしまうかもしれません。計算が高価な場合は、必要な場合にのみ実行します。
ローレンペクテル

1

ここでの答えの多くは、複雑性チェックに合格するのに苦労します。

これはあなたが見ているものの「アンチパターン」の部分だと思います。コードの循環的な複雑さを測定するツールを見つけてください。Eclipse用のプラグインがあります。これは基本的に、コードのテストがどれだけ難しいかを示す尺度であり、コードブランチの数とレベルが含まれます。

可能な解決策の全体的な推測として、コードのレイアウトは「タスク」で考えさせられますが、これが多くの場所で発生する場合、おそらくあなたが本当に望むのはタスク指向のアーキテクチャです-各タスクは独自のものですオブジェクトおよびタスクの途中で、別のタスクオブジェクトをインスタンス化してキューにスローすることにより、次のタスクをキューに入れることができます。これらはセットアップが驚くほど簡単であり、特定のタイプのコードの複雑さを大幅に軽減しますが、私が言ったように、これは暗闇の中で完全な刺し傷です。


1

上記のpdrの例を使用して、これは良い例なので、さらに一歩進めます。

彼は持っていた:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

だから、私は次のことがうまくいくことに気付きました:

if(BuildList(list)) 
    SortList(list)

しかし、それほど明確ではありません。

元の質問に、なぜ持っていないのですか:

BuildList(list)
SortList(list)

そして、SortListにソートが必要かどうかを判断させますか?

BuildListメソッドは、そのことを示すブール値を返すため、ソートについて知っているようですが、リストを作成するように設計されたメソッドには意味がありません。


そしてもちろん、次のステップは、なぜこれが2ステップのプロセスなのかを尋ねることです。そのようなコードはどこでもBuildAndSortList(list)と呼ばれるメソッドにリファクタリングします
イアン

これは答えではありません。コードの動作を変更しました。
D Drmmr

あんまり。繰り返しますが、私が7年前に投稿したものに返信しているとは信じられませんが、一体何を言っているのかというと、SortListには条件が含まれることです。条件xが満たされた場合にのみリストがソートされると断言する単体テストがある場合、それは合格します。条件をSortListに移動することにより、常に(if(something)then SortList(...))と書く必要がなくなります
イアン

0

はい、フラグをオン/オフにマークしているすべての場所を追跡し続ける必要があるため、これは問題のようです。ロジックを取り出すのではなく、ネストしたif条件としてロジックを内側に含める方が適切です。

また、リッチドメインモデルも、この場合、オブジェクト内で1つのライナーだけで大きな処理を行います。


0

フラグが1回だけ設定されている場合は、
...
コードを
...の直後に移動します

それ以外の場合は
... ... //ここに多くのコード
... //ここに他のコード
...
可能であれば別の関数にコーディングするため、この関数には分岐ロジックという1つの責任があることが明らかです。

可能な限りコードを
... 内で分離します。// ここで多くのコード
を2つ以上の関数に分けます。いくつかの作業(コマンド)と、todo値(クエリ)を返すか、それを行う関数があります。彼らはそれを変更していることを非常に明白にしています(副作用を使用したクエリ)

ここで行われているアンチパターンではないコード自体...分岐ロジックと実際の処理(コマンド)の混在は、あなたが探しているアンチパターンだと思います。


この投稿は、既存の回答が欠落していることを追加しますか?
esoterik

@esoterik小さなCQRSを追加する機会は、フラグに関してしばしば見落とされます。フラグを変更することを決定するロジックはクエリを表し、作業を行うことはコマンドを表します。場合によっては、2つを分離することでコードを明確にすることができます。また、上記のコードで、フラグは1つのブランチでのみ設定されるため、単純化できることを指摘する価値がありました。フラグはアンチパターンではなく、フラグの名前が実際にコードの表現力を高めるのであれば、良いことだと思います。フラグが作成、設定、および使用される場所は、可能な場合、コード内で互いに近づける必要があります。
アンドリューパテ

0

このパターンを実装する必要がある場合があります。操作を続行する前に複数のチェックを実行したい場合があります。効率上の理由から、特定のチェックを含む計算は、チェックが絶対に必要と思われる場合を除いて実行されません。したがって、通常、次のようなコードが表示されます。

// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(valuesExist) {
    try {
      // Attempt insertion
      trx.commit();
    } catch (DatabaseException dbe) {
      trx.rollback();
      throw dbe;
    }
  } else {
    closeConnection(db);
    throwException();
  }
} else {
  closeConnection(db);
  throwException();
}

これは、検証を操作を実行する実際のプロセスから分離することで簡略化できるため、次のように表示されます。

boolean proceed = true;
// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(!valuesExist) {
    proceed = false;
  }
} else {
  proceed = false;
}

// The moment of truth
if(proceed) {
  try {
    // Attempt insertion
    trx.commit();
  } catch (DatabaseException dbe) {
    trx.rollback();
    throw dbe;
  }
} else {
  if(db.isOpen()) {
    closeConnection(db);
  }
  throwException();
}

明らかに、達成しようとしているものによって異なります。このように書かれていても、「成功」コードと「失敗」コードの両方が一度書き込まれるため、ロジックが簡素化され、同じレベルのパフォーマンスが維持されます。そこから、検証をすべてのレベルの内部メソッドに適合させて、成功または失敗のブール表示を返すことにより、事態をさらに単純化することをお勧めします。


あなたが与えた例では、答えを返す関数shouldIDoIt(fieldsValidated、valuesExist)が欲しいと思います。これは、進行中の決定がいくつかの異なる不連続スポットに散らばっている私がここで見ているコードとは対照的に、yes / noの決定がすべて一度に行われるためです。
クリケット

@KelseyRider、それがまさにポイントでした。検証を実行から分離すると、プログラムの全体的なロジックをif(isValidated())doOperation()に簡素化するためにロジックをメソッドに詰めることができます
ニール・

0

これはパターンではありません。最も一般的な解釈は、ブール変数を設定し、後でその値に分岐するということです。それは通常の手続き型プログラミングであり、それ以上のものではありません。

これで、特定の例を次のように書き換えることができます。

if(cond1)
{
    ... // lots of code here
    ... // some other code here
    if (cond2)
    {
        ...
    }
}

それは読みやすいかもしれません。またはそうでないかもしれません。省略した残りのコードに依存します。そのコードをより簡潔にすることに集中してください。


-1

制御フローに使用されるローカルフラグはgoto、変装の形式として認識される必要があります。フラグが関数内でのみ使用される場合、関数の2つのコピーを書き出し、1つを「flag is true」、もう1つを「flag is false」としてラベル付けし、フラグを設定するすべての操作を置換することで削除できます。関数の2つのバージョン間をジャンプして、クリアの場合、または設定されている場合にクリアします。

多くの場合、フラグの使用を使用するコードは、goto代わりに使用する可能性のある代替手段よりもクリーンになりますが、常にそうとは限りません。場合によっては、gotoフラグを使用してコードをスキップする方が、フラグを使用するよりもクリーンな場合があります(ただし、特定の猛禽漫画をここに挿入する人もいます)。

主な指針は、プログラムロジックフローがビジネスロジックの記述に可能な限り似ている必要があるということです。奇妙な方法で分割およびマージする状態に関してビジネスロジック要件が説明されている場合、プログラムロジックを実行することは、フラグを使用してそのようなロジックを非表示にするよりもクリーンになる可能性があります。一方、ビジネスルールを記述する最も自然な方法が、特定の他のアクションが実行された場合にアクションを実行する必要があると言う場合、それを表現する最も自然な方法は、実行時に設定されるフラグを使用することです後者のアクション、およびそれ以外は明確です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.