Apa yang lebih baik IllegalStateException atau eksekusi metode silent? [Tutup]

16

Katakanlah saya memiliki kelas MediaPlayer yang memiliki metode play () dan stop (). Apa strategi terbaik untuk digunakan ketika menerapkan metode berhenti jika metode bermain belum dipanggil sebelumnya. Saya melihat dua opsi: melempar pengecualian karena pemain tidak dalam kondisi yang sesuai, atau diam-diam mengabaikan panggilan ke metode berhenti.

Apa yang seharusnya menjadi aturan umum ketika suatu metode tidak seharusnya dipanggil dalam beberapa situasi, tetapi eksekusi itu tidak membahayakan program secara umum?

x2bool
sumber
21
Jangan gunakan pengecualian untuk memodelkan perilaku yang tidak luar biasa.
Alexander - Reinstate Monica
1
Hei! Saya pikir ini bukan duplikat. Pertanyaan saya lebih lanjut tentang IllegalStateException dan pertanyaan di atas adalah tentang menggunakan pengecualian secara umum.
x2bool
1
Alih-alih gagal HANYA diam-diam, mengapa tidak JUGA mencatat anomali sebagai peringatan? Tentunya Java yang Anda gunakan mendukung level log yang berbeda (ERROR, PERINGATAN, INFO, DEBUG).
Eric Seastrand
3
Perhatikan bahwa Set.add tidak memberikan pengecualian jika item tersebut sudah ada di set. Tujuannya adalah untuk "memastikan elemen dalam set", daripada "menambahkan item ke set", sehingga mencapai tujuannya dengan melakukan apa-apa jika elemen tersebut sudah di set.
user253751
2
Word of the day: idempotence
Jules

Jawaban:

37

Tidak ada aturan. Ini sepenuhnya tergantung pada bagaimana Anda ingin membuat API "terasa".

Secara pribadi, dalam pemutar musik, saya pikir transisi dari negara Stoppedke Stoppedmelalui metode Stop()ini adalah transisi keadaan yang benar-benar valid. Ini tidak terlalu berarti, tetapi ini valid. Dengan mengingat hal ini, melempar pengecualian akan tampak seperti hal yang tidak masuk akal dan tidak adil. Itu akan membuat API terasa seperti sosial yang setara dengan berbicara dengan anak yang mengganggu di bus sekolah. Anda terjebak dengan situasi yang menjengkelkan, tetapi Anda bisa mengatasinya.

Pendekatan yang lebih "ramah" adalah untuk mengakui bahwa transisi paling tidak berbahaya dan baik untuk pengembang Anda dengan mengizinkannya.

Jadi, jika itu terserah saya, saya akan melewatkan pengecualian.

MetaFight
sumber
15
Jawaban ini mengingatkan kita bahwa kadang - kadang merupakan ide bagus untuk membuat sketsa transisi keadaan untuk interaksi sederhana sekalipun.
6
Dengan mengingat hal ini, melempar pengecualian akan tampak seperti hal yang tidak masuk akal dan tidak adil. Itu akan membuat API terasa seperti sosial yang setara dengan berbicara dengan anak yang mengganggu di bus sekolah. > Pengecualian tidak dirancang untuk menghukum Anda: Mereka dirancang untuk memberi tahu Anda sesuatu yang tidak terduga telah terjadi. Saya pribadi akan sangat berterima kasih atas MediaPlayer.stop()metode yang membuang IllegalStateExceptionalih - alih menghabiskan berjam-jam kode debugging dan bertanya-tanya mengapa Neraka "tidak ada yang terjadi" (yaitu "itu tidak berfungsi").
errantlinguist
3
Tentu saja, jika desain Anda mengatakan transisi loopback tidak valid maka YA Anda harus melempar pengecualian. Namun, jawaban saya adalah tentang desain di mana transisi itu baik-baik saja.
MetaFight
1
StopOrThrow()terdengar mengerikan. Jika Anda turun lorong itu mengapa tidak menggunakan pola standar TryStop()? Juga, secara umum, ketika perilaku API tidak jelas (karena kebanyakan dari mereka) pengembang tidak diharapkan untuk menebak atau bereksperimen, mereka diharapkan untuk melihat dokumentasi. Itu sebabnya saya sangat menyukai MSDN.
MetaFight
1
Secara pribadi saya lebih suka opsi gagal-cepat, jadi saya akan pergi untuk IllegalStateException untuk menunjukkan kepada pengguna API bahwa ada sesuatu yang salah dalam logikanya, bahkan jika itu tidak berbahaya. Saya sering mendapatkan pengecualian itu dari kode saya sendiri, sangat membantu untuk mendeteksi bahwa pekerjaan saya yang tidak berbelit-belit sudah salah
Walfrat
17

