Bagaimana saya harus menangani input pengguna yang tidak valid?

12

Saya telah memikirkan masalah ini untuk sementara waktu dan saya ingin tahu pendapat dari pengembang lain.

Saya cenderung memiliki gaya pemrograman yang sangat defensif. Blok atau metode khas saya terlihat seperti ini:

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

Juga, selama perhitungan, saya memeriksa semua petunjuk sebelum mendereferensi mereka. Ide saya adalah, jika ada beberapa bug dan beberapa pointer NULL akan muncul di suatu tempat, program saya harus menangani ini dengan baik dan hanya menolak untuk melanjutkan perhitungan. Tentu saja dapat memberitahukan masalah dengan pesan kesalahan di log atau mekanisme lainnya.

Singkatnya, pendekatan saya adalah

if all input is OK --> compute result
else               --> do not compute result, notify problem

Pengembang lain, di antaranya beberapa rekan saya, menggunakan strategi lain. Misalnya, mereka tidak memeriksa pointer. Mereka berasumsi bahwa sepotong kode harus diberikan input yang benar dan tidak boleh bertanggung jawab atas apa yang terjadi jika input salah. Selain itu, jika pengecualian pointer NULL merusak program, bug akan ditemukan lebih mudah selama pengujian dan memiliki lebih banyak peluang untuk diperbaiki.

Jawaban saya untuk ini biasanya: tetapi bagaimana jika bug tidak ditemukan selama pengujian dan muncul ketika produk sudah digunakan oleh pelanggan? Apa cara yang disukai bug untuk memanifestasikan dirinya? Haruskah program yang tidak melakukan tindakan tertentu, tetapi masih dapat terus bekerja, atau program yang macet dan perlu dimulai kembali?

Meringkas

Manakah dari dua pendekatan untuk menangani input yang salah yang akan Anda sarankan?

Inconsistent input --> no action + notification

atau

Inconsistent input --> undefined behaviour or crash

Edit

Terima kasih atas jawaban dan sarannya. Saya juga penggemar desain berdasarkan kontrak. Tetapi bahkan jika saya mempercayai orang yang telah menulis kode memanggil metode saya (mungkin itu sendiri), masih ada bug, yang mengarah ke input yang salah. Jadi pendekatan saya adalah jangan pernah menganggap metode dilewatkan input yang benar.

Juga, saya akan menggunakan mekanisme untuk menangkap masalah dan memberitahukannya. Pada sistem pengembangan, itu akan misalnya membuka dialog untuk memberi tahu pengguna. Dalam sistem produksi hanya akan menulis beberapa informasi ke log. Saya tidak berpikir bahwa pemeriksaan tambahan dapat menyebabkan masalah kinerja. Saya tidak yakin apakah asersi sudah cukup, jika dimatikan dalam sistem produksi: mungkin beberapa situasi akan terjadi dalam produksi yang tidak terjadi selama pengujian.

Bagaimanapun, saya benar-benar terkejut bahwa banyak orang mengikuti pendekatan yang berlawanan: mereka membiarkan aplikasi crash "on-purpose" karena mereka berpendapat bahwa ini akan membuatnya lebih mudah untuk menemukan bug selama pengujian.

Giorgio
sumber
Selalu kode pertahanan. Akhirnya, untuk alasan kinerja, Anda dapat mematikan beberapa tes dalam mode pelepasan.
deadalnix
Hari ini saya telah memperbaiki bug yang terkait dengan cek pointer NULL yang hilang. Beberapa objek dibuat saat logout aplikasi dan konstruktor menggunakan pengambil untuk mengakses beberapa objek lain yang tidak ada lagi. Objek itu tidak dimaksudkan untuk dibuat pada saat itu. Itu dibuat karena bug lain: beberapa timer tidak berhenti selama logout -> sinyal dikirim -> penerima mencoba membuat objek -> konstruktor ditanyai dan menggunakan objek lain -> NULL pointer -> crash ). Saya benar-benar tidak ingin situasi yang buruk seperti itu merusak aplikasi saya.
Giorgio
1
Aturan Perbaikan: Ketika Anda harus gagal, gagal berisik dan sesegera mungkin.
deadalnix
"Aturan Perbaikan: Ketika Anda harus gagal, gagal berisik dan sesegera mungkin.": Saya kira semua BSOD Windows adalah aplikasi dari aturan ini. :-)
Giorgio

