明確な意味を持つ2つの方法、または1つのデュアルユース方法を使用する方が良いでしょうか?


30

インターフェイスを簡素化するには、getBalance()メソッドを持たない方が良いでしょうか?に渡す0charge(float c);同じ結果が得られます。

public class Client {
    private float bal;
    float getBalance() { return bal; }
    float charge(float c) {
        bal -= c;
        return bal;
    }
}

たぶんメモしてくださいjavadoc?または、バランスを取る方法を理解するためにクラスユーザーに任せてください。


26
これは例であり、説明のためのコードであり、通貨値を格納するために浮動小数点演算を使用していないことをgeneしみなく想定しています。そうでなければ、私はすべての説教をしなければなりません。:
corsiKa

1
@corsiKaああ。それはほんの一例です。しかし、はい、実際のクラスでお金を表すためにフロートを使用していたでしょう...浮動小数点数の危険について思い出させてくれてありがとう。
デビッド

10
一部の言語は、ミューテーターとアクセサーを区別して効果を上げています。定数としてのみアクセス可能なインスタンスのバランスを取得できないのは、非常に迷惑です!
JDługosz

回答:


90

インターフェイスの複雑さは、インターフェイスの要素(この場合はメソッド)の数によって測定されることを示唆しているようです。多くの人は、chargeメソッドのバランスを返すためにメソッドを使用できることを覚えておく必要があると、メソッドClientの余分な要素を持つよりもはるかに複雑になると主張しgetBalanceます。物事をより明確にすることは、特にインターフェイス内の要素の数が多いかどうかに関係なく、あいまいさを残さない点で、はるかに簡単です。

さらに、呼び出しは最小驚 as charge(0)原則に違反します。これは1分あたりWTFメトリックとも呼ばれ(クリーンコード、下の画像から)、チームの新しいメンバー(またはコードからしばらく離れた現在のメンバー)が彼らは、コールが実際にバランスをとるために使用されることを理解しています。他の読者がどのように反応するかを考えてください:

ここに画像の説明を入力してください

また、chargeメソッドのシグネチャは、オブジェクトの状態を変更しながら新しい値を返すため、1つのことコマンドとクエリの分離行うというガイドラインに反します。

全体として、この場合の最も単純なインターフェイスは次のようになると思います。

public class Client {
  private float bal;
  float getBalance() { return bal; }
  void charge(float c) { bal -= c; }
}

25
コマンドとクエリの分離に関するポイントは非常に重要です、IMO。あなたがこのプロジェクトの新しい開発者だと想像してください。コードを見ると、getBalance()何らかの値を返し、何も変更しないことがすぐにわかります。一方、charge(0)おそらく何かを変更するように見えます...多分それはPropertyChangedイベントを送信しますか?そして、それは前の値と新しい値のどちらを返しますか?今、あなたはドキュメントでそれを調べ、頭脳力を浪費しなければなりませんが、より明確な方法を使用して回避することができました。
メイジXY

19
また、使用するcharge(0)ことは効果がないことが起こるので、バランスを得るための方法としては、、この実装の詳細にあなたをウェッズ。課金の追跡が重要となる将来の要件を簡単に想像できます。その時点で、実際に課金ではない課金がコードベースに散らばってしまうことが問題になります。クライアントが行う必要のあることをたまたま行うものではなく、クライアントが行う必要があることを意味するインターフェース要素を提供する必要があります。
ベン

12
CQSの警告は必ずしも適切ではありません。以下のためにcharge()そのメソッドが並行システムの原子である場合の方法、新しいか古い値を返すために適切かもしれません。
ディートリッヒエップ

3
CQRSに対する2つの引用は非常に説得力のないものでした。たとえば、私は一般にMeyerのアイデアが好きですが、関数の戻り値の型を見て、それが何かを変更するかどうかを判断することはarbitrary意的で不確実です。多くの同時または不変のデータ構造では、突然変異+リターンが必要です。CQRSに違反せずに文字列にどのように追加しますか?より良い引用はありますか?
user949300

6
コマンドクエリ分離に準拠するコードはほとんどありません。人気のあるオブジェクト指向言語ではまったく慣用的ではなく、これまで使用したすべての言語の組み込みAPIに違反しています。したがって、それを「ベストプラクティス」として説明することは奇妙に思えます。実際にこのパターンに従うソフトウェアプロジェクトを1つでも挙げることができますか?
マークアメリー

28

IMO、交換するgetBalance()charge(0)、アプリケーション全体では、簡単ではありません。はい、行は少なくなりますが、charge()メソッドの意味がわかりにくくなります。このコードを再検討する必要がある場合、潜在的に頭を悩ます可能性があります。

