Pemrograman untuk penggunaan antarmuka di masa depan

42

Saya memiliki seorang kolega yang duduk di sebelah saya yang mendesain antarmuka seperti ini:

public interface IEventGetter {

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

}

Masalahnya adalah, saat ini, kita tidak menggunakan parameter "end" ini di mana pun dalam kode kita, itu hanya ada karena kita mungkin harus menggunakannya beberapa waktu di masa depan.

Kami mencoba meyakinkannya bahwa ide buruk untuk menempatkan parameter ke antarmuka yang tidak digunakan saat ini, tetapi dia terus bersikeras bahwa banyak pekerjaan yang harus dilakukan jika kita menerapkan penggunaan tanggal "akhir" beberapa saat. nanti dan harus mengadaptasi semua kode itu.

Sekarang, pertanyaan saya adalah, apakah ada sumber yang menangani topik seperti ini dari guru pengkodean "terhormat" yang bisa kita tautkan dengannya?

sveri
sumber
29
"Prediksi sangat sulit, terutama tentang masa depan."
Jörg W Mittag
10
Sebagian masalahnya adalah bukan antarmuka terbaik untuk memulai. Mendapatkan Foos dan memfilternya adalah dua masalah terpisah. Antarmuka itu memaksa Anda untuk memfilter daftar dengan satu cara tertentu; pendekatan yang jauh lebih umum adalah meneruskan fungsi yang memutuskan apakah foo harus dimasukkan. Namun, ini hanya elegan jika Anda memiliki akses ke Java 8.
Doval
5
Untuk melawan adalah poin. Cukup buat metode itu menerima whos tipe khusus untuk saat ini hanya berisi apa yang Anda butuhkan dan pada akhirnya dapat menambahkan endparameter ke objek itu dan bahkan secara default agar tidak merusak kode
Rémi
3
Dengan metode standar Java 8 tidak ada alasan untuk menambahkan argumen yang tidak perlu. Suatu metode dapat ditambahkan kemudian dengan implementasi default yang memanggil panggilan sederhana dengan, katakanlah null,. Menerapkan kelas kemudian dapat menimpa sesuai kebutuhan.
Boris the Spider
1
@Doval Saya tidak tahu tentang Java, tetapi dalam .NET Anda kadang-kadang akan melihat hal-hal seperti menghindari mengekspos kebiasaan IQueryable(hanya dapat mengambil ekspresi tertentu) ke kode di luar DAL
Ben Aaronson

Jawaban:

62

Undang dia untuk belajar tentang YAGNI . Bagian Dasar Pemikiran dari halaman Wikipedia mungkin sangat menarik di sini:

Menurut mereka yang menganjurkan pendekatan YAGNI, godaan untuk menulis kode yang tidak perlu saat ini, tetapi mungkin di masa depan, memiliki kelemahan sebagai berikut:

  • Waktu yang dihabiskan diambil dari menambah, menguji atau meningkatkan fungsionalitas yang diperlukan.
  • Fitur baru harus didebug, didokumentasikan, dan didukung.
  • Setiap fitur baru memberikan batasan pada apa yang dapat dilakukan di masa depan, sehingga fitur yang tidak perlu dapat menghalangi fitur yang diperlukan untuk ditambahkan di masa depan.
  • Sampai fitur tersebut benar-benar diperlukan, sulit untuk sepenuhnya menentukan apa yang harus dilakukan dan mengujinya. Jika fitur baru tidak didefinisikan dan diuji dengan benar, mungkin tidak berfungsi dengan benar, bahkan jika pada akhirnya diperlukan.
  • Ini menyebabkan kode mengasapi; perangkat lunak menjadi lebih besar dan lebih rumit.
  • Kecuali ada spesifikasi dan semacam kontrol revisi, fitur tersebut mungkin tidak diketahui oleh programmer yang dapat memanfaatkannya.
  • Menambahkan fitur baru dapat menyarankan fitur baru lainnya. Jika fitur-fitur baru ini diterapkan juga, ini dapat menghasilkan efek bola salju terhadap creep fitur.

