ブール値をクリアして呼び出しを行うにはどうすればよいですか?ブールトラップ


76

@ benjamin-gruenbaumのコメントで指摘されているように、これはブールトラップと呼ばれます。

このような機能があるとしましょう

UpdateRow(var item, bool externalCall);

私のコントローラーでは、その値externalCallは常にTRUEです。この関数を呼び出す最良の方法は何ですか?私は通常書く

UpdateRow(item, true);

しかし、「真の」値が何を意味するのかを示すために、ブール値を宣言する必要がありますか?関数の宣言を見ることでそれを知ることができますが、次のようなものを見た場合は明らかに速く、明確です

bool externalCall = true;
UpdateRow(item, externalCall);

PD:この質問が本当にここに当てはまるかどうかはわかりませんが、当てはまらない場合は、どこでこれに関する情報を入手できますか?

PD2:非常に一般的な問題だと思ったため、言語にタグを付けませんでした。とにかく、私はc#で作業し、受け入れられた答えはc#で動作します



11
代数データ型をサポートする言語を使用している場合、ブール値ではなく新しいadtを強くお勧めします。data CallType = ExternalCall | InternalCallたとえばhaskellで。
フィリップハグランド

6
列挙型はまったく同じ目的を満たすと思います。ブール値の名前、および型の安全性を少し取得します。
フィリップハグランド

2
意味を示すためにブール値を宣言することは「明らかに明確」だとは思いません。最初のオプションでは、常にtrueを渡すことは明らかです。2番目では、チェックする必要があります(変数はどこで定義されていますか?その値は変化しますか?)。もちろん、2行が揃っていれば問題はありませんが、コードを追加するのに最適な場所は2行の間にあると判断する人もいるかもしれません。それが起こる!
AJPerez

ブール値を宣言することは明確ではなく、明確なステップを飛び越えて、問題のメソッドのドキュメントを見ているだけです。署名がわかっている場合、新しい定数を追加するのはあまり明確ではありません。
インサイド

回答:


154

