インターフェースを将来使用するためのプログラミング


42

次のようなインターフェイスを設計した同僚が隣に座っています。

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

問題は、現時点では、この「end」パラメーターをコードのどこにも使用していないことです。将来的に使用する必要があるため、そこにあるだけです。

現在、役に立たないインターフェースにパラメーターを入れるのは悪い考えだと彼に納得させようとしていますが、「終了」日付の使用をいつか実装すると、多くの作業が必要になると主張し続けます。その後、すべてのコードを適応させる必要があります。

さて、私の質問は、「尊敬される」コーディングの達人のようなトピックを扱っているソースはありますか?


29
「予測は、特に将来については非常に困難です。」
ヨルグWミットタグ14

10
問題の一部は、そもそも最適なインターフェースではないということです。Foosの取得とフィルタリングは、2つの別個の問題です。このインターフェイスは、特定の方法でリストをフィルターすることを強制します。より一般的なアプローチは、fooを含めるかどうかを決定する関数を渡すことです。あなたは、Java 8へのアクセスを持っている場合は、これが唯一のエレガントな
Doval

5
対抗するのがポイントです。単にその方法は、今必要なものだけを含んでおり、最終的に追加することができるためのカスタムタイプの玉葉を受け入れる作りend、そのオブジェクトにパラメータをしても、コードを壊さないために、それをデフォルト
レミ

3
Java 8のデフォルトメソッドでは、不要な引数を追加する理由はありません。メソッドは、たとえばで定義されたを単純に呼び出すデフォルトの実装で追加できますnull。クラスを実装すると、必要に応じてオーバーライドできます。
スパイダーボリス14

1
私はJavaのことは知らない@Dovalが、.NETで、あなたは時々の癖を暴露を避けるためにのようなものが表示されますIQueryableDAL外のコードに(のみ、特定の式を取ることができます)
ベン・アーロンソン

回答:


62

彼にYAGNIについて学ぶように勧めてください。ウィキペディアのページの理論的部分はここで特に興味深いかもしれません:

YAGNIアプローチを支持する人々によると、現時点では必要ではないが、将来的には必要になる可能性があるコードを記述する誘惑には、次のような欠点があります。

  • 費やされる時間は、必要な機能の追加、テスト、または改善に費やされます。
  • 新機能は、デバッグ、文書化、およびサポートする必要があります。
  • 新しい機能は、将来何ができるかに制約を課します。そのため、不必要な機能は、必要な機能が将来追加されることを妨げる可能性があります。
  • 機能が実際に必要になるまで、何をすべきかを完全に定義してテストすることは困難です。新しい機能が適切に定義およびテストされていない場合、最終的に必要になったとしても、正しく機能しない可能性があります。
  • コードの膨張につながります。ソフトウェアがより大きく複雑になります。
  • 仕様とある種のリビジョン管理がない限り、この機能はそれを利用できるプログラマーには知られていません。
  • 新しい機能を追加すると、他の新しい機能が提案される場合があります。これらの新しい機能が実装された場合、これは機能のクリープに向かって雪だる​​ま効果をもたらす可能性があります。

その他の可能な引数:

  • 「ソフトウェアのライフタイムコストの80%はメンテナンスに費やされます」ジャストインタイムでコードを記述すると、メンテナンスのコストが削減されます。つまり、メンテナンスするコードが少なくなり、実際に必要なコードに集中できます。

  • ソースコードは一度書かれますが、何十回も読みます。どこでも使用されていない追加の引数は、不要な引数がある理由を理解するのに時間を浪費することになります。これがいくつかの可能な実装とのインターフェースであることを考えると、事態はさらに難しくなります。

  • ソースコードは自己文書化されることが期待されています。読者はendメソッドの結果または実行に影響すると考えるため、実際の署名は誤解を招くものです。

  • このインターフェイスの具体的な実装を作成する人は、最後の引数を使用してはならないことを理解していない可能性があり、異なるアプローチにつながる可能性があります。

    1. 必要ないのでend、その値を無視します。

    2. 必要ないのでend、そうでない場合は例外をスローしますnull

    3. 必要ありませんがend、どういうわけかそれを使用しようとします、

    4. 後でend必要になるときに使用される可能性のある多くのコードを記述します。

ただし、同僚が正しい場合があることに注意してください。

これまでのすべてのポイントは、リファクタリングが簡単であるという事実に基づいているため、後で引数を追加するのに大きな労力は必要ありません。しかし、これはインターフェースであり、インターフェースとして、製品の他の部分に貢献する複数のチームによって使用される場合があります。これは、インターフェイスの変更が特に苦痛になる可能性があることを意味します。その場合、YAGNIはここでは実際には適用されません。