Ada dua jenis tindakan yang mungkin ingin dilakukan:

  1. Secara simultan menguji sesuatu dalam satu keadaan, dan mengubahnya ke yang lain.

  2. Tetapkan sesuatu ke kondisi tertentu, tanpa memperhatikan kondisi sebelumnya.

Beberapa konteks memerlukan satu tindakan, dan beberapa memerlukan yang lain. Jika pemutar media yang mencapai akhir konten akan tetap dalam keadaan "pemutaran", tetapi dengan posisi dibekukan di akhir, maka metode yang secara bersamaan menyatakan bahwa pemutar berada dalam keadaan pemutaran sambil mengaturnya ke "berhenti" keadaan mungkin berguna jika kode ingin memastikan bahwa tidak ada yang menyebabkan pemutaran gagal sebelum permintaan berhenti (yang mungkin penting jika misalnya ada sesuatu yang merekam konten yang diputar sebagai sarana untuk mengubah media dari satu format ke format lain) . Namun, dalam kebanyakan kasus, yang penting adalah bahwa setelah operasi, pemutar media berada dalam kondisi yang diharapkan (yaitu dihentikan).

Jika pemutar media berhenti secara otomatis ketika mencapai akhir media, fungsi yang menyatakan bahwa pemutar sedang berjalan kemungkinan akan lebih menyebalkan daripada membantu, tetapi dalam beberapa kasus mengetahui apakah pemutar sedang berjalan pada saat itu berhenti dapat menjadi berguna. Mungkin pendekatan terbaik untuk memenuhi kedua kebutuhan adalah memiliki fungsi mengembalikan nilai yang menunjukkan keadaan pemain sebelumnya. Kode yang peduli tentang keadaan itu dapat memeriksa nilai kembali; kode yang tidak peduli bisa mengabaikannya.

supercat
sumber
2
+1 untuk catatan tentang mengembalikan kondisi lama: Itu memungkinkan penerapannya dengan cara yang membuat seluruh operasi (menanyakan status dan beralih ke status yang ditentukan) atom, yang setidaknya merupakan fitur yang bagus untuk dimiliki. Ini akan membuatnya lebih mudah untuk menulis kode pengguna yang kuat yang tidak gagal secara sporadis karena beberapa kondisi ras yang aneh.
cmaster - mengembalikan monica
Contoh kode A bool TryStop()dan void Stop()akan membuat ini jawaban yang sangat bagus.
RubberDuck
2
@RubberDuck: Itu bukan pola yang bagus. Nama TryStopakan menyiratkan bahwa pengecualian tidak boleh dilemparkan jika pemain tidak dapat dipaksa ke keadaan berhenti. Mencoba menghentikan pemain ketika sudah berhenti bukanlah kondisi yang luar biasa, tetapi merupakan kondisi yang mungkin menarik bagi penelepon.
supercat
1
Maka saya salah paham jawaban Anda @supercat
RubberDuck
2
Saya ingin menunjukkan bahwa pengujian dan pengaturan dengan cara ini BUKAN merupakan pelanggaran terhadap hukum demeter asalkan API juga menyediakan cara untuk menguji keadaan pemutaran tanpa mengubahnya. Ini bisa menjadi metode yang sama sekali berbeda, katakanlah isPlaying().
candied_orange
11

