web-dev-qa-db-ja.com

インターフェイスデザイン-パラメータの選択

状況

目的のプロセスに十分な情報を提供する1つまたは少数のパラメーターを使用する方がよいですか、それとも十分な情報を提供するより具体的なパラメーターを使用する必要がありますか?

パラメータをより具体的にするとき、それらをより具体的にするプロセスがビジネスルール/ロジックまたは非常に特定のクラスとの重い結合を含む手段が必要な場合、それはまだ行われるべきですか?

インターフェイスの場合:

public interface IQuestion
{
    // Specificity in Interface parameters increases Coupling right?
    void MyFunction(IPrincipal user, string destination);

    // Is this any better? It allows me to be more flexible in what I'm doing
    // Inside these functions but it also makes them even more coupled right?
    void MyFunction(ControllerContext context);
}

質問

一般的なルールや、一方の方法でもう一方の方法を実行する方が特に良い場合はありますか?

4
Shelby115

目的のプロセスに十分な情報を提供する1つまたは少数のパラメーターがある方がよいですか

「十分すぎる」はここでの危険信号です。

店で支払うとき、レジ係がそれから現金を取り出すようにあなたはあなたの財布全体を与えません。レジ係はあなたの財布がどのようなものかを実際に知る必要はなく、あなたの子供の写真を見る必要もありません、そして顧客は全く財布を持っていないかもしれません。プログラムも同様に機能するはずです。

カプセル化を破る必要のある(または含む可能性のある)より多くの情報を含むバンドル値を渡すことにより(呼び出された関数は、ビジネス以外のデータにアクセスできます)、さらに明確な契約がないため、設計が難読化されます。呼び出されたコードが実際に何を必要とし、何を必要としないかは、それを見るだけでは明らかではありません。その実装を調べて見つける必要があります。 ControllerContextからMyFunctionに何が役立つと正確に期待されているのかわかりません。実行時に爆発する可能性があります。

もちろん、Userdestinationが一緒になった場合、それらは1つのクラスに属しているだけであり、どこにでも分割しても意味がありません。しかし、これが当てはまらないことを示す「十分すぎる」という概念があります...

4
Konrad Morawski

それはバランスです。

一般に、特定のインターフェイスを持つことは、一般的なインターフェイスよりも優れています。関数は、機能するために必要な以上のことを知っているべきではありません。

しかしまた、一般的に、これまで1か所でしか使用されなかったインターフェースの山があるのは良くありません。

したがって、ニーズに合わせて特定のインターフェイスを作成することと、既存のインターフェイスを使用することのバランスをとる必要があります。ただし、それらは適合しませんかなり。ありがたいことに、あなたが正しいことをしているなら、これはめったに起こりません。オブジェクトが単純で単一責任がある場合、関数がそのオブジェクトのpartのみを必要とすることはまれです。

5
Telastyn

目的のプロセスに十分な情報を提供する1つまたは少数のパラメーターを使用する方がよいですか、それとも十分な情報を提供するより具体的なパラメーターを使用する必要がありますか?

ここでTelastynをエコーし​​、バランスを取る必要があることを提案したかったのですが、インターフェイス設計の観点に焦点を当てました。一般に、できる限り前者に大きく傾くことをお勧めします(「できる限り」は、受け取る必要のある情報量に関する設計の安定性への信頼に基づいています)。

警告として、私は、チームにデザインをリリースしてから2か月以内に、すでに数千行のコードがコードベース全体でそれを使用し、複数の作成者によって作成されるという必死の環境で働いてきました。デザインはその段階で効果的に石に固定されており、ユーザーエンドの要件の変更に応じてデザインを変更すると、許容できないカスケード変更が発生し、チームの他のメンバーが調整されたバスであなたをひっくり返したくなるでしょう。それでも同時に、私たちは常に考えを変え、プロセスの途中で新しいデザインのアイデアをブレインストーミングするクライアントと協力していました。私が焦点を当てていることのいくつかは、インターフェース設計が非常に迅速に最も容赦のないプロセスになる環境の外ではやり過ぎかもしれません。

私はここで私の考えの混乱を共有しようとします。

柔軟なパラメータ

ここでは結合にそれほど焦点を当てるのではなく、インターフェイスを通過し、インターフェイスから返される情報の概念的な量だけにスポットライトを当てたいと思います。

関数がそのことを実行するのに十分な情報を渡すだけですか?適切な量​​の情報が何であるかを正確に知っていれば、それは絶対に理想的です。通り過ぎませんか?その結果、インターフェイスのテストが非常に困難になり、不安定なモノリシックタイプに結合され、潜在的に不明瞭な副作用が発生する可能性があります。

私にとっては絶対に理想的ですが、インターフェイス間でのメッセージパッシングが実際にはインターフェイスの安定化が最も難しい部分である場合があります。インターフェースが広いレベルで何をすべきかは明白で不変であるかもしれません、それがする必要がある正確な量の情報は実際に時間とともに変化するかもしれません。

