Saya sedang mengembangkan bahasa pemrograman baru untuk menyelesaikan beberapa persyaratan bisnis, dan bahasa ini ditargetkan untuk pengguna pemula. Jadi tidak ada dukungan untuk penanganan pengecualian dalam bahasa, dan saya tidak akan mengharapkan mereka untuk menggunakannya bahkan jika saya menambahkannya.
Saya telah mencapai titik di mana saya harus mengimplementasikan operator pembagian, dan saya bertanya-tanya bagaimana cara terbaik menangani pembagian dengan nol kesalahan?
Sepertinya saya hanya memiliki tiga cara yang mungkin untuk menangani kasus ini.
- Abaikan kesalahan dan hasilkan
0
sebagai hasilnya. Jika memungkinkan, mencatat peringatan. - Tambahkan
NaN
sebagai nilai yang mungkin untuk angka, tetapi itu menimbulkan pertanyaan tentang bagaimana menanganiNaN
nilai di bidang lain bahasa. - Hentikan pelaksanaan program dan laporkan kepada pengguna kesalahan parah terjadi.
Opsi # 1 tampaknya satu-satunya solusi yang masuk akal. Opsi # 3 tidak praktis karena bahasa ini akan digunakan untuk menjalankan logika sebagai cron malam.
Apa alternatif saya untuk menangani kesenjangan dengan nol kesalahan, dan apa risiko dengan opsi # 1.
sumber
reject "Foo"
itu diterapkan, tetapi hanya bahwa ia menolak dokumen jika mengandung kata kunciFoo
. Saya mencoba membuat bahasa menjadi mudah dibaca dengan menggunakan istilah yang biasa digunakan pengguna. Memberikan pengguna bahasa pemrograman mereka sendiri memberdayakan mereka untuk menambahkan aturan bisnis tanpa tergantung pada staf teknis.Jawaban:
Saya akan sangat menyarankan terhadap # 1, karena mengabaikan kesalahan adalah anti-pola yang berbahaya. Ini dapat menyebabkan bug yang sulit dianalisis. Mengatur hasil pembagian dengan nol ke 0 tidak masuk akal sama sekali, dan melanjutkan eksekusi program dengan nilai tidak masuk akal akan menyebabkan masalah. Apalagi saat program berjalan tanpa pengawasan. Ketika juru bahasa program mengetahui bahwa ada kesalahan dalam program (dan pembagian-by-nol hampir selalu merupakan kesalahan desain), batalkan dan simpan semuanya apa adanya biasanya lebih disukai daripada mengisi database Anda dengan sampah.
Juga, Anda tidak akan mungkin berhasil dengan tuntas mengikuti pola ini. Cepat atau lambat Anda akan mengalami situasi kesalahan yang tidak dapat diabaikan (seperti kehabisan memori atau stack overflow) dan Anda harus menerapkan cara untuk menghentikan program.
Opsi # 2 (menggunakan NaN) akan sedikit bekerja, tetapi tidak sebanyak yang Anda kira. Cara menangani NaN dalam perhitungan berbeda didokumentasikan dengan baik dalam standar IEEE 754, jadi Anda mungkin bisa melakukan apa saja yang dilakukan oleh penerjemah bahasa Anda.
Omong-omong: Membuat bahasa pemrograman yang dapat digunakan oleh non-programmer adalah sesuatu yang sudah kami coba lakukan sejak 1964 (Dartmouth BASIC). Sejauh ini, kami tidak berhasil. Tapi semoga berhasil juga.
sumber
PHP
telah memberi pengaruh buruk pada saya.NaN
dalam bahasa pemula, tetapi secara umum, jawaban yang bagus.Itu bukan ide yang bagus. Sama sekali. Orang-orang akan mulai bergantung padanya dan jika Anda pernah memperbaikinya, Anda akan merusak banyak kode.
Anda harus menangani NaN dengan cara runtimes bahasa lain melakukannya: Setiap perhitungan lebih lanjut juga menghasilkan NaN dan setiap perbandingan (bahkan NaN == NaN) menghasilkan false.
Saya pikir ini dapat diterima, tetapi tidak selalu ramah pendatang baru.
Ini adalah solusi terbaik yang saya pikir. Dengan informasi itu di tangan, pengguna harus dapat menangani 0. Anda harus menyediakan lingkungan pengujian, terutama jika itu dimaksudkan untuk dijalankan sekali malam.
Ada juga opsi keempat. Jadikan pembagian sebagai operasi ternary. Salah satu dari dua ini akan berfungsi:
sumber
NaN == NaN
menjadifalse
, maka Anda akan harus menambahkanisNaN()
fungsi sehingga pengguna dapat mendeteksiNaN
s.isNan(x) => x != x
. Namun, ketika Anda telahNaN
muncul dalam kode pemrograman Anda, Anda tidak harus mulai menambahkanisNaN
cek, tetapi melacak penyebabnya dan melakukan pemeriksaan yang diperlukan di sana. Karena itu penting untukNaN
disebarkan sepenuhnya.NaN
Ini terutama kontra-intuitif. Dalam bahasa pemula, mereka mati pada saat kedatangan.1/0
- Anda harus melakukan sesuatu dengannya. Tidak ada hasil yang bermanfaat selainInf
atauNaN
- sesuatu yang akan menyebarkan kesalahan lebih lanjut ke dalam program. Kalau tidak, satu-satunya solusi adalah berhenti dengan kesalahan pada titik ini.Hentikan aplikasi yang sedang berjalan dengan prasangka ekstrem. (Sambil memberikan informasi debug yang memadai)
Kemudian ajarkan pengguna Anda untuk mengidentifikasi dan menangani kondisi di mana pembagi mungkin nol (nilai yang dimasukkan pengguna, dll.)
sumber
Dalam Haskell (dan serupa dalam Scala), alih-alih melemparkan pengecualian (atau mengembalikan referensi nol) jenis pembungkus
Maybe
danEither
dapat digunakan. DenganMaybe
pengguna memiliki kesempatan untuk menguji apakah nilai yang ia dapatkan adalah "kosong", atau ia mungkin memberikan nilai default ketika "membuka bungkus".Either
serupa, tetapi dapat digunakan mengembalikan objek (mis. string kesalahan) yang menjelaskan masalah jika ada.sumber
error "some message"
fungsi yang sedang dievaluasi.Haskell
tidak mengizinkan ekspresi murni untuk membuang pengecualian.Jawaban lain telah mempertimbangkan manfaat relatif dari ide-ide Anda. Saya mengusulkan yang lain: gunakan analisis aliran dasar untuk menentukan apakah suatu variabel bisa nol. Kemudian Anda bisa dengan mudah melarang pembagian oleh variabel yang berpotensi nol.
Atau, miliki fungsi penegasan cerdas yang menetapkan invarian:
Ini sama baiknya dengan melempar kesalahan runtime — Anda sepenuhnya menjalankan operasi yang tidak terdefinisi — tetapi memiliki keuntungan bahwa jalur kode bahkan tidak perlu mengenai kemungkinan kegagalan untuk diekspos. Hal ini dapat dilakukan seperti halnya pemeriksaan ketik biasa, dengan mengevaluasi semua cabang program dengan lingkungan pengetikan bersarang untuk melacak dan memverifikasi invarian:
Selain itu, ia meluas secara alami ke jangkauan dan
null
memeriksa, jika bahasa Anda memiliki fitur tersebut.sumber
def foo(a,b): return a / ord(sha1(b)[0])
. Analyzer statis tidak dapat membalikkan SHA-1. Dentang memiliki jenis analisis statis dan ini bagus untuk menemukan bug yang dangkal tetapi ada banyak kasus yang tidak dapat ditangani.Nomor 1 (masukkan nol untuk debuggable) selalu buruk. Pilihan antara # 2 (menyebarkan NaN) dan # 3 (membunuh proses) tergantung pada konteks dan idealnya harus menjadi pengaturan global, seperti di Numpy.
Jika Anda melakukan satu perhitungan besar, terintegrasi, menyebarkan NaN adalah ide yang buruk karena pada akhirnya akan menyebar dan menginfeksi seluruh perhitungan Anda --- ketika Anda melihat hasilnya di pagi hari dan melihat bahwa semuanya NaN, Anda saya harus membuang hasilnya dan mulai lagi lagi. Akan lebih baik jika program dihentikan, Anda mendapat telepon di tengah malam dan memperbaikinya --- dalam hal jumlah jam yang terbuang, setidaknya.
Jika Anda melakukan banyak perhitungan kecil yang sebagian besar independen (seperti penghitungan peta atau penghitungan paralel yang memalukan), dan Anda dapat mentolerir sebagian dari persentase itu tidak dapat digunakan karena NaN, itu mungkin pilihan terbaik. Menghentikan program dan tidak melakukan 99% yang akan baik dan bermanfaat karena 1% yang cacat dan dibagi dengan nol mungkin merupakan kesalahan.
Pilihan lain, terkait dengan NaN: spesifikasi floating-point IEEE yang sama mendefinisikan Inf dan -Inf, dan ini diperbanyak secara berbeda dari NaN. Sebagai contoh, saya cukup yakin bahwa Inf> angka apa saja dan -Jika <angka apa pun, yang akan menjadi apa yang Anda inginkan jika pembagian Anda dengan nol terjadi karena nol itu seharusnya hanya angka kecil. Jika input Anda dibulatkan dan mengalami kesalahan pengukuran (seperti pengukuran fisik yang dilakukan dengan tangan), perbedaan dua jumlah besar dapat menghasilkan nol. Tanpa pembagian dengan nol, Anda akan mendapatkan sejumlah besar, dan mungkin Anda tidak peduli seberapa besar itu. Dalam hal ini, In dan -Inf adalah hasil yang valid sempurna.
Itu bisa benar secara resmi, juga --- katakan saja Anda bekerja di real diperpanjang.
sumber
Tentu saja itu praktis: Adalah tanggung jawab programmer untuk menulis sebuah program yang benar-benar masuk akal. Membagi dengan 0 tidak masuk akal. Oleh karena itu, jika programmer melakukan pembagian, itu juga merupakan tanggung jawabnya untuk memverifikasi sebelumnya bahwa pembagi tidak sama dengan 0. Jika programmer gagal melakukan pemeriksaan validasi, maka ia harus menyadari kesalahan itu segera setelah mungkin, dan hasil perhitungan dinormalisasi (NaN) atau salah (0) tidak akan membantu dalam hal itu.
Opsi 3 kebetulan yang saya akan merekomendasikan kepada Anda, btw, untuk menjadi yang paling mudah, jujur, dan benar secara matematis.
sumber
Sepertinya ide yang buruk bagi saya untuk menjalankan tugas penting (yaitu; "nightly cron") di lingkungan di mana kesalahan diabaikan. Ide yang buruk untuk menjadikan ini fitur. Ini mengesampingkan opsi 1 dan 2.
Opsi 3 adalah satu-satunya solusi yang dapat diterima. Pengecualian tidak harus menjadi bagian dari bahasa, tetapi mereka adalah bagian dari kenyataan. Pesan penghentian Anda harus sespesifik dan se informatif mungkin tentang kesalahan tersebut.
sumber
IEEE 754 sebenarnya memiliki solusi yang didefinisikan dengan baik untuk masalah Anda. Penanganan pengecualian tanpa menggunakan
exceptions
http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handlingdengan cara ini semua operasi Anda masuk akal secara matematis.
\ lim_ {x \ hingga 0} 1 / x = Inf
Menurut pendapat saya mengikuti IEEE 754 paling masuk akal karena memastikan bahwa perhitungan Anda sama benarnya dengan di komputer dan Anda juga konsisten dengan perilaku bahasa pemrograman lainnya.
Satu-satunya masalah yang muncul adalah Inf dan NaN akan mencemari hasil Anda dan pengguna Anda tidak akan tahu persis dari mana masalah itu berasal. Lihatlah bahasa seperti Julia yang melakukan ini dengan cukup baik.
Kesalahan pembagian diperbanyak dengan benar melalui operasi matematika tetapi pada akhirnya pengguna tidak perlu tahu dari mana operasi berasal dari kesalahan tersebut.
edit:
Saya tidak melihat bagian kedua dari jawaban oleh Jim Pivarski yang pada dasarnya adalah apa yang saya katakan di atas. Salahku.sumber
SQL, bahasa yang paling mudah digunakan oleh non-programmer, tidak # 3, untuk apa pun itu layak. Dalam pengalaman saya mengamati dan membantu non-programmer menulis SQL, perilaku ini umumnya dipahami dengan baik dan mudah dikompensasi (dengan pernyataan kasus atau sejenisnya). Ini membantu bahwa pesan kesalahan yang Anda peroleh cenderung menjadi sangat langsung misalnya di Postgres 9 Anda mendapatkan "GALAT: pembagian dengan nol".
sumber
Saya pikir masalahnya adalah "ditargetkan untuk pengguna pemula. -> Jadi tidak ada dukungan untuk ..."
Mengapa Anda berpikir bahwa penanganan pengecualian bermasalah bagi pengguna pemula?
Apa yang lebih buruk? Punya fitur "sulit" atau tidak tahu mengapa sesuatu terjadi? Apa yang bisa lebih membingungkan? Kecelakaan dengan dump inti atau "Fatal error: Divide by Zero"?
Sebaliknya, saya pikir JAUH lebih baik untuk membidik kesalahan pesan HEBAT. Jadi lakukan sebagai gantinya: "Perhitungan buruk, Bagi 0/0" (yaitu: Selalu tunjukkan DATA yang menyebabkan masalah, bukan hanya jenis masalah). Lihatlah bagaimana PostgreSql melakukan kesalahan pesan, itu adalah IMHO yang hebat.
Namun, Anda dapat melihat cara lain untuk bekerja dengan pengecualian seperti:
http://dlang.org/exception-safe.html
Saya juga punya mimpi membangun bahasa, dan dalam hal ini saya berpikir bahwa mencampur Mungkin / Opsional dengan Pengecualian normal bisa menjadi yang terbaik:
sumber
Dalam pandangan saya bahasa Anda harus menyediakan mekanisme umum untuk mendeteksi dan menangani kesalahan. Kesalahan pemrograman harus dideteksi pada waktu kompilasi (atau sedini mungkin) dan biasanya mengarah pada penghentian program. Kesalahan yang dihasilkan dari data yang tidak terduga atau salah, atau dari kondisi eksternal yang tidak terduga, harus dideteksi dan tersedia untuk tindakan yang sesuai, tetapi memungkinkan program untuk melanjutkan bila memungkinkan.
Tindakan yang masuk akal termasuk (a) mengakhiri (b) meminta pengguna untuk tindakan (c) mencatat kesalahan (d) mengganti nilai yang diperbaiki (e) menetapkan indikator untuk diuji dalam kode (f) meminta rutinitas penanganan kesalahan. Manakah dari ini yang Anda sediakan dan dengan cara apa saja pilihan yang harus Anda buat.
Dari pengalaman saya, kesalahan data umum seperti konversi yang salah, bagi dengan nol, luapan dan nilai di luar rentang jinak dan secara default harus ditangani dengan mengganti nilai yang berbeda dan menetapkan bendera kesalahan. (Non-programmer) yang menggunakan bahasa ini akan melihat data yang salah dan dengan cepat memahami kebutuhan untuk memeriksa kesalahan dan menanganinya.
[Sebagai contoh, pertimbangkan spreadsheet Excel. Excel tidak menghentikan spreadsheet Anda karena angka meluap atau apa pun. Sel mendapat nilai aneh dan Anda mencari tahu mengapa dan memperbaikinya.]
Jadi untuk menjawab pertanyaan Anda: Anda tentu tidak harus berhenti. Anda mungkin mengganti NaN tetapi Anda tidak harus membuatnya terlihat, pastikan saja perhitungannya selesai dan menghasilkan nilai tinggi yang aneh. Dan atur flag kesalahan sehingga pengguna yang membutuhkannya dapat menentukan bahwa kesalahan terjadi.
Pengungkapan: Saya menciptakan implementasi bahasa (Powerflex) dan mengatasi masalah ini (dan banyak lainnya) pada 1980-an. Ada sedikit atau tidak ada kemajuan pada bahasa untuk non-programmer dalam 20 tahun terakhir, dan Anda akan menarik banyak kritik karena mencoba, tetapi saya sangat berharap Anda berhasil.
sumber
Saya menyukai operator ternary di mana Anda memberikan nilai alternatif jika penyebutnya 0.
Satu lagi gagasan yang tidak saya lihat adalah menghasilkan nilai "tidak valid" umum. Umum "variabel ini tidak memiliki nilai karena program melakukan sesuatu yang buruk", yang membawa jejak tumpukan penuh dengan dirinya sendiri. Lalu, jika Anda pernah menggunakan nilai itu di mana saja, hasilnya sekali lagi tidak valid, dengan operasi baru dicoba di atas (yaitu jika nilai tidak valid pernah muncul dalam ekspresi, seluruh ekspresi menghasilkan tidak valid dan tidak ada panggilan fungsi yang dicoba; pengecualian akan menjadi operator boolean - benar atau tidak benar benar dan salah dan tidak valid salah - mungkin ada pengecualian lain untuk itu). Setelah nilai itu tidak lagi direferensikan di mana saja, Anda mencatat deskripsi panjang yang bagus dari seluruh rantai di mana segala sesuatu salah dan melanjutkan bisnis seperti biasa. Mungkin mengirim email jejak ke pimpinan proyek atau sesuatu.
Sesuatu seperti monad Maybe pada dasarnya. Ini akan bekerja dengan hal lain yang dapat gagal juga, dan Anda dapat memungkinkan orang untuk membangun cacat mereka sendiri. Dan program akan terus berjalan selama kesalahannya tidak terlalu dalam, yang sebenarnya diinginkan di sini, saya pikir.
sumber
Ada dua alasan mendasar untuk membagi dengan nol.
Untuk 1. Anda harus berkomunikasi kembali kepada pengguna bahwa mereka melakukan kesalahan karena merekalah yang bertanggung jawab dan merekalah yang paling tahu bagaimana memperbaiki situasi.
Untuk 2. Ini bukan kesalahan pengguna, Anda dapat menunjuk ke algoritme, implementasi perangkat keras, dll. Tapi ini bukan kesalahan pengguna sehingga Anda tidak boleh menghentikan program atau bahkan melempar pengecualian (jika diizinkan yang tidak dalam kasus ini). Jadi solusi yang masuk akal adalah melanjutkan operasi dengan cara yang masuk akal.
Saya dapat melihat orang yang mengajukan pertanyaan ini menanyakan kasus 1. Jadi, Anda perlu berkomunikasi kembali dengan pengguna. Menggunakan standar floating point apa pun, Inf, -Inf, Nan, IEEE tidak cocok dalam situasi ini. Strategi yang secara fundamental salah.
sumber
Tolak dalam bahasa. Dengan kata lain, larang membagi dengan angka sampai terbukti tidak nol, biasanya dengan mengujinya terlebih dahulu. Yaitu.
sumber
int
memungkinkan nilai nol, tetapi GCC masih dapat menentukan di mana dalam kode int tertentu tidak boleh nol.Saat Anda menulis bahasa pemrograman, Anda harus mengambil keuntungan dari fakta dan mewajibkan untuk memasukkan tindakan untuk rancangan dengan keadaan nol. a <= n / c: 0 div-by-zero-action
Saya tahu apa yang saya sarankan pada dasarnya adalah menambahkan 'goto' ke PL Anda.
sumber