Argumen lain yang mungkin:

  • “80% dari biaya seumur hidup sebuah perangkat lunak digunakan untuk pemeliharaan” . Menulis kode tepat pada waktunya mengurangi biaya perawatan: seseorang harus mempertahankan lebih sedikit kode, dan dapat fokus pada kode yang sebenarnya dibutuhkan.

  • Kode sumber ditulis sekali, tetapi dibaca puluhan kali. Argumen tambahan, tidak digunakan di mana pun, akan menyebabkan waktu terbuang untuk memahami mengapa ada argumen yang tidak diperlukan. Mengingat bahwa ini adalah antarmuka dengan beberapa kemungkinan implementasi membuat semuanya menjadi lebih sulit.

  • Kode sumber diharapkan untuk mendokumentasikan diri sendiri. Tanda tangan yang sebenarnya menyesatkan, karena pembaca akan berpikir bahwa itu endmempengaruhi hasil atau pelaksanaan metode.

  • Orang yang menulis implementasi konkret dari antarmuka ini mungkin tidak mengerti bahwa argumen terakhir tidak boleh digunakan, yang akan mengarah pada pendekatan yang berbeda:

    1. Saya tidak perlu end, jadi saya akan mengabaikan nilainya,

    2. Saya tidak perlu end, jadi saya akan melemparkan pengecualian jika tidak null,

    3. Saya tidak perlu end, tetapi entah bagaimana akan mencoba menggunakannya,

    4. Saya akan menulis banyak kode yang dapat digunakan nanti ketika endakan dibutuhkan.

Namun ketahuilah bahwa kolega Anda mungkin benar.

Semua poin sebelumnya didasarkan pada fakta bahwa refactoring itu mudah, jadi menambahkan argumen nanti tidak akan membutuhkan banyak usaha. Tetapi ini adalah antarmuka, dan sebagai antarmuka, ini dapat digunakan oleh beberapa tim yang berkontribusi pada bagian lain dari produk Anda. Ini berarti bahwa mengubah antarmuka bisa sangat menyakitkan, dalam hal ini, YAGNI tidak benar-benar berlaku di sini.

Jawaban oleh hjk memberikan solusi yang baik: menambahkan metode ke antarmuka yang sudah digunakan tidak terlalu sulit, tetapi dalam beberapa kasus, ia memiliki biaya yang besar juga:

  • Beberapa kerangka kerja tidak mendukung kelebihan beban. Sebagai contoh dan jika saya ingat dengan baik (koreksi saya jika saya salah), .NET WCF tidak mendukung kelebihan beban.

  • Jika antarmuka memiliki banyak implementasi konkret, menambahkan metode ke antarmuka akan memerlukan melalui semua implementasi dan menambahkan metode di sana juga.

Arseni Mourzenko
sumber
4
Saya pikir juga penting untuk mempertimbangkan seberapa besar kemungkinan fitur ini muncul di masa depan. Jika Anda tahu pasti itu akan ditambahkan tetapi tidak sekarang karena alasan apa pun maka saya akan mengatakan itu argumen yang baik untuk diingat karena itu bukan hanya fitur "dalam kasus".
Jeroen Vannevel
3
@ JoenenVannevel: tentu saja, tapi itu sangat spekulatif. Dari pengalaman saya, sebagian besar fitur yang saya yakin pasti akan diimplementasikan dibatalkan atau ditunda selamanya. Ini termasuk fitur yang awalnya disorot sebagai sangat penting oleh para pemangku kepentingan.
Arseni Mourzenko
26

tetapi dia terus bersikeras bahwa banyak pekerjaan yang harus dilakukan jika kita menerapkan penggunaan tanggal "end" beberapa waktu kemudian dan harus mengadaptasi semua kode itu.

(lain kali)

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

Itu yang Anda butuhkan, kelebihan metode. Metode tambahan di masa depan dapat diperkenalkan secara transparan tanpa memengaruhi panggilan ke metode yang ada.

hjk
sumber
6
Kecuali bahwa perubahan Anda berarti itu bukan lagi antarmuka, dan tidak akan mungkin digunakan dalam kasus di mana objek sudah berasal dari objek dan mengimplementasikan antarmuka. Ini masalah sepele jika semua kelas yang mengimplementasikan antarmuka berada di proyek yang sama (chase compile errors). Jika itu adalah perpustakaan yang digunakan oleh banyak proyek, penambahan lapangan menyebabkan kebingungan dan bekerja untuk orang lain pada waktu sporadis selama x bulan / tahun berikutnya.
Kieveli
1
Jika tim OP (simpan kolega) benar-benar percaya bahwa endparameter tidak perlu sekarang dan telah menggunakan endmetode -less di seluruh proyek, maka memperbarui antarmuka adalah yang paling tidak dikhawatirkan karena menjadi keharusan untuk memperbarui kelas implementasi. Jawaban saya menargetkan secara khusus tentang bagian "sesuaikan semua kode", karena sepertinya rekan kerja berpikir sepanjang garis harus memperbarui penelepon metode asli di seluruh proyek untuk memperkenalkan parameter default / dummy ketiga .
hjk
18

[Apakah ada] guru kode "terhormat" yang bisa kita tautkan dengannya [untuk membujuknya]?

