Haruskah seseorang memeriksa setiap kesalahan kecil dalam C?

60

Sebagai seorang programmer yang baik, seseorang harus menulis kode yang kuat yang akan menangani setiap hasil dari programnya. Namun, hampir semua fungsi dari pustaka C akan mengembalikan 0 atau -1 atau NULL ketika ada kesalahan.

Terkadang jelas bahwa pengecekan kesalahan diperlukan, misalnya ketika Anda mencoba membuka file. Tetapi saya sering mengabaikan kesalahan memeriksa fungsi seperti printfatau bahkan mallockarena saya merasa tidak perlu.

if(fprintf(stderr, "%s", errMsg) < 0){
    perror("An error occurred while displaying the previous error.");
    exit(1);
}

Apakah praktik yang baik untuk mengabaikan kesalahan tertentu, atau apakah ada cara yang lebih baik untuk menangani semua kesalahan?

Derek 朕 會 功夫
sumber
13
Tergantung pada tingkat ketahanan yang dibutuhkan untuk proyek yang sedang Anda kerjakan. Sistem yang memiliki peluang menerima input dari pihak yang tidak dipercaya (misalnya server yang menghadap publik), atau beroperasi di lingkungan yang tidak sepenuhnya tepercaya, perlu diberi kode dengan sangat hati-hati, untuk menghindari kode menjadi bom waktu yang berdetak (atau tautan terlemah yang diretas) ). Jelas, hobi dan proyek-proyek pembelajaran tidak membutuhkan kekuatan seperti itu.
rwong
1
Beberapa bahasa memberikan pengecualian. Jika Anda tidak menangkap pengecualian, utas Anda akan berakhir, yang lebih baik daripada membiarkannya berlanjut dengan kondisi buruk. Jika Anda memilih untuk menangkap pengecualian, Anda dapat mencakup banyak kesalahan dalam banyak baris kode termasuk fungsi & metode yang dipanggil dengan satu trypernyataan, sehingga Anda tidak perlu memeriksa setiap panggilan atau operasi. (Perhatikan juga bahwa beberapa bahasa lebih baik daripada yang lain dalam mendeteksi kesalahan sederhana seperti null dereference atau array index di luar batas.)
Erik Eidt
1
Masalahnya sebagian besar adalah masalah metodologis: Anda tidak menulis pengecekan kesalahan biasanya ketika Anda masih mencari tahu apa yang seharusnya Anda terapkan dan Anda tidak ingin menambahkan pengecekan kesalahan sekarang karena Anda ingin mendapatkan "jalan bahagia" benar dulu Tetapi Anda masih harus memeriksa malloc dan co. Alih-alih melewatkan langkah pemeriksaan kesalahan, Anda harus memprioritaskan kegiatan pengkodean Anda dan membiarkan pemeriksaan kesalahan menjadi langkah refactoring permanen implisit dalam daftar TODO Anda, diterapkan setiap kali Anda puas dengan kode Anda. Menambahkan komentar greppable "/ * LIHAT * /" mungkin membantu.
coredump
7
Saya tidak percaya tidak ada yang disebutkan errno! Jika Anda tidak terbiasa, sementara itu benar bahwa "hampir semua fungsi dari pustaka C akan mengembalikan 0 atau −1 atau NULLketika ada kesalahan," mereka juga mengatur errnovariabel global , yang dapat Anda akses dengan menggunakan #include <errno.h>dan kemudian hanya membaca nilai dari errno. Jadi, misalnya, jika open(2) kembali -1, Anda mungkin ingin memeriksa apakah errno == EACCES, yang akan menunjukkan kesalahan izin, atau ENOENT, yang akan menunjukkan bahwa file yang diminta tidak ada.
wchargin
1
@ErikEidt C tidak mendukung try/ catch, meskipun Anda bisa mensimulasikannya dengan lompatan.
Tiang

Jawaban:

29

Secara umum, kode harus menangani kondisi luar biasa di mana pun sesuai. Ya, ini adalah pernyataan yang tidak jelas.