柔軟なパラメータ:正規表現

データ型よりも「情報」に焦点を当てるために使用したい例は、正規表現です。注意として、私は嫌い正規表現です。構文は私に頭痛の種を与えます、そして私はいつもそこで物事を調べなければなりません。それでも、次のように、正規表現インターフェイス設計の安定性に注目する価値があります。

// Search for just about anything you can imagine in a string.
string regex_search(string str, string pattern);

単純、基本、拡張など、あらゆる種類の正規表現標準が互いに競合しているにもかかわらず、新しい正規表現標準をサポートするために拡張し続けても、この上記の設計が安定していることは、私にとって実際には非常に素晴らしいことです。

この場合、string patternは実際には、情報のスーパーバンドルをモデル化しており、可変サイズの文字列を介して実質的に無限の量の情報を伝達できます。このregex_search関数と関数の呼び出し元が、正規表現標準を進化および拡張しているにもかかわらず、安定した(変更されない)状態を維持できるのは、主にこの情報のスーパーバンドルによるものです。

これが少し異なるのは、含まれている情報の量に関係なく、patternregex_searchによって完全に使用されることです。文字列の内容全体が検索に関連します。これは、呼び出しが行われたサイトで必要なだけの情報を追加するだけのstringインターフェイスの容易さと柔軟性によるものです。文字列canには無限の情報を含めることができますが、そのインターフェイスと表現の性質により、必要以上の情報を含める必要はありません。

それにもかかわらず、この品質は、私がめったに言及することのない柔軟なパラメータータイプであり、設計の安定性を高めたいインターフェイス設計者にとって注目に値する品質です。

代わりに、上記のstring patternが実際にIRegEx patternであり、IRegExモデリングSRE(現在は非推奨)であり、パターンの個々の要素をモデリングするための文字列ではなく関数を使用していると想像してください。このようなインターフェースは、新しい標準に直面しても安定した状態を維持するのが困難であり、完全に進化して成長しようとする(おそらく多くの疣贅を伴うモノリシック設計になる)か、新しいインターフェースとおそらく非推奨(したがって新しいregex_search_ex種類の関数に加えて)。

これは、私たちのデザインの豊かさを、あらゆる場所で文字列のようなものを受け入れるものに減らすことを提案するものではありません。それにもかかわらず、ここで得られる安定性は、注目すべき非常に興味深い品質です。インターフェイスの設計は常に綱渡りのバランスを取る行為であり、ある決定の長所と短所を別の決定と比較します。可能性と、それぞれの正確な長所と短所をより認識し始めると、より簡単になります。

安定性

通常、プレーンな古いデータ型やstringintなどの標準型へのインターフェイスレベルの結合についてはあまり考えていません。上記のregex_search関数は、完全に独立した「分離」として記述される場合があります。それでも、それを見るあいまいな方法は、それがまだstringのインターフェース設計に結合されているということです。一般にそのような議論を省略できる理由は、依存関係図では、stringが非常に信じられないほど安定しているからです。言語が存在する限り、デザインが変更される可能性はほとんどありません。そのような変更は、言語に対してこれまでに作成されたほとんどすべてのコードベースを破壊する可能性があるためです。

その結果、stringまたはintが呼び出し元と呼び出し先の両方の実装を変更および中断することを心配せずに、ここでstringパラメーター、パラメータータイプとしてintに到達できることがよくあります。

ただし、ControllerContextのようなユーザー定義型に到達すると、安定性が問題になり始めます。 ControllerContext'sのデザインは変更される可能性がありますか?設計が変更された場合、それは単に成長し、したがって既存の依存関係に影響を与えないのでしょうか、それとも、カスケードの破損につながる可能性のある方法で、パブリックインターフェイスの変更の既存の部分を変更したいという誘惑があるのでしょうか。それはすべてバランスをとる行為です。

反対に、このデザインを使用する場合:

void MyFunction(IPrincipal user, string destination);

...あなたは常に十分な情報を持っていますか? IPrincipalで使用されている既存のパーツの設計は安定したままですか?

これらの質問に完全に答えることは難しいため、設計は困難です。また、そのようなデザインが広く使用されていないかどうかを尋ねる価値はありません。その場合、間違いを犯して変更する方が安かったのに、適切なデザインの選択を前もって考え出すのに時間がかかりすぎる可能性があります。変更のカスケードの多くなしで後で設計。

しかし、ここでの大きな目標は、常に安定性です。変更する必要のあるインターフェースを設計したくはありません。拡張する必要があり、これらの設計を変更せざるを得ない方法で最終的に提供される情報が少なすぎるパラメータータイプを受け入れたくありません。

副作用

考慮すべきことの1つは副作用です。ここでcontextに副作用(変更が加えられた)はありますか?

void MyFunction(ControllerContext context);

...またはMyFunctionは単にcontextから読み取りますか?副作用が関係している場合、これらの種類のバンドルタイプは、関数が必要とするよりも多くの情報を提供するだけでなく、関数が実際に変更するよりも多くの状態を提供するため、はるかに悪化する傾向があります。