Tidak ada aturan umum. Dalam kasus khusus ini, niat pengguna API Anda adalah untuk menghentikan pemain dari memainkan media. Jika pemain tidak memainkan media, MediaPlayer.stop()mungkin tidak melakukan apa-apa dan tujuan pemanggil metode masih akan tercapai - media tidak bermain.

Melempar pengecualian akan mengharuskan pengguna API untuk memeriksa apakah pemain saat ini bermain atau menangkap dan menangani pengecualian. Ini akan membuat API lebih dari tugas untuk digunakan.

kmaczuga
sumber
11

Tujuan pengecualian bukan untuk memberi sinyal bahwa sesuatu yang buruk telah terjadi; itu menandakan itu

  1. Sesuatu yang buruk telah terjadi
  2. bahwa saya tidak tahu bagaimana cara memperbaikinya di sini
  3. bahwa si penelepon, atau sesuatu yang berasal dari callstack itu harus tahu bagaimana menghadapinya
  4. jadi saya membatalkan dengan cepat, menghentikan eksekusi jalur kode saat ini untuk mencegah kerusakan atau korupsi data, dan menyerahkannya ke pemanggil untuk membersihkan

Jika mencoba penelepon untuk Stopsebuah MediaPlayeryang sudah dalam keadaan berhenti, ini bukan masalah bahwa MediaPlayertidak bisa tekad (itu hanya dapat melakukan apa-apa.) Ini bukan masalah itu, jika melanjutkan, akan menyebabkan kerusakan atau data korupsi, karena itu dapat berhasil hanya dengan tidak melakukan apa pun. Dan itu sebenarnya bukan masalah yang lebih masuk akal untuk mengharapkan penelepon untuk dapat menyelesaikan daripada MediaPlayer.

Karena itu, Anda tidak boleh melempar pengecualian dalam situasi ini.

Mason Wheeler
sumber
+1. Ini adalah jawaban pertama yang menunjukkan mengapa pengecualian tidak sesuai di sini. Pengecualian menunjukkan bahwa penelepon cenderung perlu tahu tentang kasus luar biasa. Dalam hal ini, hampir pasti klien kelas yang bersangkutan tidak akan peduli apakah panggilan 'stop ()' benar-benar menyebabkan transisi negara, jadi kemungkinan besar tidak ingin tahu tentang pengecualian.
Jules
5

Lihatlah seperti ini:

Jika klien memanggil Stop () ketika pemain tidak bermain, maka Stop () secara otomatis berhasil karena pemain saat ini dalam keadaan berhenti.

17 dari 26
sumber
3

Aturannya adalah Anda melakukan apa yang diperlukan dalam kontrak metode Anda.

Saya bisa melihat banyak cara untuk mendefinisikan kontrak yang bermakna untuk stopmetode semacam itu . Ini bisa sangat valid untuk stopmetode untuk tidak melakukan apa pun jika pemain tidak bermain. Dalam hal ini, Anda menentukan API Anda dengan sasarannya. Anda ingin bertransisi ke stoppednegara, dan stopmetode melakukan itu - hanya dengan bertransisi dari stoppednegara ke dirinya sendiri tanpa kesalahan.

Apakah ada alasan mengapa Anda ingin melemparkan Pengecualian? Jika kode pengguna akan terlihat seperti ini pada akhirnya:

try {
    player.stop();
} catch(IllegalStateException e) {
    // do nothing, player was already stopped
}

maka tidak ada untungnya memiliki pengecualian itu. Jadi pertanyaannya adalah, apakah pengguna akan peduli dengan fakta bahwa pemain sudah berhenti? Apakah dia punya wa lagi untuk menanyakannya?

Pemain mungkin diamati, dan mungkin sudah memberitahu pengamat tentang hal-hal tertentu - misalnya yang memberitahukan pengamat ketika transitons dari playingke stoppingke stopped. Dalam hal ini, tidak perlu metode melempar pengecualian. Memanggil tidak stopakan melakukan apa pun jika pemain sudah berhenti, dan memanggilnya saat tidak berhenti akan memberi tahu pengamat tentang transisi.

Secara keseluruhan, itu tergantung di mana logika Anda akan berakhir. Tapi saya pikir ada pilihan desain yang lebih baik daripada melemparkan pengecualian.

