フラグ変数は絶対的な悪ですか?[閉まっている]


47

フラグ変数は悪ですか?次の種類の変数は非常に不道徳であり、それらを使用するのは邪悪ですか?

「特定の場所で値を割り当てた後でブール値または整数変数を下にチェックしてから、何かをするかどうかをチェックしてから、たとえばnewItem = true以下の行を使用するif (newItem ) then


フラグの使用を完全に無視して、より良いアーキテクチャ/コードになったプロジェクトをいくつか行ったことを覚えています。しかし、それは私が働いている他のプロジェクトでは一般的な慣行であり、コードが大きくなりフラグが追加されると、IMHOコードスパゲッティも大きくなります。

フラグを使用するのが良い習慣である、あるいは必要な場合があると言いますか?またはコードでフラグを使用することは...赤いフラグであり、回避/リファクタリングする必要があることに同意しますか?私は、代わりにリアルタイムで状態をチェックする関数/メソッドを実行するだけでうまくいきます。


9
MainMaと私は「フラグ」の定義が異なるようです。プリプロセッサ#ifdefsを考えていました。どっちに聞いた?
カールビーレフェルト

これは本当に良い質問です。私はこれを自分自身でとても不思議に思っており、実際には「ああ、フラグを使うだけでいい」と言っていることに気づきました。
ポールリヒター

ブール値はフラグです。(整数もそうです...)
トーマスエディング

7
私はOPはあなたがダウンし、使用して、たとえば、何かかどうか、などを行うためにortherに、次にチェックの下に特定の場所に値を割り当てることをブールする参照のうえや変数の整数であると信じて@KarlBielefeldt newItem = true下にし、いくつかの線をif (newItem ) then
Tulainsコルドバ

1
また、このコンテキストでの変数リファクタリングの説明を紹介してください。メソッドが短く、パスの数が少ない限り、これは有効な使用方法であると考えています。
ダニエルB

回答:


41

フラグを使用するコードを保守するときに見た問題は、状態の数が急速に増加し、ほとんどの場合未処理の状態があることです。私自身の経験からの1つの例:私はこれらの3つのフラグを持つコードに取り組んでいました

bool capturing, processing, sending;

これら3つは8つの状態を作成しました(実際には、他にも2つのフラグがありました)。考えられるすべての値の組み合わせがコードでカバーされているわけではなく、ユーザーにはバグが表示されていました。

if(capturing && sending){ // we must be processing as well
...
}

上記のifステートメントの仮定が間違っている状況があることが判明しました。

フラグは時間とともに複雑になる傾向があり、クラスの実際の状態を隠します。だからこそ避けるべきです。


3
+1、「避けるべきです」。「しかし、状況によってはフラグが必要」(「必要な悪」と言う人もいます)について何かを追加します
トレバーボイドスミス

2
@TrevorBoydSmith私の経験ではそうではありませんが、あなたはフラグに使用する平均的な脳力より少しだけ多く必要です
-dukeofgaming

あなたの例では、3つのブール値ではなく、状態を表す単一の列挙型である必要がありました。
user949300 14

私が今直面している問題と同様の問題に遭遇するかもしれません。すべての可能な状態をカバーすることに加えて、2つのアプリケーションが同じフラグを共有する場合があります(たとえば、顧客データのアップロード)。この場合、フラグを使用してアップロードするのは1人のアップローダーのみであり、将来的に問題を見つけることができます。
アラン

38

フラグが役立つ場合の例を次に示します。

パスワードを生成するコードがあります(暗号的に安全な擬似乱数ジェネレーターを使用)。メソッドの呼び出し元は、パスワードに大文字、小文字、数字、基本記号、拡張記号、ギリシャ語記号、キリル文字、およびUnicodeを含めるかどうかを選択します。

フラグを使用すると、このメソッドを簡単に呼び出すことができます。

var password = this.PasswordGenerator.Generate(
    CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);

さらに、次のように簡略化することもできます。

var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);

フラグがなければ、メソッドのシグネチャは何でしょうか?

public byte[] Generate(
    bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
    bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);

このように呼び出されます:

// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
    true, true, true, false, false, false, false, false);

コメントで述べたように、別のアプローチはコレクションを使用することです。

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LowercaseLetters,
        CharacterSet.UppercaseLetters,
    });

これは、trueおよびのセットに比べてはるかに読みやすいですfalseが、まだ2つの欠点があります。

主な欠点は、結合された値を許可するためにCharacterSet.LettersAndDigitsGenerate()メソッドでそのようなものを書くことです:

if (set.Contains(CharacterSet.LowercaseLetters) ||
    set.Contains(CharacterSet.Letters) ||
    set.Contains(CharacterSet.LettersAndDigits) ||
    set.Contains(CharacterSet.Default) ||
    set.Contains(CharacterSet.All))
{
    // The password should contain lowercase letters.
}

