Bagaimana seharusnya kesalahan dilaporkan di perpustakaan ilmiah?

11

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:

  1. Kembalikan kode kesalahan dengan hasil yang dikembalikan oleh argumen pointer. Inilah yang dilakukan PETSc.
  2. Kembalikan kesalahan dengan nilai sentinel. Misalnya, malloc mengembalikan NULL jika tidak dapat mengalokasikan memori, sqrtakan mengembalikan NaN jika Anda memasukkan angka negatif, dll. Pendekatan ini digunakan dalam banyak fungsi libc.
  3. Melempar pengecualian. Digunakan dalam kesepakatan. II, Trilinos, dll.
  4. Kembalikan tipe varian; misalnya fungsi C ++ yang mengembalikan objek bertipe Resultjika berjalan dengan benar dan menggunakan tipe Erroruntuk menggambarkan bagaimana ia gagal akan kembali std::variant<Error, Result>.
  5. Gunakan assert dan crash. Digunakan di p4est dan beberapa bagian igraph.

Masalah dengan masing-masing pendekatan:

  1. 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.
  2. 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.
  3. Hanya mungkin dalam C ++, Python, dll., Tidak dalam C atau Fortran. Dapat ditiru dalam C menggunakan setjmp / longjmp sihir atau libunwind .
  4. Hanya mungkin dalam C ++, Rust, OCaml, dll., Tidak dalam C atau Fortran. Dapat ditiru di C menggunakan sihir makro.
  5. 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.

Daniel Shapero
sumber
Pertimbangan lain adalah konsekuensi kinerja. Bagaimana berbagai metode ini mempengaruhi kecepatan perangkat lunak? Haruskah kita menggunakan penanganan kesalahan yang berbeda di bagian "kontrol" kode (mis. Memproses file input) versus "mesin" yang mahal secara komputasi?
LedHead
Perhatikan bahwa jawaban terbaik akan berbeda menurut bahasa.
chrylis -on strike-

Jawaban:

10

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/.dealiiitu 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 Assertpengecualian lemparan alih-alih memanggil abort().)

Wolfgang Bangerth
sumber
Saya hanya akan merekomendasikan yang sebaliknya: melemparkan pengecualian ketika situasinya tidak dapat ditangani dan mengembalikan kode kesalahan saat itu dapat ditangani. Masalahnya adalah bahwa berurusan dengan pengecualian yang dilemparkan itu rumit: pemrogram aplikasi harus mengetahui jenis semua kemungkinan pengecualian untuk menangkap dan menangani mereka, jika tidak maka program hanya akan crash. Menabrak tidak masalah dan bahkan diterima untuk situasi yang tidak dapat ditangani, karena titik mogok dilaporkan out-of-the-box dengan python, misalnya, tetapi untuk situasi yang dapat ditangani, itu (kebanyakan) tidak diterima.
cdalitz
@cdalitz: Ini adalah cacat desain dari C ++ yang bisa Anda lempar objek apa pun. Tetapi setiap perangkat lunak yang masuk akal (dikecualikan Trilinos) hanya melempar pengecualian yang berasal dari std::exception, dan ini dapat ditangkap dengan referensi tanpa mengetahui jenis turunannya.
Wolfgang Bangerth
1
Tetapi saya sangat tidak setuju dengan mengembalikan kode kesalahan karena alasan yang diuraikan dalam pertanyaan asli: (i) Kode kesalahan diabaikan terlalu sering, dan akibatnya kesalahan tidak ditangani sama sekali; (ii) dalam banyak kasus, sama sekali tidak ada nilai luar biasa yang dapat dikembalikan secara wajar mengingat jenis pengembalian fungsi tetap; (iii) fungsi memiliki tipe pengembalian yang berbeda, dan Anda harus menentukan dalam setiap kasus secara terpisah apa nilai "luar biasa" yang mewakili kesalahan.
Wolfgang Bangerth
WB menulis (maaf, trik '@' tidak berfungsi karena beberapa alasan dan nama pengguna dihapus oleh StackExchage karena beberapa alasan): "Kode kesalahan diabaikan terlalu sering". Ini berlaku bahkan lebih untuk menangkap menangkap: tidak banyak pengembang perangkat lunak mengambil kesulitan mengurung setiap panggilan fungsi dalam blok try / catch. Tapi itu sebagian besar masalah selera: selama dokumentasi dengan jelas menyatakan apakah dan pengecualian yang dilempar suatu fungsi, saya bisa mengatasinya. Tetapi sekali lagi dapat dikatakan: tugas menulis dokumentasi diabaikan terlalu sering ;-)
cdalitz
Tetapi intinya adalah bahwa jika Anda lupa untuk menangkap pengecualian, maka tidak ada masalah hilir: Program hanya dibatalkan. Akan mudah untuk menemukan di mana masalahnya terjadi. Jika Anda lupa memeriksa kode kesalahan, program Anda mungkin macet di beberapa titik kemudian karena keadaan internal yang tidak ditentukan - tetapi di mana masalah asli tetap sepenuhnya tidak jelas. Sangat sulit menemukan bug semacam ini.
Wolfgang Bangerth