web-dev-qa-db-ja.com

ブールパラメータを使用して動作を決定するのは間違っていますか?

私は時々「間違っている」と感じる習慣を見てきましたが、私はそれについて何が悪いのかをはっきりと述べることはできません。あるいは、それは私の偏見にすぎないのかもしれません。ここに行く:

開発者はブール値をパラメーターの1つとして使用してメソッドを定義し、そのメソッドが別のパラメーターを呼び出すなどし、最終的にそのブール値は、特定のアクションを実行するかどうかを決定するためにのみ使用されます。これは、たとえば、ユーザーが特定の権限を持っている場合にのみアクションを許可する場合や、テストモードまたはバッチモードまたはライブモードの場合(またはそうでない場合)、またはシステムが特定の状態。

(パラメーターを渡すのではなく)アクションを実行するタイミングを照会するか、メソッドの複数のバージョンを使用するか、クラスの複数の実装を使用するかなど、常に別の方法があります。私の質問はこれを改善する方法はそれほど多くはありませんが、それが本当に間違っているかどうか(私が思っているように)、そして間違っている場合は、何が悪いのでしょうか。

203
Ray

はい、これはコードの臭いである可能性が高く、メンテナンスが困難なコードにつながり、理解が難しく、簡単に再利用される可能性が低くなります。

他のポスターがコンテキストはすべてであると指摘しているように(1回限りの場合、または慣行が意図的に技術的負債を負ったと再確認される場合は、強引に進めないでください。後で因数分解されますが、大まかに言えば、実行する特定の動作を選択する関数に渡されるパラメーターがある場合は、さらに段階的な調整が必要です。この関数を小さな関数に分割すると、より凝集性の高い関数が生成されます。

では、凝集性の高い機能とは何でしょうか。

これは、1つのことだけを実行する機能です。

あなたが説明したように渡されたパラメータの問題は、関数が3つ以上のことをしていることです。ブールパラメータの状態に応じて、ユーザーのアクセス権をチェックする場合とチェックしない場合があります。その決定ツリーに応じて、機能の一部を実行します。

アクセス制御の懸念事項を、タスク、アクション、またはコマンドの懸念事項から分離することをお勧めします。

すでにお気づきのように、これらの懸念が絡み合っているようには見えません。

したがって、凝集性の概念は、問題の関数がそれほど凝集性ではなく、コードをリファクタリングしてより凝集性の高い関数のセットを生成できることを識別するのに役立ちます。

そのため、質問は言い換えることができます。行動選択パラメータを渡すことはすべて避けることに同意することを考えると、問題をどのように改善するのですか

パラメータを完全に削除します。テストのためにアクセス制御をオフにする機能を持つことは、潜在的なセキュリティリスクです。 テスト目的では、 stub または mock アクセス許可シナリオとアクセス拒否シナリオの両方をテストするためのアクセスチェック

参照: 凝集度(コンピュータサイエンス)

112
Rob Kielty

非常に単純な理由で、私はずっと前にこのパターンの使用をやめました。メンテナンス費用。コードで何度も呼び出される関数frobnicate(something, forwards_flag)がいくつかの関数にあり、値としてfalseが値として渡されたコード内のすべての場所を見つける必要があることが何度かわかりましたforwards_flag。それらを簡単に検索することはできないため、これはメンテナンスの頭痛になります。そして、それらの各サイトでバグ修正を行う必要がある場合、1つを見逃すと不幸な問題が発生する可能性があります。

ただし、この特定の問題は、アプローチを根本的に変更することなく簡単に修正できます。

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

このコードを使用すると、FrobnicateBackwardsのインスタンスを検索するだけで済みます。これを変数に割り当てるコードがいくつかある可能性があるので、いくつかの制御スレッドをたどる必要がありますが、実際には、この代替方法で問題なく動作することはまれであることを発見しました。

ただし、少なくとも原則として、この方法でフラグを渡すことには別の問題があります。これは、この設計を持つ一部の(一部のみの)システムが、(フラグを使用する)コードの深くネストされた部分の実装の詳細に関する多くの知識を外側のレイヤー(どの値を渡すかを知る必要がある)に公開している可能性があるということですこのフラグで)。ラリーコンスタンティンの用語を使用するために、この設計では、セッターとブールフラグのユーザーとの間に、過度に強い coupling が存在する場合があります。フランキーは、コードベースの詳細を知らずにこの質問についてある程度の確実性をもって言うことは困難です。

あなたが与える具体的な例に取り組むために、私はそれぞれにある程度の懸念がありますが、主にリスク/正確さの理由のためです。つまり、システムがシステムの状態を示すフラグを渡す必要がある場合、これを考慮に入れるべきであるが、パラメーターをチェックしないコードを取得している可能性があります(パラメーターが渡されなかったため)この関数)。パラメータを渡すために誰かが省略したので、あなたにはバグがあります。

