Kapan IllegalArgumentException harus dilemparkan?

98

Saya khawatir ini adalah pengecualian waktu proses sehingga mungkin harus digunakan dengan hemat.
Kasus penggunaan standar:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Tapi sepertinya itu akan memaksa desain berikut:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

Untuk mengembalikannya menjadi pengecualian yang dicentang.

Oke, tapi mari kita lakukan itu. Jika Anda memberikan input yang buruk, Anda akan mendapatkan error runtime. Jadi pertama-tama, itu sebenarnya kebijakan yang cukup sulit untuk diterapkan secara seragam, karena Anda dapat melakukan konversi yang sangat berlawanan:

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

Dan lebih buruk - saat memeriksa 0 <= pct && pct <= 100kode klien dapat diharapkan untuk dilakukan secara statis, ini tidak berlaku untuk data yang lebih canggih seperti alamat email, atau lebih buruk, sesuatu yang harus diperiksa terhadap database, oleh karena itu secara umum kode klien tidak dapat digunakan sebelumnya. mengesahkan.

Jadi pada dasarnya apa yang saya katakan adalah saya tidak melihat kebijakan konsisten yang berarti untuk digunakan IllegalArgumentException. Tampaknya itu tidak boleh digunakan dan kami harus tetap berpegang pada pengecualian kami sendiri. Apa kasus penggunaan yang baik untuk membuang ini?

djechlin.dll
sumber

Jawaban:

80

Dokumen API untuk IllegalArgumentException:

Dilempar untuk menunjukkan bahwa metode telah melewati argumen ilegal atau tidak sesuai.

Dari melihat bagaimana ini digunakan di perpustakaan JDK , saya akan mengatakan:

  • Sepertinya tindakan defensif untuk mengeluh tentang masukan yang jelas buruk sebelum masukan dapat digunakan dan menyebabkan sesuatu gagal di tengah jalan dengan pesan kesalahan yang tidak masuk akal.

  • Ini digunakan untuk kasus-kasus di mana akan terlalu mengganggu untuk menampilkan pengecualian yang dicentang (meskipun itu muncul dalam kode java.lang.reflect, di mana kekhawatiran tentang tingkat konyol dari pemeriksaan-pengecualian-lemparan tidak terlihat).

Saya akan menggunakan IllegalArgumentExceptionuntuk melakukan pemeriksaan argumen defensif parit terakhir untuk utilitas umum (mencoba untuk tetap konsisten dengan penggunaan JDK). Atau di mana harapannya adalah bahwa argumen yang buruk adalah kesalahan programmer, mirip dengan file NullPointerException. Saya tidak akan menggunakannya untuk mengimplementasikan validasi dalam kode bisnis. Saya pasti tidak akan menggunakannya untuk contoh email.

Nathan Hughes
sumber
8
Saya pikir saran "di mana harapannya adalah bahwa argumen yang buruk adalah kesalahan programmer" paling konsisten dengan bagaimana saya melihat ini digunakan, jadi terima jawaban ini.
djechlin
22

Ketika berbicara tentang "masukan yang buruk", Anda harus mempertimbangkan dari mana masukan itu berasal.

Apakah input yang dimasukkan oleh pengguna atau sistem eksternal lain yang tidak Anda kontrol, Anda harus mengharapkan input tersebut tidak valid, dan selalu memvalidasinya. Dalam kasus ini, tidak masalah untuk menampilkan pengecualian yang dicentang. Aplikasi Anda harus 'pulih' dari pengecualian ini dengan memberikan pesan kesalahan kepada pengguna.

Jika masukan berasal dari sistem Anda sendiri, misalnya database Anda, atau beberapa bagian lain dari aplikasi Anda, Anda harus dapat mengandalkannya agar valid (seharusnya sudah divalidasi sebelum sampai di sana). Dalam kasus ini, tidak masalah untuk menampilkan pengecualian yang tidak dicentang seperti IllegalArgumentException, yang tidak boleh ditangkap (secara umum Anda tidak boleh menangkap pengecualian yang tidak dicentang). Ini adalah kesalahan programmer bahwa nilai yang tidak valid sampai di sana sejak awal;) Anda harus memperbaikinya.