おそらく次のように書き換えられます:

var lowercaseGroups = new []
{
    CharacterSet.LowercaseLetters,
    CharacterSet.Letters,
    CharacterSet.LettersAndDigits,
    CharacterSet.Default,
    CharacterSet.All,
};

if (lowercaseGroups.Any(s => set.Contains(s)))
{
    // The password should contain lowercase letters.
}

フラグを使用して、これをあなたの持っているものと比較してください:

if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
    // The password should contain lowercase letters.
}

2番目の非常に小さな欠点は、次のように呼び出された場合にメソッドがどのように動作するかが明確でないことです。

var password = this.PasswordGenerator.Generate(
    new []
    {
        CharacterSet.Digits,
        CharacterSet.LettersAndDigits, // So digits are requested two times.
    });

10
私はOPは、のように、あなたが何かをするかしないかをortherに、次にチェックダウンの下に、あなたが特定の場所に値を割り当てることをブールまたは整数の変数への参照のうえ、たとえば使用していると信じてnewItem = true、その後、以下のいくつかのラインif (newItem ) then
Tulainsコルドバ

1
@MainMaどうやら3番目があります:8個のブール引数を持つバージョンは、「フラグ」を読んだときに考えていたものです
...-Izkata

4
申し訳ありませんが、これはメソッドチェーン(en.wikipedia.org/wiki/Method_chaining)に最適なケースです。さらに、パラメーター配列(連想配列またはマップである必要があります)を使用できます。省略すると、そのパラメーターのデフォルト値の動作が使用されます。最終的に、メソッドチェーンまたはパラメーター配列を介した呼び出しは、ビットフラグと同じくらい簡潔で表現力に富んでいます。また、すべての言語にビット演算子があるわけではありません(実際にはバイナリフラグが好きですが、代わりに上記のメソッドを使用します)。
-dukeofgaming

3
それはあまりOOPではありませんか?インターフェイスalaを作成します。String myNewPassword = makePassword(randomComposeSupplier(new RandomLowerCaseSupplier()、new RandomUpperCaseSupplier()、new RandomNumberSupplier)); with String makePassword(Supplier <Character> charSupplier); およびSupplier <Character> randomComposeSupplier(Supplier <Character> ... supplier); サプライヤを他のタスクに再利用し、好きなように構成し、generatePasswordメソッドを簡素化して最小状態を使用できるようになりました。
ディベケ

4
およそ@Dibbekeの話名詞の王国 ...
フィル・

15

巨大な機能ブロックは匂いであり、フラグではありません。5行目にフラグを設定した場合、354行目にのみフラグをチェックしますが、それは悪いことです。行8でフラグを設定し、行10でフラグを確認する場合は問題ありません。また、コードのブロックごとに1つまたは2つのフラグは問題ありませんが、関数内の300フラグは不良です。


10

通常、フラグは、フラグの可能な値ごとに1つの戦略実装を使用して、戦略パターンのフレーバーに完全に置き換えることができます。これにより、新しい動作を簡単に追加できます。

パフォーマンスが重要な状況では、インダイレクションのコスト表面化し、解体を必要な明確なフラグにする可能性があります。そうは言っても、実際にそれをしなければならなかった単一のケースを思い出すのに苦労しています。


6

いいえ、フラグは悪いものでも悪いものでもありません。

JavaのPattern.compile(String regex、int flags)呼び出しを検討してください。これは従来のビットマスクであり、機能します。java の定数と、フラグがあることを知っている2 nの束がどこにあるかを見てください。

理想的なリファクタリングされた世界では、代わりに列挙型の値を使用する列挙セットを使用します。

このクラスの空間と時間のパフォーマンスは、従来のintベースの「ビットフラグ」に代わる高品質で型安全な代替として使用できるように十分に優れている必要があります。

完全な世界では、そのPattern.compile呼び出しはになりPattern.compile(String regex, EnumSet<PatternFlagEnum> flags)ます。

とはいえ、まだフラグが立っています。フラグが実際に何のためであるか、または他の何らかのスタイルを実行しようとするPattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)よりも、作業がはるかに簡単Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())です。

フラグは、システムレベルのものを操作するときによく見られます。オペレーティングシステムレベルで何かとインターフェイスをとる場合、プロセスの戻り値、ファイルのアクセス許可、ソケットを開くためのフラグなど、どこかにフラグがある可能性があります。認識されたコードの匂いに対するいくつかの魔女狩りでこれらのインスタンスをリファクタリングしようとすると、フラグを受け入れて理解したものよりも悪いコードになる可能性があります。