Jawaban:

8

Anda benar. Jadilah paranoid. Jangan percaya kode lain, meskipun itu kode Anda sendiri. Anda lupa hal-hal, Anda membuat perubahan, kode berkembang. Jangan percaya kode luar.

Poin yang baik dibuat di atas: bagaimana jika input tidak valid tetapi program tidak macet? Kemudian Anda mendapatkan sampah di database dan kesalahan di telepon.

Ketika ditanya nomor (mis. Harga dalam dolar atau jumlah unit) saya ingin memasukkan "1e9" dan melihat apa yang dilakukan kode. Itu bisa terjadi.

Empat dekade lalu, mendapatkan gelar BS dalam Ilmu Komputer dari UCBerkeley, kami diberitahu bahwa program yang bagus adalah penanganan kesalahan 50%. Jadilah paranoid.

Andy Canfield
sumber
Ya, IMHO ini adalah salah satu dari sedikit situasi di mana menjadi paranoid adalah fitur dan bukan masalah.
Giorgio
"Bagaimana jika input tidak valid tetapi program tidak macet? Kemudian Anda mendapatkan sampah di database dan kesalahan di telepon.": Alih-alih crash, program bisa menolak untuk melakukan operasi dan mengembalikan hasil yang tidak ditentukan. Tidak terdefinisi akan menyebar melalui perhitungan dan tidak ada sampah yang dihasilkan. Tetapi program tidak perlu crash untuk mencapai ini.
Giorgio
Ya, tapi - maksud saya adalah program harus DETEKSI input yang tidak valid, dan mengatasinya. Jika input tidak dicentang, itu akan berhasil masuk ke sistem dan hal-hal buruk datang kemudian. Bahkan menabrak lebih baik dari itu!
Andy Canfield
Saya sepenuhnya setuju dengan Anda: metode atau fungsi khas saya dimulai dengan urutan pemeriksaan untuk memastikan bahwa data input benar.
Giorgio
Hari ini saya mendapat konfirmasi lagi bahwa strategi "periksa semuanya, jangan percayai" sering kali merupakan ide yang bagus. Seorang kolega saya memiliki pengecualian pointer NULL karena ada cek yang hilang. Ternyata dalam konteks itu benar untuk memiliki pointer NULL karena beberapa data belum dimuat, dan itu benar untuk memeriksa pointer dan tidak melakukan apa-apa ketika NULL. :-)
Giorgio
7

Anda sudah memiliki ide yang tepat

Manakah dari dua pendekatan untuk menangani input yang salah yang akan Anda sarankan?

Masukan tidak konsisten -> tidak ada tindakan + pemberitahuan

atau lebih baik

Masukan yang tidak konsisten -> tindakan yang ditangani dengan tepat

Anda tidak dapat benar-benar mengambil pendekatan cookie cutter untuk pemrograman (Anda bisa) tetapi Anda akan berakhir dengan desain formula yang melakukan hal-hal karena kebiasaan daripada dari pilihan sadar.

Temper dogmatisme dengan pragmatisme.

Steve McConnell mengatakan yang terbaik

Steve McConnell cukup banyak menulis buku ( Kode Lengkap ) tentang pemrograman defensif dan ini adalah salah satu metode yang ia sarankan agar Anda selalu memvalidasi input Anda.

Saya tidak ingat apakah Steve menyebutkan ini, namun Anda harus mempertimbangkan melakukan ini untuk metode dan fungsi non-pribadi , dan hanya orang lain yang dianggap perlu.

Justin Shield
sumber
2
Alih-alih umum, saya sarankan, semua metode non-pribadi untuk secara defensif mencakup bahasa yang telah melindungi, berbagi, atau tanpa konsep pembatasan akses (semuanya bersifat publik, secara implisit).
JustinC
3

