Anda harus menggunakan keduanya. Masalahnya adalah memutuskan kapan akan menggunakan masing-masing .
Ada beberapa skenario di mana pengecualian adalah pilihan yang jelas :
Dalam beberapa situasi Anda tidak dapat melakukan apa pun dengan kode kesalahan , dan Anda hanya perlu menanganinya di tingkat atas dalam tumpukan panggilan , biasanya hanya mencatat kesalahan, menampilkan sesuatu kepada pengguna atau menutup program. Dalam kasus ini, kode kesalahan akan meminta Anda untuk menggelembung kode kesalahan secara manual tingkat demi tingkat yang jelas lebih mudah dilakukan dengan pengecualian. Intinya adalah ini untuk situasi yang tidak terduga dan tidak dapat ditangani .
Namun tentang situasi 1 (di mana terjadi sesuatu yang tidak terduga dan tidak dapat ditangani, Anda tidak ingin mencatatnya), pengecualian dapat berguna karena Anda dapat menambahkan informasi kontekstual . Misalnya jika saya mendapatkan SqlException di pembantu data tingkat rendah saya, saya ingin menangkap kesalahan itu di tingkat rendah (di mana saya tahu perintah SQL yang menyebabkan kesalahan) sehingga saya dapat menangkap informasi itu dan menelusuri kembali dengan informasi tambahan . Harap perhatikan kata ajaib di sini: lempar ulang, dan jangan menelan .
Aturan pertama penanganan pengecualian: jangan menelan pengecualian . Juga, perhatikan bahwa tangkapan dalam saya tidak perlu mencatat apa pun karena tangkapan luar akan memiliki seluruh jejak tumpukan dan dapat mencatatnya.
Dalam beberapa situasi Anda memiliki urutan perintah, dan jika ada yang gagal Anda harus membersihkan / membuang sumber daya (*), apakah ini adalah situasi yang tidak dapat dipulihkan (yang harus dilempar) atau situasi yang dapat dipulihkan (dalam hal ini Anda dapat menangani secara lokal atau dalam kode penelepon tetapi Anda tidak memerlukan pengecualian). Jelas jauh lebih mudah untuk meletakkan semua perintah itu dalam sekali percobaan, daripada menguji kode kesalahan setelah setiap metode, dan membersihkan / membuang di blok terakhir. Harap dicatat bahwa jika Anda ingin kesalahan meluap (yang mungkin Anda inginkan), Anda bahkan tidak perlu menangkapnya - Anda cukup menggunakan akhirnya untuk pembersihan / pembuangan - Anda hanya boleh menggunakan tangkap / retrow jika Anda mau untuk menambahkan informasi kontekstual (lihat poin 2).
Salah satu contohnya adalah urutan pernyataan SQL di dalam blok transaksi. Sekali lagi, ini juga merupakan situasi yang "tidak dapat ditangani", bahkan jika Anda memutuskan untuk mengetahuinya lebih awal (perlakukan secara lokal alih-alih membesar-besarkan) itu masih merupakan situasi yang fatal di mana hasil terbaik adalah membatalkan semuanya atau setidaknya menggugurkan bagian dari proses.
(*) Ini seperti on error goto
yang kami gunakan di Visual Basic lama
Dalam konstruktor, Anda hanya bisa melempar pengecualian.
Karena itu, dalam semua situasi lain di mana Anda mengembalikan beberapa informasi yang penelepon DAPAT / HARUS mengambil tindakan , menggunakan kode pengembalian mungkin merupakan alternatif yang lebih baik. Ini mencakup semua "error" yang diharapkan , karena mungkin error tersebut harus ditangani oleh pemanggil langsung, dan hampir tidak perlu dinaikkan terlalu banyak level di stack.
Tentu saja selalu mungkin untuk memperlakukan kesalahan yang diharapkan sebagai pengecualian, dan menangkap kemudian segera satu tingkat di atas, dan itu juga mungkin untuk mencakup setiap baris kode dalam coba tangkap dan mengambil tindakan untuk setiap kesalahan yang mungkin terjadi. IMO, ini adalah desain yang buruk, tidak hanya karena jauh lebih bertele-tele, tetapi khususnya karena kemungkinan pengecualian yang mungkin dilemparkan tidak jelas tanpa membaca kode sumber - dan pengecualian dapat dilemparkan dari metode dalam apa pun, menciptakan gotos yang tidak terlihat . Mereka merusak struktur kode dengan membuat beberapa titik keluar tak terlihat yang membuat kode sulit dibaca dan diperiksa. Dengan kata lain, Anda tidak boleh menggunakan pengecualian sebagai kontrol aliran, karena akan sulit bagi orang lain untuk memahami dan memeliharanya. Bahkan bisa menjadi sulit untuk memahami semua aliran kode yang mungkin untuk pengujian.
Sekali lagi: untuk pembersihan / pembuangan yang benar, Anda dapat menggunakan try-akhirnya tanpa menangkap apa pun .
Kritik paling populer tentang kode pengembalian adalah bahwa "seseorang dapat mengabaikan kode kesalahan, tetapi dalam arti yang sama seseorang juga dapat menelan pengecualian. Penanganan pengecualian yang buruk mudah dilakukan di kedua metode. Tetapi menulis program berbasis kode kesalahan yang baik masih jauh lebih mudah daripada menulis program berbasis pengecualian . Dan jika seseorang dengan alasan apa pun memutuskan untuk mengabaikan semua kesalahan (yang lama on error resume next
), Anda dapat dengan mudah melakukannya dengan kode yang dikembalikan dan Anda tidak dapat melakukannya tanpa banyak uji coba tangkap.
Kritik paling populer kedua tentang kode pengembalian adalah bahwa "sulit untuk meluap" - tetapi itu karena orang tidak memahami bahwa pengecualian adalah untuk situasi yang tidak dapat dipulihkan, sedangkan kode kesalahan tidak.
Memutuskan antara pengecualian dan kode kesalahan adalah area abu-abu. Bahkan mungkin saja Anda perlu mendapatkan kode kesalahan dari beberapa metode bisnis yang dapat digunakan kembali, dan kemudian Anda memutuskan untuk menggabungkannya menjadi pengecualian (mungkin menambahkan informasi) dan membiarkannya menggelembung. Tapi itu adalah kesalahan desain untuk berasumsi bahwa SEMUA kesalahan harus dilemparkan sebagai pengecualian.
Singkatnya:
Saya suka menggunakan pengecualian ketika saya mengalami situasi yang tidak terduga, di mana tidak banyak yang harus dilakukan, dan biasanya kami ingin membatalkan blok kode yang besar atau bahkan seluruh operasi atau program. Ini seperti yang lama "pada kesalahan goto".
Saya suka menggunakan kode pengembalian ketika saya mengharapkan situasi di mana kode penelepon dapat / harus mengambil tindakan. Ini mencakup sebagian besar metode bisnis, API, validasi, dan sebagainya.
Perbedaan antara kode pengecualian dan kesalahan ini adalah salah satu prinsip desain bahasa GO, yang menggunakan "panik" untuk situasi tak terduga yang fatal, sementara situasi biasa yang diharapkan dikembalikan sebagai kesalahan.
Namun tentang GO, ini juga memungkinkan beberapa nilai kembali , yang merupakan sesuatu yang sangat membantu dalam menggunakan kode pengembalian, karena Anda dapat secara bersamaan mengembalikan kesalahan dan hal lain. Di C # / Java kita dapat mencapainya tanpa parameter, Tuple, atau Generik (favorit saya), yang dikombinasikan dengan enum dapat memberikan kode kesalahan yang jelas kepada pemanggil:
public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
....
return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");
...
return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}
var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
// do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
order = result.Entity; // etc...
Jika saya menambahkan kemungkinan pengembalian baru dalam metode saya, saya bahkan dapat memeriksa semua pemanggil jika mereka mencakup nilai baru itu dalam pernyataan sakelar misalnya. Anda benar-benar tidak dapat melakukannya dengan pengecualian. Saat Anda menggunakan kode pengembalian, Anda biasanya akan mengetahui terlebih dahulu semua kemungkinan kesalahan, dan mengujinya. Dengan pengecualian, Anda biasanya tidak tahu apa yang mungkin terjadi. Membungkus enum di dalam pengecualian (bukan Generik) adalah alternatif (selama jelas jenis pengecualian yang akan dilemparkan setiap metode), tetapi IMO itu masih desain yang buruk.