Dalam bahasa tingkat yang lebih tinggi dengan penanganan pengecualian perangkat lunak, ini sering dinyatakan sebagai "tangkap pengecualian dalam metode di mana Anda benar-benar dapat melakukan sesuatu tentang hal itu." Jika terjadi kesalahan file, mungkin Anda membiarkannya menggelembungkan tumpukan ke kode UI yang benar-benar dapat memberi tahu pengguna "file Anda gagal disimpan ke disk." Mekanisme pengecualian secara efektif menelan "setiap kesalahan kecil" dan secara implisit menanganinya di tempat yang tepat.

Di C, Anda tidak memiliki kemewahan itu. Ada beberapa cara untuk menangani kesalahan, beberapa di antaranya adalah fitur bahasa / perpustakaan, beberapa di antaranya adalah praktik pengkodean.

Apakah praktik yang baik untuk mengabaikan kesalahan tertentu, atau apakah ada cara yang lebih baik untuk menangani semua kesalahan?

Abaikan kesalahan tertentu? Mungkin. Sebagai contoh, masuk akal untuk berasumsi bahwa menulis ke output standar tidak akan gagal. Jika gagal, bagaimana Anda memberi tahu pengguna? Ya, itu ide yang baik untuk mengabaikan kesalahan tertentu, atau kode defensif untuk mencegahnya. Misalnya, periksa nol sebelum membaginya.

Ada beberapa cara untuk menangani semua, atau setidaknya sebagian besar kesalahan:

  1. Anda dapat menggunakan lompatan, mirip dengan foto, untuk penanganan kesalahan . Sementara masalah kontroversial di antara para profesional perangkat lunak, ada kegunaan yang valid untuk mereka terutama dalam kode yang tertanam dan kinerja-kritis (misalnya kernel Linux).
  1. Cascading ifs:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
    
  2. Uji kondisi pertama, misalnya membuka atau membuat file, lalu menganggap operasi selanjutnya berhasil.

Kode yang kuat itu baik, dan orang harus memeriksa dan menangani kesalahan. Metode mana yang terbaik untuk kode Anda tergantung pada apa yang dilakukan oleh kode tersebut, seberapa kritis kegagalannya, dll. Dan hanya Anda yang dapat menjawabnya. Namun, metode ini telah teruji pertempuran dan digunakan dalam berbagai proyek sumber terbuka di mana Anda dapat melihat bagaimana kode nyata memeriksa kesalahan.

Komunitas
sumber
21
Maaf, minor nit untuk memilih di sini - "menulis ke output standar tidak akan gagal. Jika gagal, bagaimana Anda memberitahu pengguna, sih?" - dengan menulis ke standard error? Tidak ada jaminan bahwa kegagalan untuk menulis ke salah satu menyiratkan bahwa tidak mungkin untuk menulis ke yang lain.
Damien_The_Unbeliever
4
@Damien_The_Unbeliever - terutama karena stdoutdapat diarahkan ke file, atau bahkan tidak ada tergantung pada sistem. stdoutbukan aliran "menulis ke konsol", biasanya seperti itu, tetapi tidak harus seperti itu.
Davor Ždralo
3
@ JAB Anda mengirim pesan ke stdout... jelas :-)
TripeHound
7
@ JAB: Anda mungkin keluar dengan EX_IOERR atau lebih, jika itu sesuai.
John Marshall
6
Apa pun yang Anda lakukan, jangan terus berlari seolah tidak ada yang terjadi! Jika perintah dump_database > backups/db_dump.txtgagal menulis ke output standar pada titik tertentu, saya tidak ingin itu melanjutkan dan keluar dengan sukses. (Bukan berarti database didukung seperti itu, tetapi intinya masih ada)
Ben S
15

Namun, hampir semua fungsi dari pustaka C akan mengembalikan 0 atau -1 atau NULL ketika ada kesalahan.

Ya, tetapi Anda tahu fungsi mana yang Anda panggil, bukan?

Anda sebenarnya memiliki banyak informasi yang bisa Anda masukkan ke dalam pesan kesalahan. Anda tahu fungsi mana yang dipanggil, nama fungsi yang memanggilnya, parameter apa yang dilewati, dan nilai balik dari fungsi tersebut. Itu banyak informasi untuk pesan kesalahan yang sangat informatif.