その結果、関数がバンドル内で何を変更したかについて、そのようなバンドルを渡すときに信じられないほどの混乱が生じる可能性があり、この種のバンドル情報は、読み取り専用でない場合、はるかに多くの短所をすばやく運ぶ可能性があります。

しかし、同じ種類の懸念がここのuserにも当てはまります。

void MyFunction(IPrincipal user, string destination);

あいまいな副作用はバグの非常に一般的な発生ポイントであるため、通常、副作用を明確にし、多くても1つの論理的な副作用のみを引き起こす関数の設計に努めます。これは、関数に名前を付けたり、それらを文書化したり、場合によっては渡されるパラメータータイプにさえ影響を与える可能性があります。可能であれば、不変性を追求する価値もあります。

たとえば、読み取り専用部分と可変部分に分割できるバンドル状態がある場合、分割は、「入力」パラメーターと「出力」パラメーターを1つのバンドルに混在させるだけでなく、非常に役立つ場合があります。

テスト

バンドルは、その場での構築がいかに簡単かによっては、テストをより困難にする可能性があります。文字列は、適切な量の情報を含めるために、その場で非常に簡単に作成できます。

ControllerContextはそれほど簡単ではない可能性があり、そのインターフェイスは、インターフェイスレベルで必要とされるよりもはるかに多くの機能を含む方法で、不均一なデータを公開したい場合があります。

必要な情報を使用してそのようなバンドルをその場で構築できる容易さは、関連するさまざまなパラメーターに対してインターフェースをテストできる容易さに影響します。バンドルされたデザインがテストを本当に複雑にする可能性がある例は、構成ファイルからのみ構築できる場合です。

バンドルの非一般化

デザインにある程度の安定感を与えるが、必要以上の情報を渡す危険性にあまり踏み込まないようにする方法の1つ、および関連するすべての短所は、バンドルを非一般化し、サイト固有にすることです。

たとえば、ControllerContextに使用されるIQuestionの代わりに、QuestionContextによってのみ使用されるIQuestionを使用する必要があるかもしれません。これにより、必要以上の情報をバンドルしたいという誘惑が緩和されます。

これは、潜在的な不安定性を他の場所に転送したい場合、QuestionContextIQuestionよりも変更しやすい場合に役立つことがよくあります。例として、IQuestionに渡されるパラメーターの基になるデータ表現を拡張または変更する必要があるが、それを使用するこれまでに記述されたすべてのコードに影響を与える方法ではなく、QuestionContext自体の実装のみに影響を与える場合があります。一般的に、多くのインターフェース設計はそれに要約されます。潜在的な不安定性を、変更しやすい場所に移したいと考えています。

ABIおよびTMI

これは、この質問のコンテキストから少し外れている可能性がありますが、ABI(アプリケーションバイナリインターフェイス)が懸念される場合、下位互換性の破損の可能性を減らすために、情報(TMI)が多すぎることが不可欠なメカニズムになる可能性があります。

このような場合、インターフェイスの安定性がこれまで以上に重要になります。これは、インターフェイスの変更によって、既存のソースコードとの互換性が失われるだけでなく、チームの制御の及ばない製品(サードパーティの商用プラグインなど)の既存のバイナリとの互換性が失われる可能性があるためです。

このような場合、次のような関数があります。

void (*some_function)(int x, int y);

...入力と出力に利用できる追加情報を用意しておくと、現在はメリットがないというメリットがあります。

int (*some_function)(int x, int y, void* unused);

本当に醜いですが、戻り値の型とunusedの両方が、some_functionを使用して既存のすべてのクライアントコードを整形してunusedのnullを渡すことにより、他の方法では必要だった下位互換性のABI変更を防ぐためにいつか私たちの尻を救うかもしれません。有効ですが、それでもオプションのパラメーターです。

クライアントに対して不透明でない場合、Cで使用されるstructについても同様のアイデアが適用される可能性があります。いくつかの未使用のフィールドがあると、その日、ある日を節約し、非推奨や破損の必要性を防ぐことができます。同様に、以前に使用されていた一部のフィールドは、使用されなくなる可能性があります。

この種の方法は、将来の拡張がオプションのルートに向けられている場合に最も役立ち、広く使用されている既存のインターフェイスを壊すことから新しいオプション(オプションの場合のようにオプション)のための余地を確保できます。

結論

とにかく、これらは、インターフェイスのパラメータと戻り値の型を設計するときに必要な情報の量を正確に把握しようとするときに考慮すべきいくつかのことです。理想的な設計は、必要な情報を正確に受信して返すだけの設計です。しかし、必要以上に受け入れたり返したりする理想的ではない解決策は、変更のための追加の呼吸の余地を残す可能性があります。安定性は最大の目標の1つであり、変化する要件の世界に直面して安定性を達成することは、他の場所でより変化しやすい場所に不安定性を移さない限り、非常に難しい場合があります。

2
user204677