Anda harus melemparkan pengecualian jika penelepon harus melakukan intervensi. Dalam hal ini ia tidak harus melakukannya, Anda bisa membiarkan pemainnya apa adanya dan terus bekerja.

Polygnome
sumber
3

Ada strategi umum sederhana yang membantu Anda membuat keputusan ini.

Pertimbangkan bagaimana pengecualian akan ditangani jika itu akan dilempar.

Jadi bayangkan pemutar musik Anda, klik pengguna berhenti dan kemudian berhenti lagi. Apakah Anda ingin menampilkan pesan kesalahan dalam skenario itu? Saya belum melihat pemain yang melakukannya. Apakah Anda ingin perilaku aplikasi berbeda dari hanya mengklik berhenti sekali (seperti mencatat acara atau mengirim laporan kesalahan di latar belakang)? Mungkin tidak.

Itu berarti pengecualian perlu ditangkap dan ditelan di suatu tempat. Dan itu berarti Anda lebih baik tidak melempar pengecualian di tempat pertama karena Anda tidak ingin mengambil tindakan yang berbeda .

Ini tidak hanya berlaku untuk pengecualian tetapi semua jenis percabangan: pada dasarnya jika tidak perlu ada perbedaan perilaku yang dapat diamati, tidak perlu bercabang.

Yang mengatakan, tidak ada salahnya mendefinisikan stop()metode Anda untuk mengembalikan booleanatau nilai enum yang menunjukkan apakah berhenti itu "berhasil". Anda mungkin tidak akan pernah menggunakannya, tetapi nilai pengembalian bisa lebih mudah dan secara alami diabaikan daripada pengecualian.

biziclop
sumber
2

Berikut ini cara android melakukannya: MediaPlayer

Singkatnya, stopketika startbelum dipanggil bukan masalah, sistem tetap dalam keadaan berhenti, tetapi jika dipanggil pada pemain yang bahkan tidak tahu apa yang sedang diputar, pengecualian dilemparkan, karena tidak ada alasan yang baik untuk memanggil Berhenti disana.

njzk2
sumber
1

Apa yang seharusnya menjadi aturan umum ketika suatu metode tidak seharusnya dipanggil dalam beberapa situasi, tetapi eksekusi itu tidak membahayakan program secara umum?

Saya melihat apa yang bisa menjadi kontradiksi kecil dalam pernyataan Anda yang mungkin membuat jawabannya jelas bagi Anda. Mengapa metode ini tidak seharusnya dipanggil dan eksekusi itu tidak membahayakan ?

Mengapa metode "tidak seharusnya disebut"? Itu tampak seperti pembatasan diri sendiri. Jika tidak ada "kerusakan" atau dampak pada API Anda, maka tidak ada pengecualian. Jika metode ini benar-benar tidak boleh dipanggil karena dapat membuat keadaan tidak valid atau tidak terduga, maka pengecualian harus dilemparkan.

Misalnya jika saya berjalan ke DVD player dan tekan "stop" sebelum menekan "start" dan itu crash, itu tidak masuk akal bagi saya. Seharusnya hanya duduk di sana atau, lebih buruk, mengakui "berhenti" di layar. Kesalahan akan mengganggu dan tidak membantu dengan cara apa pun.

Namun, dapatkah saya memasukkan kode alarm untuk mematikannya "" ketika sudah dimatikan (memanggil metode), yang dapat menyebabkannya memasuki keadaan yang akan mencegahnya mempersenjatai dengan benar nanti? Jika demikian, maka buang kesalahan meskipun, secara teknis, "tidak ada salahnya dilakukan" karena mungkin ada masalah nanti. Saya ingin tahu tentang ini bahkan jika itu tidak benar-benar masuk ke keadaan itu. Hanya kemungkinannya sudah cukup. Ini akan menjadi IllegalStateException.

Dalam kasus Anda, jika mesin negara Anda dapat menangani panggilan tidak valid / tidak perlu maka abaikan saja, jika tidak lakukan kesalahan.

EDIT:

Perhatikan bahwa kompiler tidak meminta kesalahan jika Anda menetapkan variabel dua kali tanpa memeriksa nilainya. Ini juga tidak mencegah Anda menginisialisasi ulang variabel. Ada banyak tindakan yang dapat dianggap sebagai "bug" dari satu perspektif, tetapi hanya "tidak efisien", "tidak perlu", "tidak berguna", dll. Saya mencoba memberikan perspektif itu dalam jawaban saya - melakukan hal-hal ini umumnya tidak dianggap "bug" karena tidak menghasilkan sesuatu yang tidak terduga. Anda mungkin harus mendekati masalah Anda dengan cara yang sama.

Jim
sumber
Seharusnya tidak dipanggil karena memanggil itu menunjukkan bug di pemanggil.
user253751
@immibis: Belum tentu. Misalnya, penelepon itu bisa menjadi pendengar klik pada tombol UI. (Sebagai pengguna, Anda mungkin tidak akan menyukai pesan kesalahan jika Anda secara tidak sengaja mengklik tombol stop jika media sudah berhenti.)
meriton
@meriton Jika itu adalah pendengar klik tombol UI maka jawabannya akan jelas - "lakukan apa pun yang Anda inginkan jika tombol ditekan". Jelas bukan, itu adalah sesuatu yang internal untuk aplikasi.
user253751
@immibis - Saya mengedit respons saya. Saya harap ini membantu.
Jim
1

Apa yang sebenarnya dilakukan MediaPlayer.play()dan MediaPlayer.stop()dilakukan? - apakah mereka pendengar acara untuk input pengguna atau apakah mereka benar-benar metode yang memulai semacam aliran media pada sistem? Jika mereka adalah pendengar untuk input pengguna, maka sangat masuk akal bagi mereka untuk tidak melakukan apa-apa (walaupun itu ide yang baik untuk setidaknya mencatatnya di suatu tempat). Namun, jika mereka memengaruhi model yang dikontrol UI Anda, maka mereka dapat melempar IllegalStateExceptionkarena pemain harus memiliki semacam isPlaying=trueatau isPlaying=falsekeadaan (tidak harus berupa bendera Boolean yang sebenarnya seperti yang ditulis di sini), dan ketika Anda menelepon MediaPlayer.stop()kapan isPlaying=false, Metode tidak dapat benar - benar "menghentikan" itu MediaPlayerkarena objek tidak dalam keadaan yang tepat untuk dihentikan - lihat deskripsijava.lang.IllegalStateException kelas:

Tanda-tanda bahwa suatu metode telah dipanggil pada waktu yang ilegal atau tidak pantas. Dengan kata lain, lingkungan Java atau aplikasi Java tidak dalam kondisi yang sesuai untuk operasi yang diminta.

Mengapa melempar pengecualian bisa bagus

Banyak orang tampaknya berpikir bahwa pengecualian pada umumnya buruk, karena mereka tidak boleh dibuang (lih. Joel tentang Perangkat Lunak ). Namun, katakan Anda memiliki MediaPlayerGUI.notifyPlayButton()panggilan mana MediaPlayer.play(), dan MediaPlayer.play()memanggil banyak kode lain dan di suatu tempat di garis itu berinteraksi dengan misalnya PulseAudio , tetapi saya (pengembang) tidak tahu di mana karena saya tidak menulis semua kode itu.

Kemudian, suatu hari saat mengerjakan kode, saya klik "main" dan tidak ada yang terjadi. Jika MediaPlayerGUIController.notifyPlayButton()mencatat sesuatu, setidaknya saya dapat melihat log dan memeriksa apakah klik tombol itu benar-benar terdaftar ... tetapi mengapa itu tidak diputar? Nah, katakanlah sebenarnya ada sesuatu yang salah dengan bungkus PulseAudio yang MediaPlayer.play()memanggil. Saya klik tombol "play" lagi, dan kali ini saya mendapat IllegalStateException:

 Exception in thread "main" java.lang.IllegalStateException: MediaPlayer already in play state.

     at org.superdupermediaplayer.MediaPlayer.play(MediaPlayer.java:16)
     at org.superdupermediaplayer.gui.MediaPlayerGUIController.notifyPlayButton(MediaPlayerGUIController.java:25)
     at org.superdupermediaplayer.gui.MediaPlayerGUI.main(MediaPlayerGUI.java:14)