Tidak ada jawaban "benar" di sini, terutama tanpa menentukan bahasa, jenis kode, dan jenis produk yang akan dimasukkan oleh kode tersebut. Mempertimbangkan:

  • Masalah bahasa. Di Objective-C, sering tidak apa-apa mengirim pesan ke nol; tidak ada yang terjadi, tetapi programnya juga tidak macet. Java tidak memiliki pointer eksplisit, jadi pointer nil bukan masalah besar di sana. Di C, Anda harus sedikit lebih berhati-hati.

  • Menjadi paranoid berarti tidak masuk akal, kecurigaan atau ketidakpercayaan yang tidak beralasan. Itu mungkin tidak lebih baik untuk perangkat lunak daripada untuk orang-orang.

  • Tingkat kekhawatiran Anda harus sepadan dengan tingkat risiko dalam kode dan kemungkinan sulitnya mengidentifikasi masalah yang muncul. Apa yang terjadi dalam kasus terburuk? Pengguna me-restart program dan melanjutkan di mana mereka tinggalkan? Perusahaan kehilangan jutaan dolar?

  • Anda tidak selalu dapat mengidentifikasi input yang buruk. Anda dapat membandingkan pointer Anda dengan nihil, tetapi itu hanya menangkap satu dari 2 ^ 32 nilai yang mungkin, hampir semuanya buruk.

  • Ada banyak mekanisme berbeda untuk menangani kesalahan. Sekali lagi, itu tergantung pada tingkat tertentu pada bahasa. Anda dapat menggunakan makro pernyataan, pernyataan kondisional, tes unit, penanganan pengecualian, desain yang cermat, dan teknik lainnya. Tidak satu pun dari mereka yang sangat mudah, dan tidak ada yang sesuai untuk setiap situasi.

Jadi, sebagian besar bermuara di tempat Anda ingin bertanggung jawab. Jika Anda menulis pustaka untuk digunakan oleh orang lain, Anda mungkin ingin berhati-hati dengan input yang Anda dapatkan, dan melakukan yang terbaik untuk memancarkan kesalahan yang bermanfaat jika memungkinkan. Dalam fungsi dan metode pribadi Anda sendiri, Anda dapat menggunakan konfirmasi untuk menangkap kesalahan konyol tetapi jika tidak bertanggung jawab atas penelepon (yaitu Anda) untuk tidak membuang sampah masuk.

Caleb
sumber
+1 - Jawaban yang bagus. Kekhawatiran utama saya adalah bahwa input yang salah dapat menyebabkan masalah yang muncul pada sistem produksi (ketika sudah terlambat untuk melakukan sesuatu tentang hal itu). Tentu saja, saya pikir Anda sepenuhnya benar mengatakan bahwa itu tergantung pada kerusakan yang dapat menyebabkan masalah bagi pengguna.
Giorgio
Bahasa memainkan peran besar. Di PHP, setengah kode metode Anda berakhir dengan memeriksa apa jenis variabelnya dan mengambil tindakan yang sesuai. Di Jawa, jika metode ini menerima int, Anda tidak bisa meneruskannya apa pun sehingga metode Anda menjadi lebih jelas.
bab
1

Pasti harus ada pemberitahuan, seperti pengecualian yang dilemparkan. Ini berfungsi sebagai kepala ke coders lain yang mungkin menyalahgunakan kode yang Anda tulis (mencoba menggunakannya untuk sesuatu yang tidak dimaksudkan untuk dilakukan) bahwa input mereka tidak valid atau mengakibatkan kesalahan. Ini sangat berguna dalam melacak kesalahan, sedangkan jika Anda hanya mengembalikan nol, kode mereka akan terus berlanjut sampai mereka mencoba menggunakan hasilnya dan mendapatkan pengecualian dari kode yang berbeda.

Jika kode Anda mengalami kesalahan selama panggilan ke beberapa kode lain (mungkin pembaruan basis data yang gagal) yang berada di luar cakupan kode tertentu, Anda benar-benar tidak memiliki kendali atas kode tersebut dan satu-satunya jalan Anda adalah melempar pengecualian menjelaskan apa Anda tahu (hanya apa yang diperintahkan oleh kode yang Anda panggil). Jika Anda tahu bahwa input tertentu pasti akan mengarah pada hasil seperti itu, Anda tidak bisa repot mengeksekusi kode Anda dan melemparkan pengecualian yang menyatakan input mana yang tidak valid dan mengapa.

