動作を決定するためにブールパラメータを使用するのは間違っていますか? によると、動作を決定するためにブールパラメータを使用しないことの重要性を知っています。例:
元のバージョン
public void setState(boolean flag){
if(flag){
a();
}else{
b();
}
c();
}
新しいバージョン:
public void setStateTrue(){
a();
c();
}
public void setStateFalse(){
b();
c();
}
しかし、動作の代わりにブールパラメータを使用して値を決定する場合はどうでしょうか。例えば:
public void setHint(boolean isHintOn){
this.layer1.visible=isHintOn;
this.layer2.visible=!isHintOn;
this.layer3.visible=isHintOn;
}
IsHintOnフラグを削除して、2つの個別の関数を作成しようとしています。
public void setHintOn(){
this.layer1.visible=true;
this.layer2.visible=false;
this.layer3.visible=true;
}
public void setHintOff(){
this.layer1.visible=false;
this.layer2.visible=true;
this.layer3.visible=false;
}
ただし、次の理由により、変更されたバージョンは保守性が低くなります。
元のバージョンよりもコードが多い
layer2の可視性がヒントオプションと反対であることを明確に示すことはできません
新しいレイヤー(例:layer4)が追加されたら、追加する必要があります
this.layer4.visible=false;
そして
this.layer4.visible=true;
setHintOn()およびsetHintOff()に個別に
したがって、私の質問は、ブールパラメータを使用して値のみを決定し、動作を決定しない場合(たとえば、そのパラメータにif-elseがない場合)、そのブールパラメータを削除することをお勧めしますか?
APIの設計は、APIのクライアントが呼び出し側から最も使用可能なものに焦点を当てる必要があります。
たとえば、この新しいAPIが呼び出し元に次のようなコードを定期的に記述する必要がある場合
if(flag)
foo.setStateTrue();
else
foo.setStateFalse();
次に、パラメーターを回避することは、呼び出し元が書き込みを許可するAPIを使用するよりも悪いことは明らかです。
foo.setState(flag);
前のバージョンでは問題が発生するだけなので、呼び出し側で(そしておそらく複数回)解決する必要があります。これは可読性も保守性も向上させません。
ただし、実装側は、パブリックAPIの外観を決定するものではありません。パラメータ付きのsetHint
のような関数の実装で必要なコードは少ないが、APIのsetHintOn
/setHintOff
はクライアントにとって使いやすいように見える場合、これを実装できます仕方:
private void setHint(boolean isHintOn){
this.layer1.visible=isHintOn;
this.layer2.visible=!isHintOn;
this.layer3.visible=isHintOn;
}
public void setHintOn(){
setHint(true);
}
public void setHintOff(){
setHint(false);
}
したがって、パブリックAPIにはブールパラメータはありませんが、ここでは重複するロジックはありません。そのため、新しい質問(質問の例のような)が到着したときに変更する場所は1つだけです。
これは逆の方法でも機能します。上記のsetState
メソッドが2つの異なるコード部分を切り替える必要がある場合、そのコード部分を2つの異なるプライベートメソッドにリファクタリングできます。したがって、IMHOは、内部を見て「1つのパラメータ/ 1つのメソッド」と「0のパラメータ/ 2つのメソッド」を決定する基準を検索しても意味がありません。ただし、APIの消費者の役割でAPIを確認したい方法を見てください。
疑わしい場合は、「テスト駆動開発(TDD)」を使用してみてください。これにより、パブリックAPIとその使用方法について考える必要が生じます。
マーティン・ファウラーはケント・ベックを 個別のsetOn()
setOff()
メソッドを推奨 で引用していますが、これは不可侵と見なすべきではないと述べています:
UIコントロールやデータソースなどのブールソースから[sic]データを取得する場合、
setSwitch(aValue)
よりもif (aValue) setOn(); else setOff();
これは、呼び出し元が簡単にできるようにAPIを作成する必要がある例であるため、呼び出し元の出所がわかっている場合は、その情報を考慮してAPIを設計する必要があります。これは、両方の方法で呼び出し元を取得する場合、両方のスタイルを提供する場合があることも主張しています。
もう1つの推奨事項は、 列挙値またはフラグタイプを使用 して、true
およびfalse
より適切なコンテキスト固有の名前を付けることです。あなたの例では、showHint
とhideHint
の方が良いかもしれません。
あなたの投稿では、APIと実装の2つを混ぜていると思います。どちらの場合も、常に使用できる強力なルールがあるとは思いませんが、これら2つを(可能な限り)個別に検討する必要があります。
まずはAPIから始めましょう。
_public void setHint(boolean isHintOn)
_
そして:
_public void setHintOn()
public void setHintOff()
_
オブジェクトが何を提供することになっているのか、クライアントがAPIをどのように使用するのかに応じて、有効な代替手段です。 Docが指摘したように、ユーザーが(UIコントロール、ユーザーアクション、外部、APIなどから)ブール変数を既に持っている場合、最初のオプションの方が意味があります。それ以外の場合は、クライアントのコードに追加のifステートメントを強制するだけです。 。ただし、たとえば、プロセスの開始時にヒントをtrueに変更し、最後にfalseに変更する場合、最初のオプションでは次のようになります。
_setHint(true)
// Do your process
…
setHint(false)
_
2番目のオプションはこれをあなたに与えます:
_setHintOn()
// Do your process
…
setHintOff()
_
iMOの方がはるかに読みやすいので、この場合は2番目のオプションを使用します。明らかに、両方のオプションを提供することを妨げるものは何もありません(たとえば、それがより理にかなっている場合は、グラハムが言ったように列挙型を使用できます)。
重要なのは、オブジェクトの実装方法ではなく、オブジェクトが行うことになっていることとクライアントがそれをどのように使用するかに基づいてAPIを選択する必要があるということです。
次に、パブリックAPIの実装方法を選択する必要があります。パブリックAPIとしてメソッドsetHintOn
とsetHintOff
を選択し、例のようにこれらがこの共通ロジックを共有するとします。このロジックは、プライベートメソッド(Docからコピーしたコード)を使用して簡単に抽象化できます。
_private void setHint(boolean isHintOn){
this.layer1.visible=isHintOn;
this.layer2.visible=!isHintOn;
this.layer3.visible=isHintOn;
}
public void setHintOn(){
setHint(true);
}
public void setHintOff(){
setHint(false);
}
_
逆に、APIとしてsetHint(boolean isHintOn)
を選択したとしますが、ヒントをオンに設定することとオフに設定することはまったく異なる理由のため、例を逆にしてみましょう。この場合、次のように実装できます。
_public void setHint(boolean isHintOn){
if(isHintOn){
// Set it On
} else {
// Set it Off
}
}
_
あるいは:
_public void setHint(boolean isHintOn){
if(isHintOn){
setHintOn()
} else {
setHintOff()
}
}
private void setHintOn(){
// Set it On
}
private void setHintOff(){
// Set it Off
}
_
重要なのは、どちらの場合も、最初にパブリックAPIを選択し、次に、選択したAPI(および私たちの制約)に適合するように実装を調整したことです。
ちなみに、動作を決定するためにブールパラメータを使用することについてリンクした投稿にも同じことが当てはまると思います。つまり、ハードルールではなく特定のユースケースに基づいて決定する必要があります(ただし、特定のケースでは通常正しいことは、複数の関数でそれを壊すことです)。
まず最初に、コードが少し長くなるからといって、コードの保守性が自動的に低下するわけではありません。明快さが重要です。
さて、もしあなたがreallyでデータを処理しているなら、あなたが持っているのはブール型プロパティのセッターです。その場合は、その値を直接保存して、レイヤーの可視性を導出する必要があります。
_bool isBackgroundVisible() {
return isHintVisible;
}
bool isContentVisible() {
return !isHintVisible;
}
_
(私はレイヤーに実際の名前を付けるために自由を取っています-あなたが元のコードにこれを持っていない場合、私はそれから始めます)
これでも、setHintVisibility(bool)
メソッドを使用するかどうかという疑問が残ります。個人的には、showHint()
およびhideHint()
メソッドに置き換えることをお勧めします。どちらも非常にシンプルで、レイヤーを追加するときにこれらを変更する必要はありません。しかし、それは明確な正誤ではありません。
関数を呼び出すことでこれらのレイヤーの可視性を実際に変更する必要がある場合、実際には動作があります。その場合は、別の方法をお勧めします。
2番目の例ではブール型パラメーターで問題ありません。すでに理解しているように、ブールパラメータ自体には問題はありません。これはフラグに基づいて動作を切り替えるため、問題があります。
ただし、最初の例は問題があります。これは、名前がセッターメソッドを示しているため、実装が異なるように見えるためです。したがって、動作を切り替えるアンチパターンandは、誤解を招くような名前のメソッドです。ただし、メソッドが実際に通常のセッター(動作の切り替えなし)である場合は、setState(boolean)
に問題はありません。 setStateTrue()
とsetStateFalse()
の2つのメソッドがあると、不必要に複雑になり、メリットがありません。
この問題を解決する別の方法は、各ヒントを表すオブジェクトを導入し、そのオブジェクトに、そのヒントに関連付けられたブール値を決定する責任を持たせることです。この方法では、単純に2つのブール状態を持つのではなく、新しい順列を追加できます。
たとえば、Javaでは次のようにできます。
public enum HintState {
SHOW_HINT(true, false, true),
HIDE_HINT(false, true, false);
private HintState(boolean layer1Visible, boolean layer2Visible, boolean layer3Visible) {
// constructor body and accessors omitted for clarity
}
}
そして、呼び出し元のコードは次のようになります。
setHint(HintState.SHOW_HINT);
そして、実装コードは次のようになります。
public void setHint(HintState hint) {
this.layer1Visible = hint.isLayer1Visible();
this.layer2Visible = hint.isLayer2Visible();
this.layer3Visible = hint.isLayer3Visible();
}
これにより、厳密に型指定されたintentionsという名前の対応する状態のセットに明確にマップされる新しいデータ型を定義する代わりに、実装コードと呼び出し元コードが簡潔になります。私はそれがすべての点でより良いと思います。
setOn() + setOff()
対set(flag)
の質問とは別に、ここではブール型が最適かどうかを慎重に検討します。 3番目のオプションは決してないでしょうか。
ブール値ではなく列挙型を検討する価値があるかもしれません。これにより、拡張性が可能になるだけでなく、ブール値を間違った方法で取得することも困難になります。例:
setHint(false)
対
setHint(Visibility::HIDE)
列挙型を使用すると、「必要に応じて」オプションが必要だと誰かが判断したときに拡張するのがはるかに簡単になります。
enum class Visibility {
SHOW,
HIDE,
IF_NEEDED // New
}
対
setHint(false)
setHint(true)
setHintAutomaticMode(true) // New
...によると、動作を決定するためにブールパラメータを使用しないことの重要性を知っています
この知識を再評価することをお勧めします。
まず、リンクしたSEの質問で提案しているという結論はわかりません。彼らは主に、メソッド呼び出しのいくつかのステップでパラメーターを転送することについて話している。
あなたの例では、メソッド内でパラメータを評価しています。その点では、他の種類のパラメーターとまったく同じです。
一般的に、ブール型パラメーターを使用しても何も問題はありません。そして、明らかにanyパラメータは動作を決定します、またはなぜ最初にそれを持っているのですか?
タイトルの質問は「[...]を間違えていますか?」です。 -しかし、「間違った」とはどういう意味ですか?.
C#またはJavaコンパイラによると、それは間違いではありません。あなたがそれを知っていることは確かであり、それはあなたが求めていることではありません。それ以外は恐れています。 n
プログラマn+1
異なる意見。この答えは、本Clean Codeがこれについて述べていることを示しています。
クリーンコードは、一般に関数の引数に対して強い主張をします:
議論は難しいです。彼らは多くの概念的な力を持っています。 [...]私たちの読者は、それを見るたびにそれを解釈しなければならなかっただろう。
ここでの「リーダー」は、APIコンシューマーになることができます。それは、このコードがまだ何をしているのかを知らない次のコーダーになることもできます。それらは2関数を個別に通過するまたは1関数を2回通過する、 true
とfalse
を常に念頭に置いてください。
要するに、できるだけ少ない引数を使用。
フラグ引数の特定のケースは、後で直接扱われます。
フラグの引数は醜いです。ブール値を関数に渡すことは本当にひどい習慣です。メソッドのシグネチャをすぐに複雑にし、この関数が複数のことを行うことを大声で宣言します。フラグが真の場合は1つの処理を行い、フラグが偽の場合は別の処理を実行します。
質問に直接回答するには:
クリーンコードによると、そのパラメーターを削除することをお勧めします。
あなたの例はかなり単純ですが、コードに伝播する単純さを見ることができます:パラメータなしの関数は単純な代入のみを行い、他の関数は同じ目的を達成するためにブール演算を行う必要があります。これは、この単純化された例ではささいなブール演算ですが、実際の状況ではかなり複雑になる可能性があります。
多くの場所でこれを行わなければならないのはばかになるので、APIユーザーに依存させる必要があるという多くの引数をここで見ました。
if (isAfterSunset) light.TurnOn();
else light.TurnOff();
私doは、ここで最適ではないことが起こっていることに同意します。多分それは明白ではないかもしれませんが、あなたの最初の文は「動作を決定するためにブールパラメータを使用することを避ける[sic]の重要性」に言及しており、それが質問全体のベースです。私は、APIユーザーにとってそれを簡単に行うことが悪いことをする理由を知りません。
あなたがテストを行うかどうかはわかりません-その場合、これも考慮してください:
テストの観点からすると、引数はさらに困難です。引数のさまざまな組み合わせがすべて正しく機能することを確認するために、すべてのテストケースを作成するのが難しいと想像してください。引数がない場合、これは簡単です。
したがって、私の質問は、ブールパラメータを使用して値のみを決定し、動作を決定しない場合(たとえば、そのパラメータにif-elseがない場合)、そのブールパラメータを削除することをお勧めしますか?
そんなことに疑問がある時。スタックトレースがどのようになるかを描きたいです。
長年、私はsetterおよびgetterと同じ関数を使用するPHPプロジェクトに取り組みました。渡した場合null値を返しますが、それ以外の場合は設定します使用するのは恐ろしかった。
スタックトレースの例を次に示します。
function visible() : line 440
function parent() : line 398
function mode() : line 384
function run() : line 5
あなたは内部stateについて何も知りませんでした、そしてそれはデバッグを難しくしました。他にも多くのマイナスの副作用がありますが、-値がverbose関数名にあり、clarityが関数を実行するときに- 単一アクション。
次に、ブール値に基づいてAまたはBの動作を行う関数のスタックトレースを操作する画像を示します。
function bar() : line 92
function setVisible() : line 120
function foo() : line 492
function setVisible() : line 120
function run() : line 5
あなたが私に尋ねると、それは混乱します。同じsetVisible
行は、2つの異なるトレースパスを生成します。
それではあなたの質問に戻りましょう。スタックトレースがどのように見えるか、何が起こっているかを人に伝える方法を描写し、将来の人がコードをデバッグするのを手伝っているかどうか自問してください。
ここにいくつかのヒントがあります:
顕微鏡ではコードが過剰に見える場合がありますが、全体像に戻ると、最小限のアプローチでコードが消えます。あなたがそれを維持することができるようにそれを目立たせる必要がある場合。多くの小さな関数を追加すると、過度に冗長に感じるかもしれませんが、より大きなコンテキストで広く使用すると、保守性が向上します。
何かの動作を変更するためにboolean
パラメータをflagとしてメソッドに渡すほとんどすべてのケースで、あなたは考慮すべきですこれを行うためのより多くのexplicitおよびタイプセーフな方法。
状態を表すEnum
を使用するだけで、コードの理解度が向上します。
この例では、Node
のJavaFX
クラスを使用しています。
_public enum Visiblity
{
SHOW, HIDE
public boolean toggleVisibility(@Nonnull final Node node) {
node.setVisible(!node.isVisible());
}
}
_
多くのJavaFX
オブジェクトで見られるように、常により優れています:
_public void setVisiblity(final boolean flag);
_
しかし、.setVisible()
と.setHidden()
は、flagがboolean
は、最も明示的で、最も冗長ではないためです。
複数の選択があるものの場合、これはこの方法で行うことがさらに重要です。 EnumSet
が存在するのはこのためです。
Ed Mannは、この件に関して本当に良いブログ投稿をしています。私は彼の言うことを言い換えようとしていたので、努力を繰り返さないように、この回答への補足として彼の ブログ投稿 へのリンクを投稿します。
(ブール)パラメータを渡すインターフェースのアプローチと、上記のパラメータなしのオーバーロードされたメソッドのどちらを使用するかを決定するときは、使用しているクライアントに注意してください。
すべての使用が定数値(たとえば、true、false)を渡す場合、それはオーバーロードを主張します。
すべての使用法が変数値を渡す場合、それはパラメーターアプローチを使用するメソッドについて主張します。
これらの極端な方法がどちらも当てはまらない場合は、クライアントの使用法が混在していることを意味します。そのため、両方のフォームをサポートするか、またはクライアントの1つのカテゴリを他のクライアントに適合させる(どちらが不自然な)スタイルにするかを選択する必要があります。
設計には2つの考慮事項があります。
それらは混同されるべきではありません。
それは完全に問題ありません:
そのため、API設計のコスト/利点と実装設計のコスト/利点のバランスをとろうとする議論は疑わしく、慎重に検討する必要があります。
API側
プログラマーとして、私は一般にプログラム可能なAPIを支持します。呼び出す関数を決定するために値にif
/switch
ステートメントが必要な場合よりも、値を転送できる場合の方がコードがはるかに明確です。
後者は、各関数が異なる引数を期待する場合に必要になることがあります。
したがって、あなたの場合、単一のメソッドsetState(type value)
の方が良いようです。
ただし、名前のないtrue
、false
、_2
_などなど、これらの魔法の値自体には意味がありません。 原始的な強迫観念を避け、強いタイピングを採用します。
したがって、API POVから、setState(State state)
が必要です。
実装側
もっと簡単なことをすることをお勧めします。
方法が単純な場合は、まとめておくのが最善です。制御フローが複雑な場合は、複数のメソッドに分けて、それぞれがサブケースまたはパイプラインのステップを処理するのが最善です。
最後に、groupingを検討します。
あなたの例では(読みやすくするために空白を追加しています):
_this.layer1.visible = isHintOn;
this.layer2.visible = ! isHintOn;
this.layer3.visible = isHintOn;
_
なぜ_layer2
_がトレンドを後退させているのですか?それは機能ですか、それともバグですか?
2つのリスト_[layer1, layer3]
_と_[layer2]
_を、explicitの名前でグループ化する理由を示し、それらのリストを反復処理することは可能でしょうか? 。
例えば:
_for (auto layer : this.mainLayers) { // layer2
layer.visible = ! isHintOn;
}
for (auto layer : this.hintLayers) { // layer1 and layer3
layer.visible = isHintOn;
}
_
コードはそれ自体が語っています。2つのグループがあり、それらの動作が異なる理由は明らかです。