Tom
sumber
2
Mengapa "Anda tidak boleh menangkap pengecualian yang tidak dicentang"?
Koray Tugay
9
Karena pengecualian yang tidak dicentang dimaksudkan untuk dilemparkan sebagai akibat dari kesalahan pemrograman. Pemanggil metode yang melontarkan pengecualian seperti itu tidak dapat diharapkan untuk pulih darinya, dan oleh karena itu biasanya tidak masuk akal untuk menangkapnya.
Tom
1
Because an unchecked exception is meant to be thrown as a result of a programming errormembantu saya menjernihkan banyak hal di kepala saya, terima kasih :)
svarog
14

Melempar pengecualian waktu proses "dengan hemat" sebenarnya bukan kebijakan yang baik - Java yang efektif merekomendasikan agar Anda menggunakan pengecualian yang dicentang saat pemanggil dapat diharapkan pulih secara wajar . (Kesalahan programmer adalah contoh khusus: jika kasus tertentu menunjukkan kesalahan programmer, maka Anda harus memberikan pengecualian yang tidak dicentang; Anda ingin programmer memiliki jejak tumpukan di mana masalah logika terjadi, bukan mencoba menanganinya sendiri.)

Jika tidak ada harapan untuk pulih, silakan gunakan pengecualian yang tidak dicentang; tidak ada gunanya menangkap mereka, jadi tidak apa-apa.

Tidak 100% jelas dari contoh Anda kasus mana contoh ini ada dalam kode Anda.

Louis Wasserman
sumber
Saya pikir "cukup diharapkan untuk pulih" itu buruk. Operasi apa pun foo(data)bisa saja terjadi sebagai bagian for(Data data : list) foo(data);di mana pemanggil ingin sebanyak mungkin berhasil meskipun beberapa data salah format. Termasuk kesalahan program juga, jika aplikasi saya gagal berarti transaksi tidak akan berjalan, itu mungkin lebih baik, jika itu berarti pendinginan nuklir menjadi offline itu buruk.
djechlin
StackOverflowErrordan kasus-kasus di mana penelepon tidak dapat diharapkan pulih secara wajar. Tapi sepertinya kasus level logika data atau aplikasi harus diperiksa. Itu berarti lakukan pemeriksaan penunjuk nol Anda!
djechlin
4
Dalam aplikasi pendingin nuklir, saya lebih suka gagal keras dalam pengujian daripada membiarkan kasus yang menurut pemrogram tidak mungkin terlewat tanpa disadari.
Louis Wasserman
Boolean.parseBoolean (..), melontarkan IllegalArugmentException meskipun "pemanggil cukup diharapkan untuk pulih". jadi ..... terserah kode Anda untuk menanganinya atau gagal kembali ke pemanggil.
Jeryl Cook
5

Sebagaimana ditentukan dalam tutorial resmi oracle, dinyatakan bahwa:

Jika klien cukup diharapkan untuk pulih dari pengecualian, jadikan itu pengecualian yang dicentang. Jika klien tidak dapat melakukan apa pun untuk memulihkan dari pengecualian, jadikan sebagai pengecualian yang tidak dicentang.

Jika saya memiliki Aplikasi yang berinteraksi dengan database menggunakan JDBC, Dan saya memiliki metode yang mengambil argumen sebagai int itemdan double price. Untuk priceitem yang sesuai dibaca dari tabel database. Saya hanya mengalikan jumlah total yang itemdibeli dengan pricenilainya dan mengembalikan hasilnya. Meskipun saya selalu yakin di akhir saya (Aplikasi akhir) bahwa nilai bidang harga di tabel tidak akan pernah negatif. Tetapi bagaimana jika nilai harga keluar negatif ? Ini menunjukkan bahwa ada masalah serius dengan sisi database. Mungkin salah memasukkan harga oleh operator. Ini adalah jenis masalah yang tidak dapat diantisipasi dan tidak dapat dipulihkan oleh bagian lain dari aplikasi yang memanggil metode itu. Ini ada BUGdi database Anda. Jadi, danIllegalArguementException()harus dilemparkan dalam kasus ini yang akan menyatakan itu the price can't be negative.
Saya harap saya telah mengungkapkan maksud saya dengan jelas ..