また、ほぼすべての関数に渡す必要があるシステム状態インジケーターが、実際には本質的にグローバル変数であることも認める価値があります。グローバル変数の欠点の多くが当てはまります。多くの場合、システム状態(またはユーザーの資格情報、またはシステムのID)の知識を、そのデータに基づいて正しく動作する役割を担うオブジェクト内にカプセル化することをお勧めします。次に、生データではなく、そのオブジェクトへの参照を渡します。ここでの重要な概念は カプセル化 です。

153
James Youngman

これは必ずしも間違っているわけではありませんが、 コードのにおい を表す場合があります。

ブールパラメータに関して回避すべき基本的なシナリオは次のとおりです。

_public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}
_

次に、呼び出すときに、通常、必要な動作に応じてfoo(false)およびfoo(true)を呼び出します。

凝集度が悪いため、これは本当に問題です。本当に必要ではないメソッド間の依存関係を作成しています

この場合にすべきことは、doThisdoThatを別々のパブリックメソッドとして残し、次のようにすることです。

_doThis();
doThat();
_

または

_doThis();
_

このようにして、結合を作成することなく、正しい決定を呼び出し元に任せます(ブール値パラメーターを渡す場合とまったく同じです)。

もちろん、すべてのブール型パラメーターがこのように悪い方法で使用されるわけではありませんが、それは間違いなくコードのにおいであり、ソースコードに多く見られる場合は疑わしいことは間違いありません。

これは私が書いた例に基づいてこの問題を解決する方法のほんの一例です。別のアプローチが必要になる他のケースがあります。

同じアイデアをさらに詳しく説明する優れた Martin Fowlerの記事 があります。

PS:2つの単純なメソッドを呼び出す代わりにメソッドfooがより複雑な実装を持っていた場合、必要なのは小さなリファクタリングを適用すること メソッドの抽出 なので、結果のコードは実装に似ています私が書いたfooの。

39
Alex

まず、プログラミングは芸術なので、プログラミングは科学ではありません。そのため、「間違った」方法や「正しい」方法でプログラミングすることはほとんどありません。ほとんどのコーディング標準は、一部のプログラマーが有用だと考える「設定」にすぎません。しかし、最終的にはかなり恣意的です。そのため、パラメーターの選択にそれ自体が「間違っている」とラベルを付けることは決してありません。確かに、ブールパラメーターほど一般的で有用なものではありません。状態をカプセル化するためのboolean(またはint)の使用は、多くの場合、完全に正当化できます。

概して、コーディングの決定は、主にパフォーマンスと保守性に基づく必要があります。パフォーマンスが危機に瀕していない場合(そして、例でそれがどのようになるか想像もできない)、次の考慮事項は次のとおりです。これは、私(または将来の編集者)がどれだけ簡単に保守できるか?直感的でわかりやすいですか?孤立していますか?チェーンされた関数呼び出しの例は、この点で実際に壊れやすいように思われます。bIsUpbIsDownに変更する場合、コード内の他の何箇所も変更する必要がありますか?また、あなたのパラメータリストは膨れ上がっていますか?関数に17個のパラメーターがある場合、読みやすさが問題となり、オブジェクト指向アーキテクチャーの利点を理解しているかどうかを再検討する必要があります。

30
kmote

Robert C Martins クリーンコード記事 は、メソッドが複数のことをしていることを示すので、可能な場合はブール引数を削除する必要があると述べています。メソッドは1つのことを行う必要があり、1つだけは彼のモットーの1つだと思います。

16
dreza

ここで最も重要なことは、実用的であることです。

ブール値が動作全体を決定したら、2番目のメソッドを作成します。

ブール値が途中で動作を少しだけ決定する場合、コードの重複を削減するためにブール値を1つに保つことができます。可能な場合は、メソッドを3つに分割することもできます。ブール値オプションの2つの呼び出しメソッドと、作業の大部分を行うメソッド。

例えば:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

もちろん、実際には、これらの両極端の間には常にポイントがあります。通常、私は正しいと思うものをそのまま使用しますが、コードの重複が少ないという点で誤りを犯すことを好みます。

8
Jorn

不変のインスタンスを返すビルダーメソッドを介して動作をカスタマイズするアプローチが好きです。 Guava Splitter が使用する方法は次のとおりです。

_private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");
_