Seruan kepada otoritas tidak terlalu meyakinkan; lebih baik menyajikan argumen yang valid tidak peduli siapa yang mengatakannya.

Berikut ini adalah reductio ad absurdum yang harus meyakinkan atau menunjukkan bahwa rekan kerja Anda terjebak pada "benar" terlepas dari sensibilitas:


Yang benar-benar Anda butuhkan adalah

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

Anda tidak pernah tahu kapan Anda harus melakukan internasionalisasi untuk Klingon, jadi lebih baik Anda menjaganya sekarang karena akan membutuhkan banyak pekerjaan untuk retrofit dan Klingon tidak dikenal karena kesabaran mereka.

msw
sumber
22
You never know when you'll have to internationalize for Klingon. Itu sebabnya Anda harus melewati a Calendar, dan biarkan kode klien memutuskan apakah dia ingin mengirim GregorianCalendaratau KlingonCalendar(saya yakin beberapa sudah melakukannya). Duh. :-p
SJuan76
3
Bagaimana dengan StarDate sdStartdan StarDate sdEnd?
Lagipula
Semua itu jelas merupakan subclass dari atau dapat dikonversi ke Date!
Darkhogg
Kalau saja sesederhana itu .
msw
@ msw, saya tidak yakin dia meminta "banding ke otoritas" ketika dia meminta tautan ke "guru pengkodean yang dihormati". Seperti yang Anda katakan, dia membutuhkan "argumen yang valid tidak peduli siapa yang mengatakannya". "Guru" -jenis orang cenderung tahu banyak tentang itu. Anda juga lupa HebrewDate startdan HebrewDate end.
trysis
12

Dari perspektif rekayasa perangkat lunak, saya percaya solusi yang tepat untuk masalah seperti ini adalah dalam pola pembangun. Ini jelas merupakan tautan dari penulis 'guru' untuk kolega Anda http://en.wikipedia.org/wiki/Builder_pattern .

Dalam pola pembangun, pengguna membuat objek yang berisi parameter. Wadah parameter ini kemudian akan diteruskan ke metode. Ini akan menangani setiap ekstensi dan kelebihan parameter yang dibutuhkan rekan Anda di masa depan sambil membuat semuanya menjadi sangat stabil saat perubahan harus dilakukan.

Contoh Anda akan menjadi:

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) {...}
    ...
}
InformedA
sumber
3
Ini adalah pendekatan umum yang baik, tetapi dalam hal ini sepertinya hanya mendorong masalah dari IEventGetterke ISearchFootRequest. Saya malah menyarankan sesuatu seperti public IFooFilterdengan anggota public bool include(FooType item)yang mengembalikan apakah akan memasukkan item atau tidak. Kemudian implementasi antarmuka individu dapat memutuskan bagaimana mereka akan melakukan penyaringan itu
Ben Aaronson
@ Ben Aaronson Saya hanya mengedit kode untuk menunjukkan bagaimana objek parameter Permintaan dibuat
InformedA
Builder tidak diperlukan di sini (dan sejujurnya, pola yang terlalu sering digunakan / direkomendasikan secara umum); tidak ada aturan kompleks tentang bagaimana parameter perlu diatur, sehingga objek parameter baik-baik saja jika ada kekhawatiran yang sah atas parameter metode yang kompleks atau sering diubah. Dan saya setuju dengan @BenAaronson, meskipun titik menggunakan objek parameter bukan untuk memasukkan parameter yang tidak perlu langsung dari kelelawar, tetapi membuatnya sangat mudah untuk ditambahkan nanti (bahkan jika ada beberapa implementasi antarmuka).
Aaronaught
7

Anda tidak membutuhkannya sekarang, jadi jangan menambahkannya. Jika Anda membutuhkannya nanti, perluas antarmuka:

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

}
ToddR
sumber
+1 untuk tidak mengubah antarmuka yang ada (dan mungkin sudah diuji / dikirim).
Sebastian Godelet
4

