@ benjamin-gruenbaumのコメントで指摘されているように、これはブールトラップと呼ばれます。
このような機能があるとしましょう
UpdateRow(var item, bool externalCall);
私のコントローラーでは、externalCall
の値は常にTRUEです。この関数を呼び出す最良の方法は何ですか?私は通常書きます
UpdateRow(item, true);
しかし、私は自分に質問します。ブール値を宣言する必要があるのは、その「真の」値が何を表すのかを示すためだけですか?あなたは関数の宣言を見ればそれを知ることができますが、次のようなものを見た方が明らかに速くて明確です
bool externalCall = true;
UpdateRow(item, externalCall);
PD:この質問が本当にここに当てはまるかどうかわからない、もし当てはまらない場合、どこでもっと詳しい情報を入手できますか?
PD2:それは非常に一般的な問題だと思ったので、言語にタグを付けませんでした。とにかく、私はc#で作業し、受け入れられた答えはc#で機能します
常に完璧な解決策があるとは限りませんが、次の選択肢から選択できます。
ご使用の言語で利用可能な場合は、名前付き引数を使用してください。これは非常にうまく機能し、特定の欠点はありません。一部の言語では、任意の引数を名前付き引数として渡すことができます。 updateRow(item, externalCall: true)
( C# )またはupdate_row(item, external_call=True)
(Python)。
別の変数を使用するという提案は、名前付き引数をシミュレートする1つの方法ですが、関連する安全上の利点はありません(その引数に正しい変数名を使用したことを保証するものではありません)。
より適切な名前を使用して、パブリックインターフェイスに個別の関数を使用します。これは、名前にパラメーター値を入れることにより、名前付きパラメーターをシミュレートする別の方法です。
これはとても読みやすいですが、これらの関数を書いているあなたにとって多くの定型文につながります。また、複数のブール引数がある場合、組み合わせの爆発をうまく処理できません。重要な欠点は、クライアントがこの値を動的に設定できないことですが、if/elseを使用して正しい関数を呼び出す必要があります。
enumを使用します。ブール値の問題は、それらが「true」および「false」と呼ばれることです。そのため、代わりに、より適切な名前のタイプを導入します(例:enum CallType { INTERNAL, EXTERNAL }
)。追加の利点として、これによりプログラムのタイプセーフティが向上します(言語が列挙型を異なる型として実装している場合)。列挙型の欠点は、公開されているAPIに型が追加されることです。純粋に内部関数の場合、これは問題ではなく、列挙型には重大な欠点はありません。
列挙型のない言語では、代わりに短い文字列が使用されることがあります。これは機能し、生のブール値よりも優れている場合がありますが、タイプミスの影響を非常に受けやすくなっています。次に、関数は、引数が可能な値のセットと一致することをすぐにアサートする必要があります。
これらのソリューションはどれも、パフォーマンスに極端な影響を与えません。名前付きパラメーターと列挙型は、コンパイル時に完全に解決できます(コンパイル済み言語の場合)。文字列の使用には文字列の比較が含まれる場合がありますが、そのコストは小さな文字列リテラルやほとんどの種類のアプリケーションでは無視できます。
正しい解決策は、あなたが提案することを実行することですが、それをミニファサードにパッケージ化します。
void updateRowExternally() {
bool externalCall = true;
UpdateRow(item, externalCall);
}
読みやすさがマイクロ最適化に勝る。ブールフラグのセマンティクスを一度も調べなくてはならないという開発者の努力よりも、確かに優れた追加の関数呼び出しを実行できます。
UpdateRow(var item、bool externalCall);のような関数があるとします。
なぜこのような機能があるのですか?
どのような状況の下で、externalCall引数を異なる値に設定して呼び出しますか?
たとえば、1つが外部のクライアントアプリケーションからであり、もう1つが同じプログラム(つまり、異なるコードモジュール)内にある場合、2つのdifferentメソッドが必要で、それぞれに1つずつあるはずです。場合によっては、異なるインターフェースで定義されることもあります。
ただし、プログラム以外のソース(構成ファイルやデータベースの読み取りなど)から取得したdatumに基づいて呼び出しを行っている場合は、ブール値を渡す方法の方が理にかなっています。
言語機能を使用して可読性と値の安全性の両方を強化するのはidealであることに同意しますが、実用的アプローチを選択することもできます:呼び出し時のコメント。お気に入り:
UpdateRow(item, true /* row is an external call */);
または:
UpdateRow(item, true); // true means call is external
または(正しい、Fraxの提案どおり):
UpdateRow(item, /* externalCall */true);
あなたはあなたのブールに「名前を付ける」ことができます。以下はOO言語の例です(UpdateRow()
を提供するクラスで表現できます)。ただし、概念自体は任意の言語に適用できます。
_class Table
{
public:
static const bool WithExternalCall = true;
static const bool WithoutExternalCall = false;
_
そして呼び出しサイトで:
_UpdateRow(item, Table::WithExternalCall);
_
項目1の方が良いと思いますが、関数を使用するときにユーザーが新しい変数を使用する必要はありません。タイプセーフが重要な場合は、enum
タイプを作成して、bool
の代わりにUpdateRow()
にこれを受け入れさせることができます。
UpdateRow(var item, ExternalCallAvailability ec);
関数の名前は、bool
パラメータの意味をより適切に反映するように変更できます。確かではありませんが、多分:
UpdateRowWithExternalCall(var item, bool externalCall)
ここでまだ読んでいない別のオプションは、最新のIDEを使用することです。
たとえば、IntelliJ IDEAは、true
やnull
などのリテラルを渡した場合に、呼び出しているメソッド内の変数の変数名を出力します。 username + “@company.com
。これは小さなフォントで行われるため、画面上のスペースをあまり取りません。実際のコードとは非常に異なって見えます。
私はまだどこにでもブール値を投入するのが良い考えだとは言っていません。コードを書くよりもはるかに頻繁に読むという主張は非常に強力ですが、この特定のケースでは、それはあなた(そしてあなたの同僚!)があなたの仕事をサポートするために使用するテクノロジーに大きく依存します。 IDEを使用すると、たとえばvimを使用する場合よりも問題がはるかに少なくなります。
2日間、誰もポリモーフィズムについて言及していませんか?
target.UpdateRow(item);
行をアイテムで更新したいクライアントの場合、データベースの名前、マイクロサービスのURL、通信に使用されるプロトコル、または外部呼び出しがそれを実現するために使用する必要があります。これらの詳細を私に押し付けるのをやめてください。ちょうど私のアイテムを取り、すでにどこかで行を更新してください。
それを行うと、それは建設問題の一部になります。それは多くの方法で解決できます。ここに一つあります:
Target xyzTargetFactory(TargetBuilder targetBuilder) {
return targetBuilder
.connectionString("some connection string")
.table("table_name")
.external()
.build()
;
}
あなたがそれを見ていて、「でも私の言語には名前付き引数があるので、私はこれを必要としません」と考えています。すばらしい、すばらしい、それらを使用してください。何を話しているのかさえわからないはずの呼び出し側のクライアントから、このナンセンスを排除してください。
これは意味上の問題以上のものであることに注意してください。それは設計上の問題でもあります。ブール値を渡すと、ブランチを使用してそれを処理する必要があります。あまりオブジェクト指向ではありません。渡すブール値が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つの関数に取る関数をリファクタリングすることは、通常検討する価値がありますが、常に最良の選択であるとは限りません。
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;
} );
一般に、このような制御の反転により、データストレージとモデル間の結合が低くなります。