これの利点は次のとおりです。

  • 優れた読みやすさ
  • 構成メソッドとアクションメソッドの明確な分離
  • オブジェクトが何であるか、何をすべきか、何をすべきでないかを考えるように強制することにより、まとまりを促進します。この場合はSplitterです。 someVaguelyStringRelatedOperation(List<Entity> myEntities)Splitterというクラスに配置することはありませんが、静的メソッドとしてStringUtilsクラスに配置することを考えます。
  • インスタンスは事前に構成されているため、依存関係を簡単に注入できます。クライアントは、trueまたはfalseをメソッドに渡して正しい動作を取得するかどうかを心配する必要はありません。
7
oksayt

間違いなく コードのにおい単一の責任の原則 に違反していない場合は、おそらく Tell、Do n't Ask に違反しています。考慮してください:

これら2つの原則のいずれかに違反していないことが判明した場合でも、列挙型を使用する必要があります。ブールフラグは マジックナンバー に相当するブール値です。 foo(false)bar(42)と同じくらい意味があります。列挙型は Strategy Pattern に役立ち、別の戦略を追加できる柔軟性があります。 (ただ覚えておいてください 適切な名前を付けてください 。)

あなたの特定の例は特に私を悩ませます。このフラグが非常に多くのメソッドを通過するのはなぜですか? パラメータをサブクラスに分割する する必要があるようです。

5
Eva

TL; DR:ブール引数を使用しないでください。

以下を参照してくださいなぜそれらが悪いのか、それらを置き換える方法(太字)。


ブール引数は非常に読みにくいため、保守が困難です。主な問題は、引数が指定されているメソッドのシグネチャを読み取ると、目的が一般的に明確になることです。ただし、ほとんどの言語では、パラメータに名前を付ける必要はありません。したがって、 RSACryptoServiceProvider#encrypt(Byte[], Boolean) のようなアンチパターンがあり、ブールパラメーターは関数で使用する暗号化の種類を決定します。

したがって、次のような呼び出しが行われます。

_rsaProvider.encrypt(data, true);
_

読者がメソッドのシグネチャを検索して、単にtrueが実際に何を意味するかを判断する必要がある場合。整数を渡すことはもちろん同じように悪いです:

_rsaProvider.encrypt(data, 1);
_

同じように、またはむしろ:少しだけあなたに伝えます。整数に使用する定数を定義したとしても、関数のユーザーはそれらを単に無視してリテラル値を使い続けるかもしれません。

これを解決する最良の方法は、列挙を使用することです。列挙型RSAPaddingを2つの値で渡す必要がある場合:OAEPまたは_PKCS1_V1_5_すると、すぐにコードを読み取ることができます。

_rsaProvider.encrypt(data, RSAPadding.OAEP);
_

ブール値は2つの値のみを持つことができます。つまり、3番目のオプションがある場合は、署名をリファクタリングする必要があります。一般に、下位互換性が問題になる場合、これは簡単に実行できません。そのため、パブリッククラスを別のパブリックメソッドで拡張する必要があります。これがMicrosoftが RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding) を導入したときに、ブール値ではなく列挙型(または少なくとも列挙型を模倣するクラス)を使用したときに、これが最後に行ったものです。

パラメーター自体をパラメーター化する必要がある場合は、パラメーターとして完全なオブジェクトまたはインターフェイスを使用する方が簡単な場合もあります。上記の例では、OAEPパディング自体をハッシュ値でパラメーター化して、内部で使用できます。現在、6つのSHA-2ハッシュアルゴリズムと4つのSHA-3ハッシュアルゴリズムがあるため、パラメーターではなく単一の列挙型のみを使用すると、列挙値の数が急増する可能性があります(これは、Microsoftが次に調べることになる可能性があります) )。


ブールパラメータは、メソッドまたはクラスが適切に設計されていないことを示す場合もあります。上記の例と同様:.NETライブラリ以外の暗号ライブラリは、メソッドシグネチャでパディングフラグをまったく使用しません。


私が好きなほとんどすべてのソフトウェアの教祖は、ブール引数に対して警告します。たとえば、Joshua Blochは、高く評価されている本「Effective Java」で警告を発しています。一般に、それらは単に使用されるべきではありません。理解しやすいパラメーターが1つある場合に使用できると主張できます。しかしそれでも:Bit.set(boolean)はおそらく2つのメソッドを使用して実装するほうがよい:Bit.set()Bit.unset()


コードを直接リファクタリングできない場合は、定数を定義して少なくともそれらを読みやすくすることができます:

_const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);
_

より読みやすいです:

_cipher.init(key, true);
_

あなたがしたい場合でも:

_cipher.initForEncryption(key);
cipher.initForDecryption(key);
_

代わりに。

4
Maarten Bodewes

named-parametersについて誰も言及していないことに驚いています。

ブールフラグで見られる問題は、読みやすさが損なわれることです。たとえば、trueは何をしますか

myObject.UpdateTimestamps(true);

行う?何も思いつきません。しかし、どうですか:

