web-dev-qa-db-ja.com

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

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

public interface IEventGetter {

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

}

問題は、現在、この「終了」パラメーターをコードのどこにも使用していないことです。将来的に使用する必要がある可能性があるため、このパラメーターが存在するだけです。

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

さて、私の質問ですが、このような「尊敬される」コーディンググルのトピックを扱っているソースがあれば、彼にリンクできますか?

42
sveri

[〜#〜] yagni [〜#〜] について学ぶように彼を招待します。 WikipediaページのRationale部分は、ここで特に興味深いかもしれません。

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

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

その他の可能な引数:

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

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

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

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

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

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

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

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

しかし、あなたの同僚は正しいかもしれないことに注意してください。

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

h.j.k。による回答 は、優れたソリューションを提供します。すでに使用されているインターフェースにメソッドを追加することは、それほど難しくはありませんが、場合によっては、かなりのコストがかかります。

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

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

62

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

(今度いつか)

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
    }
}

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

26
h.j.k.

[ある]彼をリンクすることができる「尊敬される」コーディングの教祖は、[彼を説得するために]?

当局への訴えは特に説得力があるわけではない。誰が言ったとしても有効な議論を提示する方がよい。

これがreductio ad absurdumです。これは、感性に関係なく、同僚が「正しい」ことで立ち往生していることを説得または実証する必要があります。


あなたが本当に必要なのは

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

クリンゴン語の国際化がいつ必要になるかわからないので、改造するには多くの作業が必要であり、クリンゴン語の忍耐力は知られていないので、今すぐ対処することをお勧めします。

18
msw

ソフトウェアエンジニアリングの観点からは、この種の問題の適切な解決策はビルダーパターンにあると私は信じています。これは間違いなくあなたの同僚のための「達人」作者からのリンク 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) {...}
    ...
}
12
InformedA

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

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;
    ....

}
7
ToddR

絶対的な設計原則はないので、私は他の答えにはほとんど同意しますが、悪魔の支持者を演じ、同僚の解決策を受け入れることを検討するいくつかの条件について話し合うと思いました。

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

そうは言っても、私の経験では、YAGNIの最大の帰結はYDKWFYNです。必要なフォームがわからない(そうです、私は単にその頭字語を作りました)。 some制限パラメーターが必要な場合でも比較的予測可能かもしれませんが、ページ制限、日数、またはユーザー設定テーブルの終了日を使用することを指定するブール値、物事の数。

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

4
Karl Bielefeldt

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

以下は、使用されているかどうかにかかわらず、追加のパラメーターをサポートする必要があるメソッドの明白な例です。

void CapitalizeSubstring (String capitalize_me, int start_index);

コレクションの開始は指定できるが、終了は指定できないコレクションを操作するメソッドの実装は、多くの場合ばかげています。

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

2
QuestionC

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

彼の提案したインターフェースから、

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

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

だからより良いインターフェースは:

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

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

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

そうすれば、クライアントは終了時間を指定する必要さえ感じなくなります。

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

私の提案は、メソッドが現在何をしているのかから来ており、それが将来やるべきことからではないことに注意してください。そのため、YAGNIの原則(実際に非常に有効です)はここでは適用されません。

1
bowmore

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

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

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

0
usr