それらは同じ結果をもたらすかもしれませんが、口座の残高を取得することはゼロの請求と同じではないので、懸念を分離することがおそらく最善です。たとえばcharge()、アカウントトランザクションがあるたびにログに記録するように変更する必要がある場合、問題が発生しているため、機能を分離する必要があります。


13

コードは自己文書化する必要があることを覚えておくことが重要です。に電話するとcharge(x)x請求されることを期待しています。バランスに関する情報は二次的です。さらに、私はcharge()それを呼び出すときにどのように実装されているのかわからないかもしれませんし、明日どのように実装されるのか絶対にわかりません。たとえば、この潜在的な将来の更新を検討してcharge()ください:

float charge(float c) {
    lockDownUserAccountUntilChargeClears();
    bal -= c;
    Unlock();
    return bal;
}

charge()バランスをとるために突然使用することは、あまり良くありません。


はい!chargeはるかに重いという優れた点。
デデュプリケーター

2

charge(0);残高の取得に使用するのは悪い考えです:ある日、誰かがそこにコードを追加して、他の機能の使用を認識せずに行われた請求を記録し、誰かが残高を取得するたびに請求として記録されます。(これを回避する方法があります。たとえば、次のような条件付きステートメントです。

if (c > 0) {
    // make and log charge
}
return bal;

しかし、これらはそれらを実装することを知っているプログラマーに依存します。それらが必要であることがすぐに明らかでないならば、彼はそれをしません。

要するに、ユーザーまたはプログラマーの後継者charge(0);がバランスをとる正しい方法であることに気づかないでください。可能。


0

たくさんの答えがあることは知っていますが、反対するもう1つの理由charge(0)は、単純なタイプミスcharge(9)が原因で、顧客の残高を取得するたびに残高が減少することです。優れた単体テストがあれば、そのリスクを軽減できる可能性がありますが、電話をかけるたびに勤勉にならなければ、chargeこのような事故が起こる可能性があります。


-2

私はそれが特定のケースに言及したいと思います少ない、より多目的、メソッドを持ってしても意味が:ある多型の多くは、多くがあれば実装このインタフェースを、特に、これらの実装が同期して更新できない個別に開発されたコードである場合(インターフェイスはライブラリによって定義されます)。

その場合、各実装を記述する作業を単純化することは、その使用の明確さよりもはるかに価値があります。前者は契約違反のバグを回避するためです(2つの方法は互いに矛盾します)。ヘルパー関数や定義スーパークラスのメソッドによって回収さgetBalanceの面でcharge

(これはデザインパターンであり、特定の名前は思い出せません:実装者に優しい最小限のインターフェイスという点で、呼び出し元に優しい複雑なインターフェイスを定義します。クラシックMac OSでは、描画操作の最小インターフェイスは「ボトルネック」と呼ばれていましたしかし、これは一般的な用語ではないようです。)

これが当てはまらない場合(実装が少ないか、正確に1つある場合)、明確にするためにメソッドを分離し、ゼロ以外の電荷に関連する動作を単純に追加できるようにすることcharge()は理にかなっています。


1
実際、完全なカバレッジテストを簡単にするために、より最小限のインターフェイスが選択された場合がありました。ただし、これは必ずしもクラスのユーザーに公開されるわけではありません。例えば、insertappend、およびdeleteは、すべての制御パスが十分に研究され、テストされている一般的な置換の単純なラッパーです。
JDługosz

私はそのようなAPIを見てきました。Lua 5.1+アロケーターが使用します。1つの関数を提供し、渡されるパラメーターに基づいて、新しいメモリの割り当て、メモリの割り当て解除、または古いメモリからのメモリの再割り当てのいずれを試みるかを決定します。そのような関数を書くのはつらいです。私はむしろ、Luaが割り当てと割り当て解除の2つの機能を与えただけだと思います。
ニコルボーラス

@NicolBolas:そして再割り当てはありませんか?とにかく、そのreallocシステムには、一部のシステムでは、ほとんどの場合と同じ「利点」が追加されることがあります。
デデュプリケーター

@Deduplicator:割り当てと再割り当ては... OK それらを同じ関数に入れるのは素晴らしいことではありませんが、同じものに割り当てを解除するほど悪くはありません。また、簡単に区別できます。
ニコルボーラス

割り当て解除と(再)割り当てを混同することは、このタイプのインターフェースの特に悪い例です。(割り当て解除とは非常に異なる契約があります。有効なポインターにはなりませ。)
ケビンリード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.