Dengan melihat jejak stack itu, saya dapat mengabaikan semuanya sebelum MediaPlayer.play()saat debugging dan menghabiskan waktu saya mencari tahu mengapa misalnya tidak ada pesan yang diteruskan ke PulseAudio untuk memulai aliran audio.

Di sisi lain, jika Anda tidak melempar pengecualian, saya tidak akan memiliki apa pun untuk mulai bekerja selain: "Program bodoh tidak memainkan musik apa pun"; Meskipun tidak seburuk eksplisit kesalahan bersembunyi seperti benar-benar menelan pengecualian yang telah sebenarnya dilemparkan , Anda masih berpotensi membuang-buang waktu orang lain ketika ada sesuatu yang salah ... jadi bagus dan melemparkan sebuah pengecualian pada mereka.

errantlinguist
sumber
0

Saya lebih suka merancang API dengan cara yang membuat lebih sulit atau tidak mungkin bagi konsumen untuk membuat kesalahan. Misalnya, alih-alih memiliki MediaPlayer.play()dan MediaPlayer.stop(), Anda dapat memberikan MediaPlayer.playToggle()yang beralih antara "berhenti" dan "bermain". Dengan cara ini, metode ini selalu aman untuk dipanggil - tidak ada risiko masuk ke kondisi ilegal.

Tentu saja, ini tidak selalu mungkin atau mudah dilakukan. Contoh yang Anda berikan sebanding dengan mencoba menghapus elemen yang sudah dihapus dari daftar. Anda bisa jadi

  • pragmatis tentang hal itu dan tidak membuang kesalahan apa pun. Ini tentu menyederhanakan banyak kode, misalnya, daripada harus menulis if (list.contains(x)) { list.remove(x) }Anda hanya perlu list.remove(x)). Tapi, itu juga bisa menyembunyikan bug.
  • atau Anda bisa bertele-tele dan menandakan kesalahan bahwa daftar tidak mengandung elemen itu. Kode dapat menjadi lebih rumit di kali, karena Anda selalu harus memverifikasi jika pra-kondisi puas sebelum memanggil metode, tetapi terbalik adalah bahwa bug akhirnya lebih mudah untuk dilacak.

Jika menelepon MediaPlayer.stop()ketika sudah berhenti tidak ada salahnya dalam aplikasi Anda, maka saya akan membiarkannya berjalan diam-diam karena menyederhanakan kode dan saya punya sesuatu untuk metode idempoten. Tetapi jika Anda benar-benar yakin bahwa MediaPlayer.stop()tidak akan pernah dipanggil dalam kondisi itu, maka saya akan membuat kesalahan karena mungkin ada bug di tempat lain dalam kode dan pengecualian akan membantu melacaknya.

Pedro Rodrigues
sumber
3
Saya tidak setuju tentang playToggle()menjadi lebih mudah digunakan: Untuk mencapai efek yang diinginkan (pemain bermain atau tidak bermain), Anda akan diminta untuk mengetahui keadaan saat ini. Jadi, jika Anda mendapatkan input pengguna yang meminta Anda untuk menghentikan pemain, Anda harus terlebih dahulu menanyakan apakah pemain saat ini bermain, kemudian beralih statusnya atau tidak tergantung pada jawabannya. Itu jauh lebih rumit dari sekadar memanggil stop()metode dan menyelesaikannya.
cmaster - mengembalikan monica
Yah saya tidak pernah mengatakan itu lebih mudah digunakan - klaim saya adalah lebih aman. Plus, contoh Anda mengasumsikan bahwa pengguna akan diberikan tombol berhenti dan putar yang terpisah. Jika memang demikian maka ya, playToggleakan sangat rumit untuk digunakan. Tetapi jika pengguna juga diberi tombol sakelar, maka playToggleakan lebih mudah digunakan daripada bermain / berhenti.
Pedro Rodrigues