Apakah praktik yang baik untuk menerapkan dua metode standar Java 8 dalam hal satu sama lain?

14

Saya merancang antarmuka dengan dua metode terkait, mirip dengan ini:

public interface ThingComputer {
    default Thing computeFirstThing() {
        return computeAllThings().get(0);
    }

    default List<Thing> computeAllThings() {
        return ImmutableList.of(computeFirstThing());
    }
}

Sekitar setengah implementasi hanya akan menghitung satu hal, sedangkan setengah lainnya dapat menghitung lebih.

Apakah ini memiliki preseden dalam kode Java 8 yang banyak digunakan? Saya tahu Haskell melakukan hal serupa di beberapa jenis kacamata ( Eqmisalnya).

Keuntungannya adalah saya harus menulis kode yang jauh lebih sedikit daripada jika saya memiliki dua kelas abstrak ( SingleThingComputerdan MultipleThingComputer).

The downside adalah bahwa implementasi kosong mengkompilasi tetapi meledak saat runtime dengan StackOverflowError. Dimungkinkan untuk mendeteksi rekursi timbal balik dengan a ThreadLocaldan memberikan kesalahan yang lebih bagus, tetapi itu menambah biaya tambahan pada kode non-kereta.

Barnes Tavian
sumber
3
Mengapa Anda dengan sengaja menulis kode yang dijamin memiliki rekursi tak terbatas? Tidak ada hal baik yang bisa datang dari situ.
1
Yah itu tidak "dijamin"; implementasi yang tepat akan menimpa salah satu metode tersebut.
Tavian Barnes
6
Anda tidak tahu itu. Kelas atau antarmuka perlu berdiri sendiri dan tidak menganggap bahwa subkelas akan melakukan hal-hal dengan cara tertentu untuk menghindari kesalahan logika utama. Kalau tidak, ia rapuh dan tidak bisa memenuhi jaminannya. Perhatikan bahwa saya tidak mengatakan antarmuka Anda dapat atau harus membuat kelas pelaksana melakukan throw new Error();atau sesuatu yang bodoh, hanya saja antarmuka itu sendiri tidak boleh memiliki kontrak rapuh melalui defaultmetode.
Saya setuju dengan @Snowman, ini ranjau darat. Saya pikir itu buku teks "kode jahat" dalam arti arti Jon Skeet - perhatikan bahwa kejahatan tidak sama dengan buruk , itu jahat / pintar / menggoda, tetapi masih salah :) Saya merekomendasikan youtu.be/lGbQiguuUGc?t=15m26s ( atau memang, keseluruhan pembicaraan untuk konteks yang lebih luas - sangat menarik).
Konrad Morawski
1
Hanya karena masing-masing fungsi dapat didefinisikan dalam pengertian yang lain tidak berarti bahwa keduanya dapat didefinisikan dalam pengertian satu sama lain. Itu tidak terbang dalam bahasa apa pun. Anda ingin antarmuka dengan 2 pewaris abstrak, masing-masing mengimplementasikan satu dalam hal yang lain tetapi abstrak yang lain sehingga implementator memilih sisi mana yang ingin mereka terapkan dengan mewarisi dari kelas abstrak yang benar. Satu sisi harus didefinisikan independen dari yang lain, atau pop pergi tumpukan . Anda memiliki default gila yang menganggap pelaksana akan menyelesaikan masalah bagi Anda, abstractada untuk memaksa mereka menyelesaikannya.
Jimmy Hoffa

Jawaban:

16

TL; DR: jangan lakukan ini.

Apa yang Anda tunjukkan di sini adalah kode rapuh.

Antarmuka adalah kontrak. Dikatakan "terlepas dari objek apa yang Anda dapatkan, ia dapat melakukan X dan Y." Seperti yang tertulis, antarmuka Anda tidak X atau Y karena dijamin menyebabkan stack overflow.

Melempar Kesalahan atau subkelas menunjukkan kesalahan parah yang tidak boleh ditangkap:

Sebuah Kesalahan adalah subkelas Throwable yang mengindikasikan masalah serius yang tidak boleh ditangkap oleh aplikasi yang masuk akal.

Lebih jauh, VirtualMachineError , kelas induk StackOverflowError , mengatakan ini:

Dilemparkan untuk menunjukkan bahwa Java Virtual Machine rusak atau telah kehabisan sumber daya yang diperlukan untuk terus beroperasi.

Program Anda seharusnya tidak peduli dengan sumber daya JVM . Itu adalah pekerjaan JVM. Membuat program yang menyebabkan kesalahan JVM sebagai bagian dari operasi normal buruk. Ini menjamin program Anda akan macet, atau memaksa pengguna antarmuka ini untuk menjebak kesalahan yang seharusnya tidak perlu diperhatikan.


Anda mungkin mengenal Eric Lippert dari upaya seperti emeritus "anggota komite desain bahasa C #". Dia berbicara tentang fitur bahasa yang mendorong orang menuju kesuksesan atau kegagalan: sementara ini bukan fitur bahasa atau bagian dari desain bahasa, maksudnya sama-sama valid ketika datang ke mengimplementasikan antarmuka atau menggunakan objek.

Anda ingat di The Princess Bride ketika Westley bangun dan menemukan dirinya terkunci di The Pit Of Despair dengan albino yang serak dan pria berjari enam yang jahat, Count Rugen? Gagasan prinsip jurang keputusasaan ada dua. Pertama, bahwa itu adalah lubang, dan karena itu mudah jatuh ke dalam tetapi sulit untuk keluar dari pekerjaan. Dan kedua, itu menyebabkan keputusasaan. Maka nama.

Sumber: C ++ dan Pit Of Despair

Memiliki antarmuka melempar secara StackOverflowErrordefault mendorong pengembang ke Pit of Despair dan merupakan ide yang buruk . Sebaliknya, dorong pengembang ke Pit of Success . Buatlah mudah untuk menggunakan antarmuka Anda dengan benar dan tanpa menabrak JVM.

Mendelegasikan antara metode tidak masalah di sini. Namun, ketergantungan harus berjalan satu arah. Saya suka memikirkan delegasi metode seperti grafik terarah . Setiap metode adalah simpul pada grafik. Setiap kali suatu metode memanggil metode lain, gambarkan tepi dari metode pemanggilan ke metode yang dipanggil.

Jika Anda menggambar grafik dan memperhatikannya adalah siklik, itu adalah bau kode. Itu adalah potensi untuk mendorong pengembang di Pit of Despair. Perhatikan bahwa itu tidak boleh dilarang secara kategoris, hanya saja seseorang harus berhati-hati . Algoritma rekursif secara khusus akan memiliki siklus dalam grafik panggilan: itu bagus. Dokumentasikan dan peringatkan pengembang. Jika tidak berulang, cobalah untuk memutus siklus itu. Jika Anda tidak bisa, cari tahu input apa yang mungkin menyebabkan stack overflow dan mitigasi atau dokumentasikan sebagai kasus terakhir jika tidak ada yang lain yang berfungsi.

Komunitas
sumber
Jawaban yang bagus Saya ingin tahu mengapa ini adalah praktik yang umum di Haskell saat itu. Budaya dev yang berbeda kurasa.
Tavian Barnes
Saya tidak berpengalaman dalam Haskell dan tidak dapat berbicara jika atau mengapa ini lebih lazim dalam pemrograman fungsional.
Melihat lebih dalam lagi, tampaknya komunitas Haskell juga tidak terlalu senang dengannya. Kompiler juga baru-baru ini belajar untuk memeriksa "definisi lengkap minimal," sehingga implementasi kosong setidaknya akan menghasilkan peringatan.
Tavian Barnes
1
Sebagian besar bahasa pemrograman fungsional memiliki optimasi panggilan ekor. Dengan optimasi ini, rekursi ekor tidak akan menghancurkan tumpukan, sehingga praktik seperti itu masuk akal.
Jason Yeo