Vishal K
sumber
Saya tidak suka nasihat (oracle) ini karena penanganan pengecualian adalah tentang bagaimana memulihkan, bukan apakah memulihkan. Misalnya satu permintaan pengguna yang cacat tidak layak membuat seluruh server web rusak.
djechlin
5

API apa pun harus memeriksa validitas setiap parameter metode publik apa pun sebelum menjalankannya:

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

Mereka mewakili 99.9% dari kali kesalahan dalam aplikasi karena meminta operasi yang tidak mungkin sehingga pada akhirnya mereka adalah bug yang seharusnya merusak aplikasi (jadi ini adalah kesalahan yang tidak dapat dipulihkan).

Dalam kasus ini dan mengikuti pendekatan cepat gagal, Anda harus membiarkan aplikasi selesai untuk menghindari kerusakan status aplikasi.

Ignacio Soler Garcia
sumber
Sebaliknya, jika klien API memberi saya masukan yang buruk, saya seharusnya tidak merusak seluruh server API saya.
djechlin
2
Tentu saja, itu seharusnya tidak membuat server API Anda crash tetapi mengembalikan pengecualian ke pemanggil Itu seharusnya tidak membuat crash apa pun kecuali klien.
Ignacio Soler Garcia
Apa yang Anda tulis di komentar bukanlah apa yang Anda tulis di jawaban.
djechlin
1
Izinkan saya menjelaskan, jika panggilan ke API dengan parameter yang salah (bug) dibuat oleh klien pihak ketiga maka klien harus macet. Jika server API-lah yang memiliki bug memanggil metode dengan parameter yang salah, maka server API akan mogok. Periksa: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia
1

Perlakukan IllegalArgumentExceptionsebagai pemeriksaan prasyarat , dan pertimbangkan prinsip desain: Sebuah metode publik harus mengetahui dan secara publik mendokumentasikan prasyaratnya sendiri.

Saya setuju contoh ini benar:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Jika EmailUtil tidak tembus pandang , artinya ada beberapa alasan prasyarat tidak dapat dijelaskan kepada pengguna akhir, maka pengecualian yang dicentang benar. Versi kedua, dikoreksi untuk desain ini:

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

Jika EmailUtil transparan , misalnya mungkin itu adalah metode privat yang dimiliki oleh kelas di bawah pertanyaan, IllegalArgumentExceptionbenar jika dan hanya jika prasyaratnya dapat dijelaskan dalam dokumentasi fungsi. Ini adalah versi yang benar juga:

/** @param String email An email with an address in the form [email protected]
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

Desain ini bisa berjalan baik.

  • Jika prasyarat mahal untuk dijelaskan, atau jika kelas dimaksudkan untuk digunakan oleh klien yang tidak tahu apakah email mereka valid, maka gunakan ParseException. Metode tingkat atas di sini dinamai scanEmailyang mengisyaratkan pengguna akhir bermaksud untuk mengirim email yang belum dipelajari sehingga kemungkinan ini benar.
  • Jika prasyarat dapat dijelaskan dalam dokumentasi fungsi, dan kelas tidak bermaksud untuk input yang tidak valid dan oleh karena itu kesalahan programmer diindikasikan, gunakan IllegalArgumentException. Meskipun tidak "dicentang", "centang" berpindah ke Javadoc yang mendokumentasikan fungsi, yang diharapkan dipatuhi oleh klien. IllegalArgumentExceptiondi mana klien tidak dapat mengatakan argumen mereka sebelumnya ilegal adalah salah.

Catatan tentang IllegalStateException : Ini berarti "status internal objek ini (variabel instance pribadi) tidak dapat melakukan tindakan ini." Pengguna akhir tidak dapat melihat status privat dengan begitu bebas sehingga lebih diutamakan IllegalArgumentExceptionjika panggilan klien tidak memiliki cara untuk mengetahui status objek tidak konsisten. Saya tidak memiliki penjelasan yang baik ketika lebih disukai daripada pengecualian yang dicentang, meskipun hal-hal seperti menginisialisasi dua kali, atau kehilangan koneksi database yang tidak dipulihkan, adalah contohnya.

djechlin.dll
sumber