Tidak ada prinsip desain yang mutlak, jadi sementara saya sebagian besar setuju dengan jawaban lain, saya pikir saya akan berperan sebagai advokat setan dan mendiskusikan beberapa kondisi di mana saya akan mempertimbangkan untuk menerima solusi rekan Anda:

  • Jika ini adalah API publik, dan Anda mengantisipasi fitur yang bermanfaat bagi pengembang pihak ketiga, bahkan jika Anda tidak menggunakannya secara internal.
  • Jika memiliki manfaat langsung yang besar, bukan hanya yang di masa depan. Jika tanggal akhir tersirat adalah Now(), kemudian menambahkan parameter menghapus efek samping, yang memiliki manfaat untuk caching dan pengujian unit. Mungkin memungkinkan untuk implementasi yang lebih sederhana. Atau mungkin lebih konsisten dengan API lain dalam kode Anda.
  • Jika budaya perkembangan Anda memiliki sejarah yang bermasalah. Jika proses atau teritorial membuatnya terlalu sulit untuk mengubah sesuatu yang sentral seperti antarmuka, maka apa yang saya lihat terjadi sebelumnya adalah orang yang mengimplementasikan solusi sisi klien alih-alih mengubah antarmuka, maka Anda mencoba mempertahankan selusin tanggal akhir ad hoc filter bukan satu. Jika hal semacam itu sering terjadi di perusahaan Anda, masuk akal untuk melakukan sedikit usaha lagi dalam pemeriksaan di masa depan. Jangan salah paham, lebih baik ubah proses pengembangan Anda, tetapi ini biasanya lebih mudah diucapkan daripada dilakukan.

Yang sedang berkata, dalam pengalaman saya konsekuensi wajar terbesar bagi YAGNI adalah YDKWFYN: Anda Tidak Tahu Bentuk Apa yang Anda Butuhkan (ya, saya baru saja membuat akronim itu). Bahkan jika memerlukan beberapa parameter batas mungkin relatif dapat diprediksi, itu mungkin mengambil bentuk batas halaman, atau jumlah hari, atau boolean yang menentukan untuk menggunakan tanggal akhir dari tabel preferensi pengguna, atau sejumlah hal lainnya.

Karena Anda belum memiliki persyaratan, Anda tidak dapat mengetahui jenis parameter yang seharusnya. Anda sering berakhir dengan antarmuka yang canggung yang tidak sesuai dengan kebutuhan Anda, atau harus mengubahnya.

Karl Bielefeldt
sumber
2

Tidak ada informasi yang cukup untuk menjawab pertanyaan ini. Itu tergantung pada apa yang getFooListsebenarnya dilakukan, dan bagaimana melakukannya.

Berikut adalah contoh nyata dari metode yang harus mendukung parameter tambahan, digunakan atau tidak.

void CapitalizeSubstring (String capitalize_me, int start_index);

Menerapkan metode yang beroperasi pada koleksi di mana Anda dapat menentukan awal tetapi tidak akhir koleksi sering konyol.

Anda benar-benar harus melihat masalah itu sendiri dan bertanya apakah parameter tidak masuk akal dalam konteks seluruh antarmuka, dan benar-benar berapa banyak beban yang dikenakan oleh parameter tambahan.

QuestionC
sumber
1

Saya khawatir kolega Anda mungkin sebenarnya memiliki poin yang sangat valid. Meskipun solusinya sebenarnya bukan yang terbaik.

Dari antarmuka yang diusulkan jelas bahwa

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

mengembalikan instance yang ditemukan dalam interval waktu tertentu. Jika klien saat ini tidak menggunakan parameter akhir, itu tidak mengubah fakta bahwa mereka mengharapkan instance ditemukan dalam interval waktu. Ini hanya berarti bahwa saat ini semua klien menggunakan interval terbuka (dari awal hingga selamanya)

Jadi antarmuka yang lebih baik adalah:

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

Jika Anda memberikan interval dengan metode pabrik statis:

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

maka klien bahkan tidak akan merasa perlu menentukan waktu akhir.

Pada saat yang sama antarmuka Anda lebih tepat menyampaikan apa yang dilakukannya, karena getFooList(String, Date)tidak berkomunikasi bahwa ini berkaitan dengan interval.

Perhatikan bahwa saran saya mengikuti dari apa yang dilakukan metode saat ini, bukan dari apa yang seharusnya atau mungkin dilakukan di masa depan, dan dengan demikian prinsip YAGNI (yang memang sangat valid) tidak berlaku di sini.

Bowmore
sumber
0

Menambahkan parameter yang tidak digunakan membingungkan. Orang mungkin memanggil metode itu dengan asumsi fitur ini akan berfungsi.

Saya tidak akan menambahkannya. Sangat mudah untuk kemudian menambahkannya menggunakan refactoring dan secara mekanis memperbaiki situs panggilan. Dalam bahasa yang diketik secara statis, ini mudah dilakukan. Tidak perlu menambahkannya dengan penuh semangat.

Jika ada adalah alasan untuk memiliki parameter Anda dapat mencegah kebingungan: Tambahkan menegaskan dalam pelaksanaan antarmuka yang untuk menegakkan bahwa parameter ini dilalui sebagai nilai default. Jika seseorang secara tidak sengaja menggunakan parameter itu setidaknya dia akan segera melihat selama pengujian bahwa fitur ini tidak diterapkan. Ini menghilangkan risiko tergelincirnya bug ke dalam produksi.

usr
sumber