この問題は、人々がフラグを誤使用して、それらを一緒に投げ、あらゆる種類の無関係なフラグのフランケンフラグセットを作成するか、フラグがまったくない場所でフラグを使用しようとするときに発生します。


5

メソッドシグネチャ内のフラグについて話していると思います。

単一のフラグを使用するだけで十分です。

同僚が最初にそれを見たときに何も意味しません。彼らは、メソッドの動作を確立するために、メソッドのソースコードを調べる必要があります。あなたの方法が何であるかを忘れたとき、あなたはおそらく数ヶ月下の同じ位置にいるでしょう。

メソッドにフラグを渡すことは、通常、メソッドが複数のことを担当していることを意味します。メソッド内では、おそらく次の行で簡単なチェックを実行しています。

if (flag)
   DoFlagSet();
else
   DoFlagNotSet();

それは懸念事項の分離が不十分であり、通常はその回避策を見つけることができます。

通常、2つの別々のメソッドがあります。

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

これは、解決しようとしている問題に適用可能なメソッド名を使用する方が理にかなっています。

複数のフラグを渡すのは2倍悪いです。本当に複数のフラグを渡す必要がある場合は、それらをクラス内にカプセル化することを検討してください。その場合でも、メソッドは複数のことを行っている可能性が高いため、同じ問題に直面することになります。


3

フラグとほとんどの一時変数は強い臭いです。ほとんどの場合、リファクタリングしてクエリメソッドに置き換えることができます。

改訂:

状態を表現するときのフラグと一時変数は、クエリメソッドにリファクタリングする必要があります。状態の値(ブール値、整数、およびその他のプリミティブ)は、実装の詳細の一部としてほとんど常に非表示にする必要があります。

制御、ルーティング、および一般的なプログラムフローに使用されるフラグは、制御構造のセクションを個別の戦略またはファクトリー、または状況に応じて適切なクエリメソッドを使用し続けるものにリファクタリングする機会を示す場合もあります。


2

フラグについて話すときは、プログラムの実行中にフラグが変更され、状態に基づいてプログラムの動作に影響を与えることを知っておく必要があります。これら2つのことをきちんと制御できる限り、うまく機能します。

フラグは

  • 適切なスコープでそれらを定義しました。適切とは、スコープに、変更する必要のない/すべきでないコードを含めないことを意味します。または、少なくともコードは安全です(たとえば、外部から直接呼び出すことはできません)
  • 外部からフラグを処理する必要があり、多くのフラグがある場合、フラグを安全に変更する唯一の方法としてフラグハンドラをコーディングできます。このフラグハンドラー自体が、フラグとメソッドをカプセル化してそれらを変更します。その後、シングルトンにし、フラグへのアクセスが必要なクラス間で共有できます。
  • 最後に、メンテナンス性のために、フラグが多すぎる場合:
    • 彼らは賢明な命名に従うべきだと言う必要はありません
    • 有効な値を列挙する必要があります(列挙型の場合があります)
    • どのコードがそれらをそれぞれ修正するか、またどの条件でフラグに特定の値が割り当てられるかを文書化する必要があります。
    • どのコードがそれらを消費し、特定の値に対してどのような動作が生じるか

フラグがたくさんある場合は、フラグから適切な設計作業を行う必要があります。その後、プログラムの動作でキーの役割を果たし始めます。モデリング用の状態図に行くことができます。このような図は、それらを扱う際のドキュメントおよび視覚的なガイダンスとしても機能します。

これらのものが適切に配置されている限り、混乱を招くことはないと思います。


1

質問から、QAはフラグ(グローバル)変数を意味し、関数パラメーターのビットを意味していないと仮定しました。

他の多くの可能性がない状況があります。たとえば、オペレーティングシステムがないと、割り込みを評価する必要があります。割り込みが非常に頻繁に発生し、ISRで長時間の評価を行う時間がない場合は、許可されるだけでなく、ISRで一部のグローバルフラグのみを設定することをお勧めします(できるだけ少ない時間を費やす必要があります) ISRで)、メインループでこれらのフラグを評価します。


0

プログラミングにおいて絶対に悪いことはないと思います。

フラグが順番に並んでいる別の状況がありますが、ここではまだ言及していません...

このJavascriptスニペットでのクロージャーの使用を検討してください。

exports.isPostDraft = function ( post, draftTag ) {
  var isDraft = false;
  if (post.tags)
    post.tags.forEach(function(tag){ 
      if (tag === draftTag) isDraft = true;
    });
  return isDraft;
}

「Array.forEach」に渡される内部関数は、単に「trueを返す」ことはできません。

したがって、フラグを使用して状態を外部に保持する必要があります。

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