web-dev-qa-db-ja.com

フラグ変数は絶対的な悪ですか?

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

「特定の場所で値を割り当て、その下でチェックインしてから、何かを実行するかどうかをチェックするブール変数または整数変数。たとえば、_newItem = true_を使用してから、if (newItem ) thenの下のいくつかの行を使用する」


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

フラグを使用することが良い方法である、または必要な場合さえあると思いますか?または、コードでフラグを使用することは...レッドフラグであり、回避/リファクタリングする必要があることに同意します。私は、代わりにリアルタイムで状態をチェックする関数/メソッドを実行することで問題を解決しています。

49
dukeofgaming

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

bool capturing, processing, sending;

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

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

上記のifステートメントの仮定が誤っている状況があることがわかりました。

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

42
Ben

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

パスワードを生成するコードがあります(暗号的に安全な疑似乱数ジェネレータを使用)。メソッドの呼び出し元は、パスワードに大文字、小文字、数字、基本記号、拡張記号、ギリシャ記号、キリル文字、および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,
    });
_

これはtruefalseのセットに比べてはるかに読みやすくなっていますが、それでも2つの欠点があります。

主な欠点は、_CharacterSet.LettersAndDigits_のような結合された値を許可するために、Generate()メソッドにそのようなものを記述することです。

_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.
    });
_
38

巨大な機能ブロックは、フラグではなくにおいです。 5行目でフラグを設定した場合、354行目でフラグを確認するだけで問題が発生します。 8行目でフラグを設定し、10行目でフラグを確認すると、問題ありません。また、コードのブロックごとに1つまたは2つのフラグで問題ありません。関数内の300個のフラグは不適切です。

15
nbv4

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

パフォーマンスが重要な状況では、間接参照のコストmightが表面化し、解体してフラグをクリアする必要があります。それは私が実際にそれをしなければならなかった一つのケースを思い出すのに苦労していると言われています。

10
back2dos

いいえ、フラグは悪くはありませんし、あらゆる犠牲を払ってリファクタリングしなければならない悪でもありません。

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

理想的なリファクタリングの世界では、代わりに EnumSet を使用します。定数は列挙型の値であり、ドキュメントに次のように記載されています。

このクラスの空間と時間のパフォーマンスは、従来の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())や、フラグが実際に何のために役立つかを試行する他のスタイルを使用するよりもはるかに簡単です。

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

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

6
user40980

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

フラグを1つ使用するだけでは十分ではありません。

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

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

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

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

通常、2つの方法があります。

public void DoFlagSet() 
{
}

public void DoFlagNotSet()
{
}

これは、解決している問題に適用できるメソッド名でより意味があります。

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

5
CodeART

フラグとほとんどの一時変数は強いにおいです。ほとんどの場合、それらはリファクタリングされ、クエリメソッドに置き換えられます。

改訂:

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

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

3
JustinC

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

フラグは、

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

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

これらが整っている限り、混乱につながらないと思います。

2
Maha

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

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

1
vsz

anythingはプログラミングにおいて絶対的な悪だとは思いません。

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

この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を返す」ことはできません。

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

0
firstdoit