Pada catatan terkait pengguna akhir, yang terbaik adalah mengembalikan sesuatu yang deskriptif namun sederhana sehingga siapa pun dapat memahaminya. Jika klien Anda menelepon dan mengatakan "program macet, perbaiki", Anda memiliki banyak pekerjaan untuk melacak apa yang salah dan mengapa, dan berharap Anda dapat mereproduksi masalah. Menggunakan penanganan pengecualian yang tepat tidak hanya dapat mencegah kerusakan, tetapi memberikan informasi yang berharga. Panggilan dari klien yang mengatakan "Program ini memberi saya kesalahan. Dikatakan 'XYZ bukan input yang valid untuk metode M, karena Z terlalu besar", atau hal semacam itu, bahkan jika mereka tidak tahu apa artinya, Anda tahu persis ke mana harus mencari. Selain itu, tergantung pada praktik bisnis Anda / perusahaan Anda, bahkan mungkin bukan Anda yang memperbaiki masalah ini, jadi yang terbaik adalah meninggalkannya peta yang baik.

Jadi versi singkat dari jawaban saya adalah opsi pertama Anda adalah yang terbaik.

Inconsistent input -> no action + notify caller
yoozer8
sumber
1

Saya berjuang dengan masalah yang sama saat melewati kelas universitas dalam pemrograman. Saya bersandar ke sisi paranoid dan cenderung memeriksa semuanya tetapi diberitahu bahwa ini adalah perilaku yang salah arah.

Kami diajari "Desain berdasarkan kontrak". Penekanannya adalah bahwa prasyarat, invarian dan kondisi pasca ditentukan dalam komentar dan dokumen desain. Sebagai orang yang menerapkan bagian kode saya, saya harus percaya pada arsitek perangkat lunak dan memberdayakan mereka dengan mengikuti spesifikasi yang akan mencakup prasyarat (input apa metode saya harus dapat menangani dan input apa yang saya tidak akan dikirim) . Pengecekan yang berlebihan di setiap pemanggilan metode menghasilkan bloat.

Pernyataan harus digunakan selama pembangunan iterasi untuk memverifikasi kebenaran program (validasi prasyarat, invarian, kondisi pos). Pernyataan kemudian akan berubah dalam kompilasi produksi.

Richard
sumber
0

Menggunakan "pernyataan" adalah cara untuk memberi tahu sesama pengembang bahwa mereka melakukan kesalahan, hanya dalam metode "pribadi" saja . Mengaktifkan / menonaktifkannya hanya satu bendera untuk ditambahkan / dihapus pada waktu kompilasi dan karena itu mudah untuk menghapus pernyataan dari kode produksi. Ada juga alat yang bagus untuk mengetahui apakah Anda melakukan kesalahan dengan metode Anda sendiri.

Sedangkan untuk memverifikasi parameter input dalam metode publik / terlindungi, saya lebih suka bekerja secara defensif dan memeriksa parameter dan melempar InvalidArgumentException atau sejenisnya. Itu sebabnya ada di sini untuk. Itu juga tergantung pada apakah Anda menulis API atau tidak. Jika itu adalah API, dan bahkan lebih lagi jika itu adalah sumber tertutup, lebih baik memvalidasi semuanya sehingga pengembang tahu persis apa yang salah. Kalau tidak, jika sumber tersedia untuk pengembang lain, itu bukan hitam / putih. Konsisten dengan pilihan Anda.

Sunting: hanya untuk menambahkan bahwa jika Anda melihat misalnya di Oracle JDK, Anda akan melihat bahwa mereka tidak pernah memeriksa "null" dan membiarkan kode mogok. Karena itu akan melempar NullPointerException pula, mengapa repot-repot memeriksa nol dan melempar pengecualian eksplisit. Saya kira itu masuk akal.

Jalayn
sumber
Di Jawa Anda mendapatkan pengecualian pointer nol. Dalam C ++ pointer nol crash aplikasi. Mungkin ada contoh lain: pembagian dengan nol, indeks di luar jangkauan, dan sebagainya.
Giorgio