myObject.UpdateTimestamps(saveChanges: true);

これで、渡すパラメーターの意味が明確になりました。変更を保存するように関数に指示しています。この場合、クラスが非公開であれば、ブール値のパラメーターで問題ないと思います。


もちろん、クラスのユーザーに名前付きパラメーターの使用を強制することはできません。このため、デフォルトのケースがどれほど一般的であるかに応じて、通常はenumまたは2つの個別の方法のいずれかが推奨されます。これはまさに.Netがすることです:

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 

何が悪いのかはっきりとは言えません。

それがコードのにおいのように見え、コードのにおいのように感じ、そして-まあ-コードのにおいのようににおいがする場合、それはおそらくコードのにおいです。

あなたがしたいことは:

1)副作用のあるメソッドは避けてください。

2)必要な状態を、中央の正式な状態マシン( this など)で処理します。

1
BaBu

ブールパラメータを使用してパフォーマンスを決定しないことのすべての懸念に同意します。改善、可読性、信頼性、複雑さの低減、不十分なカプセル化と凝集によるリスクの低減、および保守性を伴う総所有コストの低減。

70年代半ばにハードウェアの設計を開始しました。現在はSCADA(監視制御とデータ取得)と呼んでいます。これらは、マクロリモートコントロールを実行し高速データを収集するEPROMのマシンコードを備えた微調整されたハードウェアでした。

ロジックは MealeyMoore 現在呼び出すマシン 有限状態マシン 。これらも上記と同じルールで実行する必要があります。ただし、実行時間が有限のリアルタイムマシンであり、目的を果たすためにショートカットを実行する必要がある場合を除きます。

データは同期ですが、コマンドは非同期であり、コマンドロジックはメモリレスブールロジックに従いますが、前の、現在の、および望ましい次の状態のメモリに基づいた順次コマンドがあります。これを最も効率的な機械語(64kBのみ)で機能させるために、ヒューリスティックなIBM HIPO方式ですべてのプロセスを定義するように細心の注意が払われました。これは、ブール変数を渡し、インデックス付きブランチを実行することを意味する場合がありました。

しかし、大量のメモリとOOKの容易さにより、カプセル化は今日の必須の要素ですが、リアルタイムおよびSCADAマシンコードのコードがバイト単位でカウントされた場合のペナルティ。

これは必ずしも間違っているわけではありませんが、「ユーザー」の属性に応じたアクションの具体例では、フラグではなくユーザーへの参照を渡します。

これにより、さまざまな点で明確になり、役立ちます。

呼び出しステートメントを読んだ人なら誰でも、結果はユーザーによって異なることに気付くでしょう。

最終的に呼び出される関数では、任意のユーザー属性にアクセスできるため、より複雑なビジネスルールを簡単に実装できます。

「チェーン」内の1つの関数/メソッドがユーザー属性に応じて何か異なる場合、ユーザー属性への同様の依存関係が「チェーン」内の他のいくつかのメソッドに導入される可能性が非常に高くなります。

0
James Anderson

ほとんどの場合、私はこの悪いコーディングを検討します。ただし、2つのケースを考えることができます。なぜそれが悪いのかについてすでに多くの答えがあるので、私はそれが良いかもしれないときに2回提供します:

1つ目は、シーケンス内の各呼び出しがそれ自体で意味があるかどうかです。呼び出しコードmightがtrueからfalseまたはfalseからtrueに変更された場合は理にかなっていますor呼び出されたメソッドmightが変更された場合ブールパラメータを渡すのではなく、直接使用します。このような呼び出しが続けて10回発生する可能性は低いですが、発生する可能性があり、発生した場合はプログラミングの練習として優れています。

2番目のケースは、ブール値を扱っていることを考えると、少し伸びています。ただし、プログラムに複数のスレッドまたはイベントがある場合、パラメーターを渡すことがスレッド/イベント固有のデータを追跡する最も簡単な方法です。たとえば、プログラムは2つ以上のソケットから入力を取得する場合があります。あるソケットに対して実行されているコードは警告メッセージを生成する必要がある場合があり、別のソケットに対して実行されているコードは生成しない場合があります。次に、(一種の)警告メッセージが生成される可能性のある場所への多くのメソッド呼び出しを通過する非常に高レベルのブールセットに対して意味があります。複数のスレッドまたはインターリーブされたイベントはそれぞれ独自の値を必要とするため、どのような種類のグローバルでもデータを保存することはできません(非常に困難な場合を除く)。

確かに、後者の場合、おそらくコンテンツがブール値のみであるクラス/構造を作成し、代わりにthatを渡します。たとえば、警告メッセージを送信するにはwhereのように、長くなる前に他のフィールドが必要になることはほぼ確実です。

0
RalphChapin