Ada banyak filosofi dalam berbagai disiplin ilmu rekayasa perangkat lunak tentang bagaimana perpustakaan harus mengatasi kesalahan atau kondisi luar biasa lainnya. Beberapa yang pernah saya lihat:
- Kembalikan kode kesalahan dengan hasil yang dikembalikan oleh argumen pointer. Inilah yang dilakukan PETSc.
- Kembalikan kesalahan dengan nilai sentinel. Misalnya, malloc mengembalikan NULL jika tidak dapat mengalokasikan memori,
sqrt
akan mengembalikan NaN jika Anda memasukkan angka negatif, dll. Pendekatan ini digunakan dalam banyak fungsi libc. - Melempar pengecualian. Digunakan dalam kesepakatan. II, Trilinos, dll.
- Kembalikan tipe varian; misalnya fungsi C ++ yang mengembalikan objek bertipe
Result
jika berjalan dengan benar dan menggunakan tipeError
untuk menggambarkan bagaimana ia gagal akan kembalistd::variant<Error, Result>
. - Gunakan assert dan crash. Digunakan di p4est dan beberapa bagian igraph.
Masalah dengan masing-masing pendekatan:
- Memeriksa setiap kesalahan memperkenalkan banyak kode tambahan. Nilai-nilai di mana hasil akan disimpan selalu harus dideklarasikan terlebih dahulu, memperkenalkan banyak variabel sementara yang mungkin hanya digunakan sekali. Pendekatan ini menjelaskan kesalahan apa yang terjadi tetapi sulit untuk menentukan mengapa atau, untuk tumpukan panggilan yang dalam, di mana.
- Kasing kesalahan mudah diabaikan. Selain itu, banyak fungsi bahkan tidak dapat memiliki nilai sentinel yang bermakna jika seluruh jajaran tipe output adalah hasil yang masuk akal. Banyak masalah yang sama dengan # 1.
- Hanya mungkin dalam C ++, Python, dll., Tidak dalam C atau Fortran. Dapat ditiru dalam C menggunakan setjmp / longjmp sihir atau libunwind .
- Hanya mungkin dalam C ++, Rust, OCaml, dll., Tidak dalam C atau Fortran. Dapat ditiru di C menggunakan sihir makro.
- Boleh dibilang yang paling informatif. Tetapi jika Anda mengadopsi pendekatan ini untuk, katakanlah, pustaka C yang kemudian Anda tulis pembungkus Python, kesalahan konyol seperti melewatkan indeks out-of-bounds ke array akan membuat crash interpreter Python.
Banyak saran di internet tentang penanganan kesalahan ditulis dari sudut pandang sistem operasi, pengembangan yang disematkan, atau aplikasi web. Gangguan tidak dapat diterima dan Anda harus khawatir tentang keamanan. Aplikasi ilmiah tidak memiliki masalah ini pada tingkat yang hampir sama, jika sama sekali.
Pertimbangan lain adalah jenis kesalahan apa yang dapat dipulihkan atau tidak. Gagal malloc tidak dapat dipulihkan dan, bagaimanapun, OS out-of-memory killer akan melakukannya sebelum Anda melakukannya. Indeks di luar batas untuk ukuran array juga tidak dapat dipulihkan. Bagi saya sebagai pengguna, hal terbaik yang dapat dilakukan perpustakaan adalah dengan crash dengan pesan kesalahan informatif. Di sisi lain, kegagalan, katakanlah, solver linear iteratif untuk konvergen dapat dipulihkan dari dengan menggunakan solver faktorisasi langsung.
Bagaimana seharusnya perpustakaan ilmiah melaporkan kesalahan dan mengharapkannya ditangani? Tentu saja saya menyadari bahwa itu tergantung pada bahasa apa perpustakaan diimplementasikan. Tapi sejauh yang saya tahu, untuk perpustakaan yang cukup bermanfaat, orang akan ingin menyebutnya dari beberapa bahasa selain yang diimplementasikan di dalamnya.
Sebagai tambahan, saya pikir pendekatan # 5 dapat ditingkatkan secara substansial untuk pustaka C jika ia mendefinisikan penunjuk fungsi penangan pernyataan global sebagai bagian dari API publik. Penangan pernyataan akan secara default melaporkan nomor file / baris dan macet. Binding C ++ untuk pustaka ini akan mendefinisikan penangan pernyataan baru yang sebagai gantinya melemparkan pengecualian C ++. Demikian juga, binding Python akan mendefinisikan penangan pernyataan yang menggunakan API CPython untuk melempar pengecualian Python. Tapi saya tidak tahu ada contoh yang mengambil pendekatan ini.
Jawaban:
Saya akan memberikan Anda perspektif saya, yang dikodekan dalam kesepakatan.
Pertama, ada dua jenis kondisi kesalahan: Kesalahan yang dapat dipulihkan dari, dan kesalahan yang tidak dapat dipulihkan.
Yang pertama adalah, misalnya, jika file input tidak dapat dibaca - misalnya jika Anda membaca informasi dari file seperti
$HOME/.dealii
itu mungkin atau mungkin tidak ada. Fungsi membaca hanya harus kembali ke fungsi panggilan untuk yang terakhir untuk mencari tahu apa yang harus dilakukan. Mungkin juga bahwa sumber daya tidak tersedia saat ini tetapi mungkin lagi dalam satu menit (sistem file yang dipasang dari jarak jauh).Yang terakhir adalah, misalnya, jika Anda mencoba untuk menambahkan vektor ukuran 10 ke vektor ukuran 20: Cobalah sebanyak mungkin, tidak ada yang dapat dilakukan tentang ini - ada bug dalam kode yang menyebabkan titik di mana kami berusaha melakukan penambahan.
Kedua kondisi ini harus diperlakukan berbeda, terlepas dari bahasa pemrograman yang Anda gunakan:
Dalam kasus kedua, karena tidak ada jalan lain, hentikan program. Anda bisa melakukannya dengan melemparkan pengecualian atau mengembalikan kode kesalahan yang menunjukkan kepada penelepon bahwa tidak ada yang bisa dilakukan, tetapi Anda mungkin membatalkan program segera karena itu membuat jauh lebih mudah bagi programmer untuk men-debug masalah.
Dalam kasus sebelumnya, situasi luar biasa telah muncul yang dapat ditangani. Meskipun C dan Fortran tidak memiliki cara untuk mengekspresikan hal ini, semua bahasa yang masuk akal yang kemudian datang telah memasukkan cara-cara ke dalam standar bahasa untuk menangani pengembalian "luar biasa" dengan menyediakan, yah, "pengecualian". Gunakan ini - itulah tujuan mereka; mereka juga dirancang sedemikian rupa sehingga Anda tidak bisa lupa untuk mengabaikannya (jika Anda melakukannya, pengecualian hanya menyebar satu tingkat lebih tinggi).
Dengan kata lain, apa yang saya anjurkan di sini (dan apa kesepakatannya. Saya lakukan) adalah campuran dari strategi Anda 3 dan 5, tergantung pada konteksnya. Memang benar bahwa 3 tidak berfungsi dalam bahasa seperti C atau Fortran - dalam hal ini orang mungkin berpendapat bahwa itu adalah alasan yang baik untuk tidak menggunakan bahasa yang membuatnya sulit untuk mengekspresikan apa yang ingin Anda lakukan.
Saya akan mencatat bahwa beberapa sistem tidak seharusnya crash, bahkan dalam kasus di mana kesalahan tidak dapat dipulihkan. Contohnya adalah di mana satu set fungsi dipanggil berulang kali untuk sejumlah pertanyaan - katakanlah, untuk mengevaluasi fungsi kemungkinan untuk input yang diberikan dalam skema sampling statistik. Mungkin evaluator tidak dapat menangani nilai-nilai negatif karena masalahnya tidak masuk akal dalam situasi itu (misalnya, mengevaluasi kekakuan pelat logam dengan ketebalanx ), tetapi karena evaluator perlu dipanggil berulang kali itu tidak hanya crash tetapi hanya membuang pengecualian. Dalam kasus seperti itu, meskipun memberikan nilai negatif tidak dapat dipulihkan, seseorang harus membuang pengecualian daripada membatalkan program. Saya tidak setuju dengan pendirian ini beberapa tahun yang lalu, tetapi telah berubah pikiran setelah pedoman perangkat lunak komunitas xSDK menyandikan persyaratan bahwa program tidak boleh macet (atau setidaknya harus memiliki cara untuk beralih dari crash ke pengecualian - jadi kesepakatan. A-Aku sekarang memiliki opsi untuk membuat
Assert
pengecualian lemparan alih-alih memanggilabort()
.)sumber
std::exception
, dan ini dapat ditangkap dengan referensi tanpa mengetahui jenis turunannya.