常に完璧なソリューションがあるわけではありませんが、次の選択肢から多くの選択肢があります。

  • 使用する名前付き引数あなたの言語で利用可能な場合に、。これは非常にうまく機能し、特に欠点はありません。一部の言語では、任意の引数を名前付き引数として渡すことができますupdateRow(item, externalCall: true)(例:(C#)またはupdate_row(item, external_call=True)(Python))。

    別の変数を使用するという提案は、名前付き引数をシミュレートする1​​つの方法ですが、関連する安全性の利点はありません(その引数に正しい変数名を使用した保証はありません)。

  • パブリックインターフェイスには、より適切な名前の個別の関数を使用します。これは、名前にパラメーター値を入れることにより、名前付きパラメーターをシミュレートするもう1つの方法です。

    これは非常に読みやすいですが、これらの関数を書いている多くの定型句につながります。また、複数のブール引数がある場合、組み合わせ爆発にうまく対処できません。大きな欠点は、クライアントがこの値を動的に設定できないが、if / elseを使用して正しい関数を呼び出す必要があることです。

  • enumを使用します。ブール値の問題は、ブール値が「true」および「false」と呼ばれることです。そのため、代わりに、より適切な名前のタイプを導入してください(例:)enum CallType { INTERNAL, EXTERNAL }。追加の利点として、これによりプログラムの型安全性が向上します(言語が列挙型を個別の型として実装している場合)。列挙型の欠点は、公開されているAPIに型を追加することです。純粋に内部関数の場合、これは重要ではなく、列挙型には大きな欠点はありません。

    列挙のない言語では、代わりに短い文字列が使用される場合があります。これは機能し、生のブール値よりも優れている場合もありますが、タイプミスの影響を非常に受けやすくなっています。関数は、引数が可能な値のセットと一致することをすぐにアサートする必要があります。

これらのソリューションはどれも、パフォーマンスに大きな影響を与えません。名前付きパラメーターと列挙型は、コンパイル時に完全に解決できます(コンパイルされた言語の場合)。文字列の使用には文字列の比較が含まれる場合がありますが、そのコストは小さな文字列リテラルとほとんどの種類のアプリケーションでは無視できます。


7
「名前付き引数」が存在することを知りました。これは断然最高の提案だと思います。そのオプションについて少し詳しく説明できる場合(他の人があまりにもGoogleを必要としないように)、この答えを受け入れます。可能な場合はパフォーマンスに関するヒントもあります。ありがとう
マリオガルシア

6
短い文字列の問題を軽減するのに役立つことの1つは、文字列値に定数を使用することです。@MarioGarcia詳しく説明することはあまりありません。ここで言及されていることは別として、詳細のほとんどは特定の言語に依存します。ここで言及したいと思う何か特別なものがありましたか?
jpmc26

8
我々はこの方法を話している言語では列挙私は、一般的に列挙型を使用したクラスにスコープすることができます。それは完全に自己文書化されており(列挙型を宣言するコードの1行で、軽量です)、メソッドが裸のブール値で呼び出されることを完全に防ぎ、パブリックAPIへの影響は無視できます(識別子のため)クラスにスコープされます)。
-davidbak

4
この役割で文字列を使用する別の欠点は、列挙型とは異なり、コンパイル時に文字列の有効性をチェックできないことです。
ルスラン

32
enumが勝者です。パラメーターが2つの可能な状態のうちの1つしか持つことができないからといって、それがパラメーターが自動的にブール値であることを意味するわけではありません。
ピート

39

正しい解決策は、あなたが提案することをすることですが、ミニファサードにパッケージ化することです:

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

可読性は、マイクロ最適化よりも優れています。ブールフラグのセマンティクスを一度でも検索しなければならない開発者の努力よりも、確実に優れた追加の関数呼び出しを行う余裕があります。


8
このカプセル化は、その関数をコントローラーで1回だけ呼び出す場合、本当に価値がありますか?
マリオガルシア

14
はい。はい、そうです。理由は、上で書いたように、コスト(関数呼び出し)が利益よりも小さいことです(だれも何を調べる必要がないため、開発者の時間を節約trueできます)。 CPU時間の方がずっと安いため、開発者の時間を節約できるだけでなく、CPU時間もかかります。
キリアン・フォス

8
また、有能なオプティマイザーはそれをインライン化できるので、最終的には何も失うことはありません。
firedraco

33
@KilianFothそれは間違った仮定です。メンテナー、2番目のパラメーターが何をするのか、そしてなぜそれが真実であるのかを知る必要あり、ほとんどの場合、これはあなたがしようとしていることの詳細に関連しています。数千人の死の小さな機能に機能を隠すと、保守性が向上するのではなく、保守性が低下します。私はそれが自分で起こるのを見たことがあります。機能への過度の分割は、実際には巨大な神の機能よりも悪い場合があります。
グラハム

6
UpdateRow(item, true /*external call*/);そのコメント構文を許可する言語では、きれいになると思います。コメントを書くことを避けるためだけに余分な関数を使用してコードを肥大化することは、単純な場合には価値がないようです。この関数に他の引数がたくさんある場合や、周囲のコードが散らかった+トリッキーな場合は、もっと魅力的になり始めるでしょう。しかし、デバッグしている場合は、ラッパー関数をチェックしてその機能を確認し、どの関数がライブラリAPIの薄いラッパーであり、実際にはいくつかのロジックを持っていることを覚えておく必要があると思います。
ピーター

25

UpdateRow(var item、bool externalCall);のような関数があるとします。

なぜこのような機能があるのですか?

どのような状況で、externalCall引数を異なる値に設定して呼び出しますか?

一方が外部のクライアントアプリケーションなどからのもので、もう一方が同じプログラム(つまり異なるコードモジュール)内にある場合、2つの異なるメソッドが必要であると主張します。インターフェース。

ただし、プログラム以外のソース(構成ファイルやデータベースの読み取りなど)から取得したデータに基づいて呼び出しを行っている場合は、ブール値を渡す方法の方が意味があります。


6
同意する。そして、他の読者のために作られたポイントを打ち立てるために、true、false、または変数のいずれかを渡すすべての呼び出し元について分析を行うことができます。後者のいずれも見つからない場合は、ブールを使用した2つの別個のメソッド(ブールなし)について議論します。ブールが未使用/不要な複雑さをもたらすのはYAGNI引数です。一部の呼び出し元が変数を渡している場合でも、定数を渡す呼び出し元に使用する(ブール型の)パラメーターなしのバージョンを提供することがあります。
エリックエイド

18

言語機能を使用して読みやすさと価値の安全性の両方を実施することが理想的であることに同意しますが、実用的なアプローチを選択することもできます:呼び出し時コメント。好む:

UpdateRow(item, true /* row is an external call */);

または:

UpdateRow(item, true); // true means call is external

または(当然、Fraxの提案どおり):

UpdateRow(item, /* externalCall */true);

1
投票する理由はありません。これは完全に有効な提案です。
Suncat2000

<3 @ Suncat2000に感謝します!
ビショップ

11
(おそらく望ましい)バリアントは、のように単純な引数名をそこに置くだけUpdateRow(item, /* externalCall */ true )です。全文コメントは解析がはるかに難しく、実際にはほとんどがノイズです(特に2番目のバリアントは引数とも非常に疎結合です)。
-Frax

1
これが最も適切な答えです。これがまさにコメントの目的です。
センチネル

9
このようなコメントの大ファンではありません。リファクタリングやその他の変更を行うときにコメントをそのままにしておくと、コメントが古くなる傾向があります。言うまでもなく、boolパラメータが複数あるとすぐに混乱を招きます。
ジョンV.

11
  1. ブールに「名前を付ける」ことができます。以下は、オブジェクト指向言語(を提供するクラスで表現できるUpdateRow())の例ですが、コンセプト自体はどの言語にも適用できます。

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    そして、コールサイトで:

    UpdateRow(item, Table::WithExternalCall);
    
  2. アイテム#1の方が優れていると思いますが、関数の使用時にユーザーに新しい変数の使用を強制することはありません。型の安全性が重要な場合は、enum型を作成し、UpdateRow()代わりにこれを受け入れるようにすることができますbool

    UpdateRow(var item, ExternalCallAvailability ec);

  3. boolパラメータの意味をよりよく反映するように、何らかの方法で関数の名前を変更できます。よくわからないが、多分:

    UpdateRowWithExternalCall(var item, bool externalCall)


8
で関数を呼び出す場合、今のように#3を好きにならないでくださいexternalCall=false。あなたの答えの残りは良いです。
軌道上の明度レース

同意した。よくわかりませんでしたが、他の機能の実行可能なオプションかもしれません。私
シミュレーション

@LightnessRacesinOrbit確かに。に行く方が安全ですUpdateRowWithUsuallyExternalButPossiblyInternalCall
エリックドゥミニル

@EricDuminil:Spotのスポット
軌道上の明るさレース

10

ここでまだ読んでいないもう1つのオプションは、最新のIDEを使用することです。

たとえば、IntelliJ IDEAは、trueor nullやなどのリテラルを渡す場合、呼び出しているメソッドの変数の変数名を出力しますusername + “@company.com。これは小さなフォントで行われるため、画面上のスペースを取りすぎず、実際のコードとは大きく異なります。

私はいまだにブール値をあらゆる場所に投入することは良い考えだとは言っていません。あなたが書くよりもはるかに頻繁にコードを読むという主張はしばしば非常に強力ですが、この特定の場合、それはあなた(そしてあなたの同僚!)があなたの仕事をサポートするために使用するテクノロジーに大きく依存します。IDEを使用すると、たとえばvimを使用する場合よりもはるかに問題が少なくなります。

テスト設定の一部の変更例: ここに画像の説明を入力してください


5
これは、チームの全員がIDEを使用してコードを読み取る場合にのみ当てはまります。非IDEエディターの普及にもかかわらず、コードレビューツール、ソース管理ツール、Stack Overflowの質問などもあります。これらのコンテキストでもコードを読みやすくすることが非常に重要です。
ダニエル・プライデン

@DanielPryden確かに、したがって、私の「そしてあなたの同僚」のコメント。企業で標準IDEを使用することは一般的です。他のツールについては、これらの種類のツールもこれまたは将来サポートする可能性がある、またはそれらの議論が単に適用されない(2人のペアプログラミングチームのように)
Sebastiaan van den Broek

2
@セバスチャン:私は同意するとは思わない。ソロ開発者としても、Git diffで定期的に自分のコードを読んでいます。Gitは、IDEが実行できるのと同じ種類のコンテキスト対応の視覚化を実行することはできません。私はコードの作成を支援するために優れたIDEを使用しますが、IDEを口実として使用して、コードなしでは作成しないコードを作成することはできません。
ダニエル・プライデン

1
@DanielPryden私は極端にずさんなコードを書くことを支持していません。白黒の状況ではありません。過去に特定の状況でブール値を使用して関数を作成することを40%支持し、60%を使用せずに最終的にそれを実行しなかった場合があります。IDEが適切にサポートされていれば、バランスは55%がそのように記述し、45%はそうではないかもしれません。はい、それでもIDEの外で読む必要があるかもしれないので、完璧ではありません。ただし、別のメソッドや列挙を追加してコードを明確にするなど、代替案との有効なトレードオフです。
セバスチャンヴァンデンブローク

6

2日間、誰も多型に言及していませんか?

target.UpdateRow(item);

行をアイテムで更新したいクライアントの場合、データベースの名前、マイクロサービスのURL、通信に使用されるプロトコル、または外部呼び出しがそれを実現するために使用する必要があります。これらの詳細をプッシュするのをやめてください。私のアイテムを取り、すでにどこかで行を更新してください。

それを行うと、建設問題の一部になります。それは多くの方法で解決できます。以下がその1つです。

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

あなたがそれを見て、「でも私の言語は引数に名前を付けているので、これは必要ありません」と考えているなら。素晴らしい、素晴らしい、それらを使用してください。このナンセンスは、何を話しているのかさえ知らない呼び出し側のクライアントから遠ざけてください。

これは意味的な問題以上のものであることに注意してください。また、設計上の問題でもあります。ブール値を渡すと、それに対処するために分岐を使用する必要があります。オブジェクト指向ではありません。渡す2つ以上のブール値がありますか?あなたの言語が複数のディスパッチをしたいですか?コレクションをネストしたときにできることを調べてください。適切なデータ構造は、あなたの人生をとても楽にします。


2

updateRow関数を変更できる場合は、おそらく2つの関数にリファクタリングできます。関数がブール値のパラメータを取る場合、次のようになります。

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

それはちょっとしたコードの匂いかもしれません。関数は、externalCall変数の設定内容に応じて劇的に異なる動作をする場合があります。この場合、2つの異なる責任があります。責任を1つだけ持つ2つの関数にリファクタリングすると、読みやすくなります。

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

これで、これらの関数を呼び出す場所であればどこでも、外部サービスが呼び出されているかどうかが一目でわかります。

もちろん、これは常にそうとは限りません。それは状況に応じた好みの問題です。通常、ブール値パラメーターを2つの関数に変換する関数をリファクタリングすることは検討する価値がありますが、常に最良の選択とは限りません。


0

UpdateRowが制御されたコードベースにある場合、戦略パターンを検討します。

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

Requestは、ある種のDAO(ささいな場合のコールバック)を表します。

真の場合:

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

偽の場合:

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

通常、エンドポイントの実装はより高い抽象化レベルで構成され、ここで使用します。便利な無料の副作用として、これにより、コードを変更せずにリモートデータにアクセスする別の方法を実装するオプションが提供されます。

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

一般に、このような制御の反転により、データストレージとモデル間の結合が低くなります。

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