Saya sedang mengerjakan buku "Head First Python" (ini bahasa saya untuk belajar tahun ini) dan saya sampai di bagian di mana mereka berdebat tentang dua teknik kode:
Memeriksa penanganan Pertama vs Pengecualian.
Berikut ini contoh kode Python:
# Checking First
for eachLine in open("../../data/sketch.txt"):
if eachLine.find(":") != -1:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
# Exception handling
for eachLine in open("../../data/sketch.txt"):
try:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
except:
pass
Contoh pertama berkaitan langsung dengan masalah dalam .split
fungsi. Yang kedua hanya memungkinkan penanganan handler pengecualian dengan itu (dan mengabaikan masalahnya).
Mereka berdebat dalam buku ini untuk menggunakan penanganan pengecualian alih-alih memeriksa terlebih dahulu. Argumennya adalah bahwa kode pengecualian akan menangkap semua kesalahan, di mana pengecekan pertama hanya akan menangkap hal-hal yang Anda pikirkan (dan Anda melewatkan kasus sudut). Saya telah diajari untuk memeriksanya terlebih dahulu, jadi insting awal saya adalah melakukan itu, tetapi ide mereka menarik. Saya tidak pernah berpikir untuk menggunakan penanganan pengecualian untuk menangani kasus.
Manakah dari keduanya yang secara umum dianggap praktik yang lebih baik?
sumber
if -1 == eachLine.find(":"): continue
maka sisa dari loop tidak akan di-indentasi juga.Jawaban:
Di .NET, itu adalah praktik umum untuk menghindari terlalu banyak Pengecualian. Salah satu argumen adalah kinerja: di .NET, melemparkan pengecualian adalah mahal secara komputasi.
Alasan lain untuk menghindari penggunaan berlebihan mereka adalah karena bisa sangat sulit untuk membaca kode yang terlalu mengandalkan mereka. Entri blog Joel Spolsky melakukan pekerjaan yang baik untuk menggambarkan masalah ini.
Inti dari argumen ini adalah kutipan berikut:
Secara pribadi, saya melempar pengecualian ketika kode saya tidak dapat melakukan apa yang dikontrak. Saya cenderung menggunakan try / catch ketika saya akan berurusan dengan sesuatu di luar batas proses saya, misalnya panggilan SOAP, panggilan database, file IO, atau panggilan sistem. Kalau tidak, saya mencoba kode pertahanan. Ini bukan aturan yang keras dan cepat, tetapi itu adalah praktik umum.
Scott Hanselman juga menulis tentang pengecualian di .NET di sini . Dalam artikel ini ia menjelaskan beberapa aturan praktis tentang pengecualian. Kesukaan saya?
sumber
Dalam Python khususnya, biasanya dianggap praktik yang lebih baik untuk menangkap pengecualian. Itu cenderung disebut Lebih Mudah untuk Meminta Pengampunan daripada Izin (EAFP), dibandingkan dengan Look Before You Leap (LBYL). Ada beberapa kasus di mana LBYL akan memberi Anda bug halus dalam beberapa kasus.
Namun, berhati-hatilah dengan
except:
pernyataan telanjang dan terlalu banyak kecuali pernyataan, karena keduanya bisa juga menutupi bug - sesuatu seperti ini akan lebih baik:sumber
try
/except
daripada menyiapkan persyaratan untuk "adalah kemungkinan berikut untuk melempar pengecualian", dan latihan Python pasti lebih memilih yang sebelumnya. Tidak ada salahnya bahwa pendekatan itu (mungkin) lebih efisien di sini, karena, dalam kasus di mana perpecahan berhasil, Anda hanya menjalankan string sekali. Seperti apakahsplit
harus melempar pengecualian di sini, saya akan mengatakan itu pasti harus - satu aturan umum adalah Anda harus melempar ketika Anda tidak bisa melakukan apa yang nama Anda katakan, dan Anda tidak bisa membagi pada pembatas yang hilang.Pendekatan Pragmatis
Anda harus bersikap defensif tetapi to the point. Anda harus menulis penanganan perkecualian tetapi tidak langsung. Saya akan menggunakan pemrograman web sebagai contoh karena ini adalah tempat tinggal saya.
Ini dari pengalaman bekerja dalam skenario tim besar.
Analogi
Bayangkan jika Anda mengenakan pakaian luar angkasa di ISS ALL ALL the time. Akan sulit untuk pergi ke kamar mandi atau makan, sama sekali. Akan sangat besar di dalam modul luar angkasa untuk bergerak. Itu akan payah. Menulis banyak try-catch di dalam kode Anda adalah semacam itu. Anda harus memiliki titik di mana Anda mengatakan, hei saya mengamankan ISS dan astronot saya di dalam tidak apa-apa jadi tidak praktis untuk memakai baju ruang angkasa untuk setiap skenario yang mungkin terjadi.
sumber
Argumen utama buku ini adalah bahwa versi pengecualian kode lebih baik karena akan menangkap apa pun yang mungkin Anda abaikan jika Anda mencoba menulis pengecekan kesalahan sendiri.
Saya pikir pernyataan ini benar hanya dalam keadaan yang sangat spesifik - di mana Anda tidak peduli jika hasilnya benar.
Tidak ada keraguan bahwa mengajukan pengecualian adalah praktik yang sehat dan aman. Anda harus melakukannya kapan pun Anda merasa ada sesuatu dalam kondisi saat ini dari program yang Anda (sebagai pengembang) tidak bisa, atau tidak ingin, atasi.
Namun, contoh Anda adalah tentang menangkap pengecualian. Jika Anda menangkap pengecualian, Anda tidak melindungi diri dari skenario yang mungkin Anda abaikan. Anda melakukan sebaliknya: Anda berasumsi bahwa Anda tidak mengabaikan skenario apa pun yang mungkin menyebabkan jenis pengecualian ini, dan karena itu Anda yakin bahwa tidak apa-apa untuk menangkapnya (dan dengan demikian mencegahnya menyebabkan program keluar, sebagai pengecualian tanpa tertangkap akan).
Menggunakan pendekatan pengecualian, jika Anda melihat
ValueError
pengecualian, Anda melewatkan satu baris. Menggunakan pendekatan non-pengecualian tradisional, Anda menghitung jumlah nilai yang dikembalikan darisplit
, dan jika kurang dari 2, Anda melewatkan satu baris. Jika Anda merasa lebih aman dengan pendekatan pengecualian, karena Anda mungkin telah melupakan beberapa situasi "kesalahan" lain dalam pemeriksaan kesalahan tradisional Anda, danexcept ValueError
akan menangkapnya untuk Anda?Ini tergantung pada sifat program Anda.
Jika Anda menulis, misalnya, peramban web atau pemutar video, masalah dengan input tidak boleh menyebabkannya rusak dengan pengecualian yang tidak tertangkap. Jauh lebih baik untuk mengeluarkan sesuatu yang masuk akal (bahkan jika, sebenarnya, salah) daripada berhenti.
Jika Anda menulis aplikasi di mana kebenarannya penting (seperti perangkat lunak bisnis atau rekayasa), ini akan menjadi pendekatan yang mengerikan. Jika Anda lupa tentang beberapa skenario yang muncul
ValueError
, hal terburuk yang dapat Anda lakukan adalah mengabaikan skenario yang tidak diketahui ini secara diam-diam dan lewati saja. Begitulah bug yang sangat halus dan mahal berakhir di perangkat lunak.Anda mungkin berpikir bahwa satu-satunya cara Anda dapat melihat
ValueError
dalam kode ini, adalah jikasplit
dikembalikan hanya satu nilai (bukan dua). Tetapi bagaimana jikaprint
pernyataan Anda kemudian mulai menggunakan ekspresi yang munculValueError
dalam beberapa kondisi? Ini akan menyebabkan Anda melewatkan beberapa baris bukan karena mereka ketinggalan:
, tetapi karenaprint
gagal pada mereka. Ini adalah contoh bug halus yang saya maksudkan sebelumnya - Anda tidak akan melihat apa-apa, hanya kehilangan beberapa baris.Rekomendasi saya adalah untuk menghindari menangkap (tetapi tidak menaikkan!) Pengecualian dalam kode di mana menghasilkan output yang salah lebih buruk daripada keluar. Satu-satunya waktu saya menangkap pengecualian dalam kode tersebut adalah ketika saya memiliki ekspresi yang benar-benar sepele, jadi saya dapat dengan mudah alasan apa yang menyebabkan masing-masing jenis pengecualian yang mungkin.
Mengenai dampak kinerja menggunakan pengecualian, itu sepele (dengan Python) kecuali pengecualian yang sering ditemui.
Jika Anda menggunakan pengecualian untuk menangani kondisi yang terjadi secara rutin, dalam beberapa kasus Anda mungkin harus membayar biaya kinerja yang sangat besar. Misalnya, misalkan Anda menjalankan beberapa perintah dari jarak jauh. Anda dapat memeriksa apakah teks perintah Anda melewati setidaknya validasi minimum (mis., Sintaks). Atau Anda bisa menunggu pengecualian muncul (yang terjadi hanya setelah server jauh mem-parsing perintah Anda dan menemukan masalah dengannya). Jelas, yang pertama adalah urutan besarnya lebih cepat. Contoh sederhana lain: Anda dapat memeriksa apakah angka nol ~ 10 kali lebih cepat daripada mencoba menjalankan divisi dan kemudian menangkap pengecualian ZeroDivisionError.
Pertimbangan ini hanya masalah jika Anda sering mengirim string perintah salah ke server jauh atau menerima argumen bernilai nol yang Anda gunakan untuk divisi.
Catatan: Saya berasumsi Anda akan menggunakan
except ValueError
bukan yang adilexcept
; seperti yang ditunjukkan orang lain, dan seperti yang dikatakan buku itu dalam beberapa halaman, Anda tidak boleh menggunakan telanjangexcept
.Catatan lain: pendekatan non-pengecualian yang tepat adalah menghitung jumlah nilai yang dikembalikan oleh
split
, alih-alih mencari:
. Yang terakhir ini terlalu lambat, karena mengulangi pekerjaan yang dilakukan olehsplit
dan mungkin hampir dua kali lipat waktu eksekusi.sumber
Sebagai aturan umum, jika Anda tahu pernyataan dapat menghasilkan hasil yang tidak valid, uji untuk itu dan tangani. Gunakan pengecualian untuk hal-hal yang tidak Anda harapkan; hal-hal yang "luar biasa". Itu membuat kode lebih jelas dalam arti kontrak ("tidak boleh nol" sebagai contoh).
sumber
Gunakan apa yang pernah bekerja dengan baik di ..
Baik penanganan pengecualian maupun pemrograman defensif merupakan cara berbeda untuk mengekspresikan maksud yang sama.
sumber
TBH, tidak masalah jika Anda menggunakan
try/except
mekanik atauif
cek pernyataan. Anda biasanya melihat EAFP dan LBYL di sebagian besar baseline Python, dengan EAFP yang sedikit lebih umum. Kadang-kadang EAFP adalah jauh lebih mudah dibaca / idiomatik, tetapi dalam kasus ini saya pikir itu baik-baik saja cara baik.Namun...
Saya akan berhati-hati menggunakan referensi Anda saat ini. Beberapa masalah mencolok dengan kode mereka:
with
idiom saat membaca file dengan Python: ada beberapa pengecualian. Ini bukan salah satunya.except
pernyataan telanjang yang tidak menangkap pengecualian tertentu)role, lineSpoken = eachLine.split(":",1)
Ivc memiliki jawaban yang bagus tentang ini dan EAFP, tetapi juga membocorkan deskriptor.
Versi LBYL tidak harus sama performannya dengan versi EAFP, sehingga mengatakan bahwa melemparkan pengecualian adalah "mahal dalam hal kinerja" adalah pasti salah. Ini benar-benar tergantung pada jenis string yang Anda proses:
sumber
Pada dasarnya penanganan Pengecualian seharusnya lebih sesuai untuk bahasa OOP.
Poin kedua adalah kinerja, karena Anda tidak harus mengeksekusi
eachLine.find
untuk setiap baris.sumber
Saya pikir pemrograman defensif merusak kinerja. Anda juga harus menangkap hanya pengecualian yang akan Anda tangani, biarkan runtime berurusan dengan pengecualian yang tidak Anda ketahui cara menanganinya.
sumber