hjkの答えは良い解決策を提供します。すでに使用されているインターフェイスにメソッドを追加することは特に難しくはありませんが、場合によってはかなりのコストがかかります。

  • 一部のフレームワークはオーバーロードをサポートしていません。たとえば、よく覚えている場合(間違っている場合は修正してください)、. NETのWCFはオーバーロードをサポートしていません。

  • インターフェイスに具体的な実装が多数ある場合、インターフェイスにメソッドを追加するには、すべての実装を調べて、そこにメソッドを追加する必要があります。


4
この機能が将来どの程度登場するかを考慮することも重要だと思います。確かに追加されますが、何らかの理由で今は追加されない場合、それは単なる「ケース」機能ではないので、覚えておくと良いでしょう。
Jeroen Vannevel 14

3
@JeroenVannevel:確かに、しかしそれは非常に投機的です。私の経験から、確かに実装されると信じていたほとんどの機能はキャンセルされるか、永遠に遅延しました。これには、利害関係者によって非常に重要であると当初強調されていた機能が含まれます。
Arseni Mourzenko

26

しかし、彼は、しばらくして「終了」日付の使用を実装し、すべてのコードを適応させる必要がある場合、多くの作業を行う必要があると主張し続けています。

(今度いつか)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

必要なのは、メソッドのオーバーロードだけです。将来の追加のメソッドは、既存のメソッドの呼び出しに影響を与えることなく透過的に導入できます。


6
変更がインターフェイスではなくなったことを意味することを除いて、オブジェクトが既にオブジェクトから派生しており、インターフェイスを実装している場合は使用できません。インターフェイスを実装するすべてのクラスが同じプロジェクト内にある場合、それは些細な問題です(コンパイルエラーを追跡します)。複数のプロジェクトで使用されるライブラリの場合、フィールドを追加すると、今後xか月/年にわたって散発的に他の人に混乱と作業が発生します。
キヴェーリ

1
OPのチーム(同僚を救う)が本当にendパラメーターが不要であると信じてendおり、プロジェクト全体で-lessメソッドを使用し続けている場合、インターフェイスを更新することは、実装クラス。私の答えは、特に「すべてのコードを適応させる」部分を対象としています。これは、同僚がプロジェクト全体で元のメソッドの呼び出し元を更新して3番目のデフォルト/ダミーパラメータを導入する必要があると考えているようだからです。
hjk 14

18

[そこに]彼をリンクさせることができる「尊敬された」コーディングの達人[彼を説得する]

権威への控訴は特に説得力がない。誰が言ったとしても有効な議論を提示する方が良い。

これは、同僚が感性に関係なく「正しい」ことに固執していることを説得または実証するための不条理な広告です。


本当に必要なのは

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

クリンゴン語で国際化が必要になる時期はわからないので、改修するには多くの作業が必要になり、クリンゴン語は忍耐力があることで知られていません。


22
You never know when you'll have to internationalize for Klingon。だから、aを渡すだけでCalendar、クライアントコードにa GregorianCalendarまたはa のどちらを送信するかを決定させる必要がありますKlingonCalendar(既にいくつかを実行したに違いありません)。ああ。:-p
SJuan76 14

3
何についてStarDate sdStartStarDate sdEnd?我々は、すべての後に連盟...忘れることができない
WernerCD

これらはすべて、明らかにサブクラスであるか、変換可能Dateです!
ダークホッグ14

それだけが簡単だったら。
msw 14

@msw、「尊敬されるコーディングの達人」へのリンクを求めたとき、「権威への訴え」を求めていたのかどうかわかりません。あなたが言うように、彼は「誰が言ったとしても有効な議論」を必要とします。「達人」タイプの人々はそれらの多くを知っている傾向があります。また、あなたは忘れてしまったHebrewDate startHebrewDate end
トリシス

12

ソフトウェアエンジニアリングの観点から、この種の問題に対する適切な解決策はビルダーパターンにあると考えています。これは間違いなくあなたの同僚http://en.wikipedia.org/wiki/Builder_patternの「グル」著者からのリンクです。

ビルダーパターンでは、ユーザーはパラメーターを含むオブジェクトを作成します。このパラメーターコンテナーは、メソッドに渡されます。これにより、同僚が将来必要とするパラメータの拡張とオーバーロードが処理され、変更が必要な場合は全体が非常に安定します。

あなたの例は次のようになります:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}

3
これは良い一般的なアプローチですが、この場合、問題をからIEventGetterにプッシュするだけのようISearchFootRequestです。代わりに、アイテムを含めるかどうかを返すpublic IFooFilterメンバーのようなものを提案public bool include(FooType item)します。そして、個々のインターフェイスの実装は、彼らがそのフィルタリングやる方法を決めることができます
ベンアーロンソン

@Benアーロンソン私は、要求パラメータオブジェクトが作成される方法を示すために、コードを編集
InformedA

