Saya ingin menambahkan penanganan kesalahan ke:
var firstVariable = 1;
var secondVariable = firstVariable;
Di bawah ini tidak dapat dikompilasi:
try
{
var firstVariable = 1;
}
catch {}
try
{
var secondVariable = firstVariable;
}
catch {}
Mengapa diperlukan blok uji coba untuk memengaruhi ruang lingkup variabel seperti yang dilakukan oleh blok kode lainnya? Selain dari konsistensi, bukankah masuk akal bagi kita untuk dapat membungkus kode kita dengan penanganan kesalahan tanpa perlu refactor?
c#
.net
error-handling
language-features
JᴀʏMᴇᴇ
sumber
sumber
try.. catch
adalah tipe spesifik dari blok kode, dan sejauh semua blok kode pergi, Anda tidak dapat mendeklarasikan variabel dalam satu dan menggunakan variabel yang sama di yang lain sebagai masalah cakupan.{}
tanpa mencobanya.Jawaban:
Bagaimana jika kode Anda adalah:
Sekarang Anda akan mencoba menggunakan variabel yang tidak dideklarasikan (
firstVariable
) jika pemanggilan metode Anda melempar.Catatan : Contoh di atas secara khusus menjawab pertanyaan asli, yang menyatakan "samping konsistensi-demi". Ini menunjukkan bahwa ada alasan selain konsistensi. Tetapi seperti yang ditunjukkan oleh jawaban Peter, ada juga argumen kuat dari konsistensi, yang tentunya akan menjadi faktor yang sangat penting dalam keputusan.
sumber
switch
dan mengaksesnya di orang lain.) Aturan ini dapat dengan mudah diterapkan di sini dan mencegah kode ini untuk dikompilasi. Saya pikir jawaban Peter di bawah ini lebih masuk akal.catch
blok pertama dan kemudian itu pasti akan ditetapkan ditry
blok kedua .Saya tahu bahwa ini telah dijawab dengan baik oleh Ben, tetapi saya ingin membahas konsistensi POV yang mudah disingkirkan. Dengan anggapan bahwa
try/catch
blok tidak memengaruhi cakupan, maka Anda akan berakhir dengan:Dan bagi saya ini crash langsung ke Prinsip paling tidak tercengang (POLA) karena Anda sekarang memiliki
{
dan}
melakukan tugas ganda tergantung pada konteks apa yang mendahuluinya.Satu-satunya jalan keluar dari kekacauan ini adalah dengan menunjuk beberapa penanda lain untuk menggambarkan
try/catch
blok. Yang mulai menambahkan bau kode. Jadi pada saat Anda tidak memilikitry/catch
bahasa dalam bahasa itu akan menjadi berantakan sehingga Anda akan lebih baik dengan versi cakupan.sumber
try
/catch
memblokir." - maksud Anda:try { { // scope } }
? :){}
menarik dua tugas sebagai ruang lingkup dan bukan menciptakan ruang lingkup tergantung pada konteks masih.try^ //no-scope ^
akan menjadi contoh penanda yang berbeda.Untuk menjawab ini, penting untuk melihat lebih dari sekadar lingkup variabel .
Bahkan jika variabel tetap dalam ruang lingkup, itu tidak akan ditetapkan secara pasti .
Mendeklarasikan variabel di blok coba menyatakan - ke kompiler, dan pembaca manusia - bahwa itu hanya bermakna di dalam blok itu. Berguna bagi kompiler untuk menerapkannya.
Jika Anda ingin variabel berada dalam lingkup setelah blok coba, Anda bisa mendeklarasikannya di luar blok:
Itu menyatakan bahwa variabel mungkin bermakna di luar blok try. Kompiler akan mengizinkan ini.
Tetapi ini juga menunjukkan alasan lain mengapa biasanya tidak berguna untuk menyimpan variabel dalam ruang lingkup setelah memasukkannya dalam blok percobaan. Compiler C # melakukan analisis penugasan yang pasti dan melarang membaca nilai variabel yang belum terbukti telah diberi nilai. Jadi Anda masih belum bisa membaca dari variabel.
Misalkan saya mencoba membaca dari variabel setelah blok coba:
Itu akan memberikan kesalahan waktu kompilasi :
Saya menelepon Lingkungan . Keluar di blok tangkap, jadi saya tahu variabel telah ditetapkan sebelum panggilan ke Console.WriteLine. Tetapi kompiler tidak menyimpulkan ini.
Mengapa kompilator begitu ketat?
Saya bahkan tidak bisa melakukan ini:
Salah satu cara untuk melihat batasan ini adalah dengan mengatakan analisis penugasan yang pasti dalam C # tidak terlalu canggih. Tetapi cara lain untuk melihatnya adalah bahwa, ketika Anda menulis kode dalam blok percobaan dengan klausa catch, Anda memberi tahu kompiler dan pembaca manusia bahwa itu harus diperlakukan seperti itu mungkin tidak semua dapat berjalan.
Untuk mengilustrasikan apa yang saya maksud, bayangkan jika kompiler mengizinkan kode di atas, tetapi kemudian Anda menambahkan panggilan di blok coba ke fungsi yang secara pribadi Anda tahu tidak akan memberikan pengecualian . Tidak dapat menjamin bahwa fungsi yang dipanggil tidak melempar
IOException
, kompiler tidak dapat mengetahui apa yangn
ditugaskan, dan kemudian Anda harus refactor.Ini untuk mengatakan bahwa, dengan menguraikan analisis yang sangat canggih dalam menentukan apakah suatu variabel yang ditetapkan dalam blok percobaan dengan klausa catch telah ditentukan secara pasti setelah itu, kompiler membantu Anda menghindari penulisan kode yang kemungkinan akan rusak kemudian. (Lagi pula, menangkap pengecualian biasanya berarti Anda berpikir seseorang akan dilempar.)
Anda dapat memastikan variabel ditugaskan melalui semua jalur kode.
Anda bisa membuat kompilasi kode dengan memberi nilai pada variabel sebelum blok coba, atau di blok tangkap. Dengan begitu, itu masih akan diinisialisasi atau ditugaskan, bahkan jika tugas di blok percobaan tidak terjadi. Sebagai contoh:
Atau:
Kompilasi itu. Tetapi yang terbaik adalah hanya melakukan sesuatu seperti itu jika nilai default yang Anda berikan masuk akal * dan menghasilkan perilaku yang benar.
Perhatikan bahwa, dalam kasus kedua ini di mana Anda menetapkan variabel di blok uji coba dan di semua blok tangkap, meskipun Anda dapat membaca variabel setelah uji coba, Anda masih tidak dapat membaca variabel di dalam
finally
blok yang terpasang , karena eksekusi dapat meninggalkan blok percobaan dalam lebih banyak situasi daripada yang sering kita pikirkan .* Omong-omong, beberapa bahasa, seperti C dan C ++, keduanya memungkinkan variabel yang tidak diinisialisasi dan tidak memiliki analisis tugas yang pasti untuk mencegah pembacaan dari mereka. Karena membaca memori yang tidak diinisialisasi menyebabkan program berperilaku dengan cara nondeterministik dan tidak menentu , umumnya disarankan untuk menghindari memasukkan variabel dalam bahasa-bahasa tersebut tanpa menyediakan inisialisasi. Dalam bahasa dengan analisis penugasan yang pasti seperti C # dan Java, kompiler menyelamatkan Anda dari membaca variabel yang tidak diinisialisasi dan juga dari kejahatan yang lebih kecil menginisialisasi mereka dengan nilai-nilai tidak berarti yang kemudian dapat disalahartikan sebagai bermakna.
Anda bisa membuatnya jadi jalur kode di mana variabel tidak ditugaskan melemparkan pengecualian (atau kembali).
Jika Anda berencana untuk melakukan beberapa tindakan (seperti penebangan) dan rethrow pengecualian atau melemparkan pengecualian lain, dan ini terjadi di setiap klausa menangkap di mana variabel tidak ditugaskan, maka kompiler akan tahu variabel telah ditugaskan:
Itu mengkompilasi, dan mungkin merupakan pilihan yang masuk akal. Namun, dalam aplikasi yang sebenarnya, kecuali pengecualian hanya dilemparkan dalam situasi di mana tidak masuk akal untuk mencoba memulihkan * , Anda harus memastikan bahwa Anda masih menangkap dan menangani dengan benar di suatu tempat .
(Anda tidak dapat membaca variabel di blok akhirnya dalam situasi ini juga, tapi rasanya Anda tidak bisa - setelah semua, akhirnya blok pada dasarnya selalu berjalan, dan dalam hal ini variabel tidak selalu ditugaskan .)
* Sebagai contoh, banyak aplikasi tidak memiliki klausa catch yang menangani OutOfMemoryException karena apa pun yang mereka dapat lakukan tentang hal itu mungkin paling tidak sama buruknya dengan crash .
Mungkin Anda benar - benar ingin memperbaiki kode.
Dalam contoh Anda, Anda memperkenalkan
firstVariable
dansecondVariable
mencoba blok. Seperti yang telah saya katakan, Anda dapat mendefinisikan mereka sebelum blok percobaan di mana mereka ditugaskan sehingga mereka akan tetap berada dalam ruang lingkup sesudahnya, dan Anda dapat memuaskan / mengelabui kompiler agar Anda dapat membaca dari mereka dengan memastikan mereka selalu ditugaskan.Tetapi kode yang muncul setelah blok itu mungkin tergantung pada mereka yang ditugaskan dengan benar. Jika demikian, maka kode Anda harus mencerminkan dan memastikan hal itu.
Pertama, bisakah (dan seharusnya) Anda benar-benar menangani kesalahan di sana? Salah satu alasan penanganan pengecualian ada adalah untuk membuatnya lebih mudah untuk menangani kesalahan di mana mereka dapat ditangani secara efektif , bahkan jika itu tidak dekat di mana mereka terjadi.
Jika Anda tidak dapat benar-benar menangani kesalahan dalam fungsi yang diinisialisasi dan menggunakan variabel-variabel itu, maka mungkin blok coba tidak boleh sama sekali dalam fungsi itu, tetapi sebaliknya di tempat yang lebih tinggi (yaitu, dalam kode yang memanggil fungsi itu, atau kode bahwa panggilan itu code). Pastikan Anda tidak secara tidak sengaja menangkap pengecualian yang dilemparkan ke tempat lain dan salah mengasumsikan bahwa itu dilemparkan saat inisialisasi
firstVariable
dansecondVariable
.Pendekatan lain adalah dengan meletakkan kode yang menggunakan variabel di blok try. Ini sering masuk akal. Sekali lagi, jika pengecualian yang sama yang Anda tangkap dari inisialisasi mereka juga bisa dilempar dari kode di sekitarnya, Anda harus memastikan bahwa Anda tidak mengabaikan kemungkinan itu saat menangani mereka.
(Saya berasumsi Anda menginisialisasi variabel dengan ekspresi lebih rumit daripada yang ditunjukkan dalam contoh Anda, sehingga mereka benar-benar bisa melempar pengecualian, dan juga bahwa Anda tidak benar-benar berencana untuk menangkap semua pengecualian yang mungkin , tetapi hanya untuk menangkap pengecualian spesifik apa pun Anda dapat mengantisipasi dan menangani secara berarti . Memang benar bahwa dunia nyata tidak selalu begitu baik dan kode produksi terkadang melakukan hal ini , tetapi karena tujuan Anda di sini adalah untuk menangani kesalahan yang terjadi saat menginisialisasi dua variabel tertentu, setiap klausa tangkapan yang Anda tulis untuk spesifik itu tujuan harus spesifik untuk kesalahan apa pun itu.)
Cara ketiga adalah mengekstraksi kode yang bisa gagal, dan try-catch yang menanganinya, ke dalam metodenya sendiri. Ini berguna jika Anda mungkin ingin menangani kesalahan sepenuhnya terlebih dahulu, dan kemudian tidak khawatir tentang menangkap secara tidak sengaja pengecualian yang seharusnya ditangani di tempat lain.
Misalkan, misalnya, Anda ingin segera keluar dari aplikasi setelah gagal menetapkan salah satu variabel. (Jelas tidak semua penanganan pengecualian adalah untuk kesalahan fatal; ini hanya sebuah contoh, dan mungkin atau mungkin bukan bagaimana Anda ingin aplikasi Anda bereaksi terhadap masalah tersebut.) Anda dapat melakukannya seperti ini:
Kode itu mengembalikan dan mendekonstruksi ValueTuple dengan sintaks C # 7.0 untuk mengembalikan beberapa nilai, tetapi jika Anda masih menggunakan versi C # sebelumnya, Anda masih dapat menggunakan teknik ini; misalnya, Anda dapat menggunakan parameter, atau mengembalikan objek kustom yang menyediakan kedua nilai . Selain itu, jika kedua variabel tersebut tidak benar-benar terkait erat, mungkin akan lebih baik untuk memiliki dua metode terpisah.
Terutama jika Anda memiliki beberapa metode seperti itu, Anda harus mempertimbangkan memusatkan kode Anda untuk memberi tahu pengguna tentang kesalahan fatal dan berhenti. (Misalnya, Anda bisa menulis
Die
metode denganmessage
parameter.) Thethrow new InvalidOperationException();
line pernah benar-benar dijalankan sehingga Anda tidak perlu (dan tidak harus) menulis klausa catch untuk itu.Selain berhenti ketika kesalahan tertentu terjadi, Anda mungkin kadang-kadang menulis kode yang terlihat seperti ini jika Anda membuang pengecualian dari tipe lain yang membungkus pengecualian asli . (Dalam situasi itu, Anda tidak perlu ekspresi lemparan kedua yang tidak dapat dijangkau.)
Kesimpulan: Ruang lingkup hanya bagian dari gambar.
Anda dapat mencapai efek membungkus kode Anda dengan penanganan kesalahan tanpa refactoring (atau, jika Anda suka, dengan hampir tidak ada refactoring), hanya dengan memisahkan deklarasi variabel dari tugas mereka. Kompilator memungkinkan ini jika Anda memenuhi aturan penugasan C # yang pasti, dan mendeklarasikan variabel di depan blok try menjadikan cakupannya lebih besar. Tetapi refactoring lebih lanjut mungkin masih menjadi pilihan terbaik Anda.
sumber