Anda tidak harus melakukan ini untuk setiap panggilan fungsi. Tetapi saat pertama kali Anda melihat pesan kesalahan "Kesalahan terjadi saat menampilkan kesalahan sebelumnya," ketika apa yang benar-benar Anda butuhkan adalah informasi yang berguna, akan menjadi yang terakhir kali Anda melihat pesan kesalahan itu di sana, karena Anda akan segera mengubah pesan kesalahan ke sesuatu yang informatif yang akan membantu Anda memecahkan masalah.

Robert Harvey
sumber
5
Jawaban ini membuat saya tersenyum, karena itu benar, tetapi tidak menjawab pertanyaan.
RubberDuck
Bagaimana itu tidak menjawab pertanyaan?
Robert Harvey
2
salah satu alasan saya berpikir bahwa C (dan bahasa lain) mendapat boolean 1 dan 0 campur aduk adalah alasan yang sama bahwa setiap fungsi yang layak mengembalikan kode kesalahan mengembalikan "0" tanpa kesalahan. tapi kemudian Anda harus mengatakannya if (!myfunc()) {/*proceed*/}. 0 bukan kesalahan, dan apa pun yang bukan nol adalah kesalahan dan setiap kesalahan memiliki kode bukan nol sendiri. cara seorang teman saya mengatakannya "hanya ada satu Kebenaran, tetapi banyak kepalsuan." "0" harus "benar" dalam if()tes dan apa pun yang bukan nol harus "salah".
robert bristow-johnson
1
tidak, lakukan dengan cara Anda, hanya ada satu kode kesalahan negatif yang mungkin. dan banyak kemungkinan kode no_error.
robert bristow-johnson
1
@ robertbristow-johnson: Lalu bacalah sebagai "Tidak ada kesalahan." if(function()) { // do something }dibaca sebagai "jika fungsi dijalankan tanpa kesalahan, maka lakukan sesuatu." atau, lebih baik lagi, "jika fungsi dijalankan dengan sukses, lakukan sesuatu." Serius, mereka yang memahami konvensi tidak bingung dengan ini. Mengembalikan false(atau 0) ketika kesalahan terjadi tidak akan memungkinkan kode kesalahan digunakan. Nol berarti false dalam C karena itulah cara kerja matematika. Lihat programmers.stackexchange.com/q/198284
Robert Harvey
11

TLDR; Anda seharusnya hampir tidak pernah mengabaikan kesalahan.

Bahasa C tidak memiliki fitur penanganan kesalahan yang baik sehingga setiap pengembang perpustakaan dapat mengimplementasikan solusi sendiri. Lebih banyak bahasa modern memiliki pengecualian di dalamnya yang membuat masalah khusus ini jauh lebih mudah untuk ditangani.

Tetapi ketika Anda terjebak dengan C Anda tidak memiliki fasilitas seperti itu. Sayangnya, Anda hanya perlu membayar harga setiap kali Anda memanggil fungsi yang kemungkinan kegagalannya kecil. Atau Anda akan menderita konsekuensi yang jauh lebih buruk seperti menimpa data dalam memori secara tidak sengaja. Jadi sebagai aturan umum Anda harus memeriksa kesalahan selalu.

Jika Anda tidak memeriksa kembalinya fprintfAnda kemungkinan besar meninggalkan bug yang dalam kasus terbaik tidak akan melakukan apa yang diharapkan pengguna dan kasus terburuk meledak semuanya selama penerbangan. Tidak ada alasan untuk melemahkan diri Anda seperti itu.

Namun sebagai pengembang C itu juga tugas Anda untuk membuat kode mudah dipelihara. Jadi kadang-kadang Anda dapat dengan aman mengabaikan kesalahan demi kejelasan jika (dan hanya jika) mereka tidak menimbulkan ancaman terhadap perilaku keseluruhan aplikasi. .

Ini masalah yang sama dengan melakukan ini:

try
{
    run();
} catch (Exception) {
    // comments expected here!!!
}

Jika Anda melihat bahwa tanpa komentar yang baik di dalam blok tangkap kosong ini tentu masalah. Tidak ada alasan untuk berpikir panggilan malloc()akan berhasil dilaksanakan 100% dari waktu.

Alex
sumber
Saya setuju perawatan adalah aspek yang sangat penting dan paragraf itu menjawab pertanyaan saya. Terima kasih atas jawaban terinci.
Derek 朕 會 功夫
Saya pikir gazillions program yang tidak pernah repot untuk memeriksa panggilan malloc mereka dan belum pernah crash adalah alasan yang bagus untuk malas ketika program desktop hanya menggunakan beberapa MB memori.
whatsisname
5
@whatsisname: Hanya karena mereka tidak crash tidak berarti mereka tidak merusak data secara diam-diam. malloc()adalah salah satu fungsi yang Anda pasti ingin memeriksa nilai kembali!
TMN
1
@ TMN: Jika malloc gagal program akan segera melakukan segfault dan berakhir, itu tidak akan melanjutkan melakukan hal-hal. Ini semua tentang risiko dan apa yang sesuai, bukan "hampir tidak pernah".
whatsisname
2
@Alex C tidak kekurangan penanganan kesalahan yang baik. Jika Anda ingin pengecualian, Anda dapat menggunakannya. Pustaka standar yang tidak menggunakan pengecualian adalah pilihan yang disengaja (pengecualian memaksa Anda untuk memiliki manajemen memori otomatis dan sering Anda tidak menginginkan itu untuk kode tingkat rendah).
martinkunev
9

Pertanyaannya sebenarnya bukan khusus bahasa, tetapi lebih khusus untuk pengguna. Pikirkan pertanyaan dari perspektif pengguna. Pengguna melakukan sesuatu, seperti mengetik nama program pada baris perintah dan menekan enter. Apa yang diharapkan pengguna? Bagaimana mereka tahu kalau ada yang salah? Apakah mereka dapat menengahi jika terjadi kesalahan?

Dalam banyak jenis kode, pemeriksaan semacam itu berlebihan. Namun, dalam kode kritis keselamatan yang keandalannya tinggi, seperti untuk reaktor nuklir, pengecekan kesalahan patologis dan jalur pemulihan yang direncanakan adalah bagian dari sifat pekerjaan sehari-hari. Itu dianggap sepadan dengan biaya untuk meluangkan waktu untuk bertanya, "Apa yang terjadi jika X gagal? Bagaimana saya kembali ke keadaan aman?" Dalam kode yang kurang andal, seperti yang untuk video game, Anda bisa lolos dengan pengecekan kesalahan yang jauh lebih sedikit.

Pendekatan lain yang serupa adalah seberapa banyak Anda benar-benar dapat memperbaiki keadaan dengan menangkap kesalahan? Saya tidak bisa menghitung jumlah program C ++ yang dengan bangga menangkap pengecualian, hanya untuk hanya memikirkan kembali mereka karena mereka tidak benar-benar tahu apa yang harus dilakukan dengan mereka ... tetapi mereka tahu mereka seharusnya melakukan penanganan pengecualian. Program-program semacam itu tidak memperoleh apa-apa dari upaya ekstra. Hanya tambahkan kode pemeriksaan kesalahan yang menurut Anda benar-benar dapat menangani situasi dengan lebih baik daripada tidak memeriksa kode kesalahan. Oleh karena itu, C ++ memiliki aturan khusus untuk menangani pengecualian yang terjadi selama penanganan pengecualian untuk menangkapnya dengan cara yang lebih bermakna (dan maksud saya, panggilan terminate () untuk memastikan tumpukan kayu yang Anda bangun untuk diri sendiri menyala dalam kemuliaan yang tepat)

Seberapa patologisnya Anda? Saya bekerja pada sebuah program yang mendefinisikan "SafetyBool" yang nilai benar dan salahnya di mana dipilih dengan cermat untuk memiliki pemerataan 1 dan 0, dan mereka dipilih sehingga salah satu dari sejumlah kegagalan perangkat keras (flip bit tunggal, bus data jejak rusak, dll.) tidak menyebabkan boolean disalahartikan. Tidak perlu dikatakan, saya tidak akan mengklaim ini sebagai praktik pemrograman tujuan umum untuk digunakan dalam program lama.

Cort Ammon
sumber
6
  • Persyaratan keselamatan yang berbeda menuntut tingkat kebenaran yang berbeda pula. Dalam perangkat lunak kontrol penerbangan atau mobil semua nilai kembali akan diperiksa, lih. MISRA.FUNC.UNUSEDRET . Dalam bukti cepat konsep yang tidak pernah meninggalkan mesin Anda, mungkin tidak.
  • Pengkodean membutuhkan waktu. Terlalu banyak pemeriksaan yang tidak relevan dalam perangkat lunak tidak aman adalah upaya yang lebih baik dilakukan di tempat lain. Tapi di mana sweet spot antara kualitas dan biaya? Itu tergantung pada alat debugging dan kompleksitas perangkat lunak.
  • Penanganan kesalahan dapat mengaburkan aliran kontrol dan memperkenalkan kesalahan baru. Saya sangat menyukai fungsi pembungkus Stevens "jaringan" Stevens yang setidaknya melaporkan kesalahan.
  • Penanganan kesalahan, jarang, bisa menjadi masalah kinerja. Tetapi sebagian besar panggilan C library akan memakan waktu sangat lama sehingga biaya untuk mengecek nilai pengembalian sangat kecil.
Peter - Pasang kembali Monica
sumber
3

Sedikit abstrak mengambil pertanyaan. Dan itu belum tentu untuk bahasa C.

Untuk program yang lebih besar Anda akan memiliki lapisan abstraksi; mungkin bagian dari mesin, perpustakaan atau dalam kerangka kerja. Lapisan yang tidak akan peduli tentang cuaca Anda mendapatkan data yang valid atau output akan beberapa nilai default: 0, -1, nulldll

Lalu ada lapisan yang akan menjadi antarmuka Anda ke lapisan abstrak, yang akan melakukan banyak penanganan kesalahan dan mungkin hal-hal lain seperti suntikan ketergantungan, acara mendengarkan dll.

Dan nanti Anda akan memiliki layer implementasi konkret Anda di mana Anda benar-benar menetapkan aturan dan menangani hasilnya.

Jadi pendapat saya tentang hal ini adalah kadang-kadang lebih baik mengecualikan penanganan kesalahan dari bagian kode karena bagian itu tidak melakukan pekerjaan itu. Dan kemudian memiliki beberapa prosesor yang akan mengevaluasi output dan menunjukkan kesalahan.

Ini sebagian besar dilakukan untuk memisahkan tanggung jawab yang mengarah pada pembacaan kode, dan skalabilitas yang lebih baik.

Sihir Kreatif
sumber
0

Secara umum, kecuali Anda memiliki alasan kuat untuk tidak memeriksa kondisi kesalahan itu, Anda harus melakukannya. Satu-satunya alasan bagus yang dapat saya pikirkan untuk tidak memeriksa kondisi kesalahan adalah ketika Anda tidak dapat melakukan sesuatu yang berarti jika gagal. Ini adalah bar yang sangat sulit untuk dijumpai, karena selalu ada satu opsi yang layak: keluar dengan anggun.

Neologisme yang Pintar
sumber
Saya biasanya tidak setuju dengan Anda karena kesalahan adalah situasi yang tidak Anda pertanggungjawabkan. Sulit untuk mengetahui bagaimana kesalahan itu dapat terjadi jika Anda tidak tahu dalam kondisi apa kesalahan itu muncul. Dan dengan demikian penanganan kesalahan sebelum pemrosesan data yang sebenarnya.
Creative Magic
Seperti, saya katakan, jika sesuatu mengembalikan atau melempar kesalahan, bahkan jika Anda tidak tahu cara memperbaiki kesalahan dan melanjutkan, Anda selalu dapat gagal dengan anggun, mencatat kesalahan, memberi tahu pengguna itu tidak berfungsi seperti yang diharapkan, dll. Intinya adalah bahwa kesalahan tidak boleh tanpa disadari, tidak dicatat, "diam-diam gagal", atau crash aplikasi. Itulah yang dimaksud "gagal dengan anggun" atau "keluar dengan anggun".
Pintar Neologisme