ここでは、ビルダーは不要です(そして、率直に言って、一般的には過度に使用される/推奨されるパターンです)。パラメータの設定方法に関する複雑なルールはありません。そのため、メソッドパラメータが複雑または頻繁に変更されることに正当な懸念がある場合、パラメータオブジェクトは完全に問題ありません。パラメーターオブジェクトを使用するポイントは、不要なパラメーターをすぐに含めることではなく、後で追加するのが非常に簡単になるようにすることですが(複数のインターフェイス実装がある場合でも)、@ BenAaronsonに同意します。
アーロンノート14

7

今は必要ないので、追加しないでください。後で必要な場合は、インターフェースを拡張します。

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

+1(既存の(おそらく既にテスト済み/出荷済みの)インターフェイスを変更しない場合)。
セバスチャンゴデレット14

4

絶対的な設計原則はないので、他の答えにはほぼ同意しますが、悪魔の擁護者を演じ、同僚の解決策を受け入れることを検討するいくつかの条件を議論すると思いました。

  • これがパブリックAPIであり、内部で使用していなくても、サードパーティの開発者にとってこの機能が役立つと予想される場合。
  • 将来のメリットだけでなく、目先のメリットがかなりある場合。暗黙の終了日がの場合、Now()パラメータを追加すると副作用がなくなり、キャッシュと単体テストにメリットがあります。多分それはより簡単な実装を可能にします。または、コード内の他のAPIとの一貫性が高い場合があります。
  • 開発文化に問題のある歴史がある場合。プロセスや領土により、インターフェイスのような中心的なものを変更するのが非常に困難な場合、私が以前に見たのは、インターフェイスを変更する代わりにクライアント側の回避策を実装する人々であり、その後、ダースの終了日を維持しようとしています1つではなくフィルター。会社でこのようなことが頻繁に発生する場合は、将来の校正にもう少し力を入れるのが理にかなっています。誤解しないでください。開発プロセスを変更した方が良いですが、これは通常、言うよりも簡単です。

そうは言っても、私の経験では、YAGNIの最大の帰結はYDKWFYNです。必要な形式はわかりません(はい、その頭字語を作成しました)。ある程度の制限パラメーターが必要な場合でも、比較的予測しやすいかもしれませんが、ページ制限、日数、ユーザー設定テーブルの終了日を使用することを指定するブール値、または任意の数の形式を取ります。

まだ要件がないため、そのパラメーターがどのタイプであるかを知る方法がありません。多くの場合、最終的には要件に最適ではない厄介なインターフェイスを使用するか、とにかく変更する必要があります。


2

この質問に答えるのに十分な情報がありません。それはgetFooList実際に何をするか、そしてそれをどのように行うかに依存します。

使用されているかどうかにかかわらず、追加のパラメーターをサポートするメソッドの明らかな例を次に示します。

void CapitalizeSubstring (String capitalize_me, int start_index);

コレクションの開始ではなく終了を指定できるコレクションを操作するメソッドを実装することは、多くの場合愚かなことです。

あなたは本当に問題自体を見て、パラメータがインターフェース全体の文脈において無意味であるかどうか、そして実際に追加のパラメータがどれだけの負担をかけるかを尋ねる必要があります。


1

あなたの同僚が実際に非常に有効なポイントを持っているのではないかと思います。彼の解決策は実際には最高ではありませんが。

彼の提案されたインターフェースから、それは明らかです

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

時間内に見つかったインスタンスを返しています。クライアントが現在endパラメータを使用していない場合、時間内にインスタンスが見つかることを期待するという事実は変わりません。これは、現在、すべてのクライアントがオープンエンドの間隔を使用することを意味します(開始から永遠まで)

したがって、より良いインターフェースは次のようになります。

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

静的ファクトリーメソッドでintervalを指定する場合:

public static Interval startingAt(Date start) { return new Interval(start, null); }

クライアントは終了時間を指定する必要さえ感じません。

同時に、インターフェースはそれがgetFooList(String, Date)間隔を扱っていることを伝えないため、インターフェースが行うことをより正確に伝えます。

私の提案は、メソッドが現在行うことから来るものであり、将来行うべき、または行うかもしれないものからではなく、そのため、YAGNI原則(実際に非常に有効)はここでは適用されません。


0

未使用のパラメーターを追加すると混乱します。この機能が機能すると仮定して、人々はそのメソッドを呼び出すかもしれません。

追加しません。コールサイトをリファクタリングし、機械的に修正して後で追加するのは簡単です。静的に型付けされた言語では、これは簡単です。熱心に追加する必要はありません。

そのパラメーターを使用する理由ある場合、混乱を防ぐことができます。そのインターフェースの実装にアサートを追加して、このパラメーターがデフォルト値として渡されるようにします。誰かが少なくともそのパラメータを誤って使用した場合、テスト中にこの機能が実装されていないことにすぐに気付くでしょう。これにより、本番環境にバグがスリップするリスクがなくなります。

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