Mengembalikan nilai sihir, membuang pengecualian atau mengembalikan false pada kegagalan?

83

Saya kadang-kadang akhirnya harus menulis metode atau properti untuk perpustakaan kelas yang tidak biasa tidak memiliki jawaban nyata, tetapi gagal. Sesuatu tidak dapat ditentukan, tidak tersedia, tidak ditemukan, saat ini tidak mungkin atau tidak ada lagi data yang tersedia.

Saya pikir ada tiga solusi yang mungkin untuk situasi yang relatif tidak biasa untuk menunjukkan kegagalan dalam C # 4:

  • mengembalikan nilai ajaib yang tidak memiliki arti sebaliknya (seperti nulldan -1);
  • melempar pengecualian (misalnya KeyNotFoundException);
  • kembali falsedan berikan nilai pengembalian aktual dalam suatu outparameter, (seperti Dictionary<,>.TryGetValue).

Jadi pertanyaannya adalah: di mana situasi luar biasa yang harus saya berikan pengecualian? Dan jika saya tidak harus melempar: kapan mengembalikan nilai ajaib yang ditentukan di atas menerapkan Try*metode dengan outparameter ? (Bagi saya outparameternya tampak kotor, dan lebih sulit untuk menggunakannya dengan benar.)

Saya mencari jawaban faktual, seperti jawaban yang melibatkan pedoman desain (saya tidak tahu tentang Try*metode), kegunaan (karena saya meminta perpustakaan kelas), konsistensi dengan BCL, dan keterbacaan.


Di perpustakaan .NET Framework Base Class Library, ketiga metode digunakan:

Perhatikan bahwa seperti Hashtableyang dibuat pada saat tidak ada obat generik di C #, ia menggunakan objectdan karenanya dapat kembali nullsebagai nilai ajaib. Tetapi dengan obat generik, pengecualian digunakan Dictionary<,>, dan pada awalnya tidak ada TryGetValue. Rupanya wawasan berubah.

Jelas, Item- TryGetValuedan Parse- TryParsedualitas ada karena suatu alasan, jadi saya berasumsi bahwa melemparkan pengecualian untuk kegagalan non-strategis di C # 4 tidak dilakukan . Namun, Try*metode itu tidak selalu ada, bahkan ketika Dictionary<,>.Itemada.

Daniel AA Pelsmaeker
sumber
6
"pengecualian berarti bug". meminta kamus untuk item yang tidak ada adalah bug; meminta aliran untuk membaca data saat itu merupakan EOF yang terjadi setiap kali Anda menggunakan arus. (ini merangkum jawaban panjang yang diformat indah saya tidak mendapatkan kesempatan untuk menyerahkan :))
KutuluMike
6
Bukannya saya pikir pertanyaan Anda terlalu luas, itu bukan pertanyaan yang konstruktif. Tidak ada jawaban kanonik karena jawaban sudah ditunjukkan.
George Stocker
2
@ GeorgeStocker Jika jawabannya lurus dan jelas maka saya tidak akan bertanya. Fakta bahwa orang dapat berdebat mengapa pilihan yang diberikan lebih disukai dari sudut pandang apa pun (seperti kinerja, keterbacaan, kegunaan, pemeliharaan, pedoman desain atau konsistensi) membuatnya secara inheren non-kanonik namun dapat dijawab untuk kepuasan saya. Semua pertanyaan bisa dijawab agak subyektif. Tampaknya Anda mengharapkan sebuah pertanyaan memiliki jawaban yang hampir sama untuk pertanyaan yang baik.
Daniel AA Pelsmaeker
3
@Virtlink: George adalah moderator terpilih komunitas yang menyumbangkan banyak waktunya untuk membantu menjaga Stack Overflow. Dia menyatakan mengapa dia menutup pertanyaan, dan FAQ mendukungnya.
Eric J.
15
Pertanyaannya ada di sini, bukan karena itu subyektif, tetapi karena konseptual. Rule of thumb: jika Anda duduk di depan kode IDE Anda, tanyakan pada Stack Overflow. Jika Anda berdiri di depan brainstorming papan tulis, tanyakan di sini di Programmer.
Robert Harvey

Jawaban:

56

Saya tidak berpikir bahwa contoh Anda benar-benar setara. Ada tiga kelompok berbeda, masing-masing dengan alasannya sendiri untuk perilakunya.

  1. Nilai ajaib adalah pilihan yang baik ketika ada kondisi "sampai" seperti StreamReader.Readatau ketika ada nilai sederhana untuk digunakan yang tidak akan pernah menjadi jawaban yang valid (-1 untuk IndexOf).
  2. Melempar pengecualian ketika semantik fungsi adalah bahwa pemanggil yakin itu akan berfungsi. Dalam hal ini kunci yang tidak ada atau format ganda yang buruk benar-benar luar biasa.
  3. Gunakan parameter keluar dan kembalikan bool jika semantik ingin menyelidiki apakah operasi itu mungkin atau tidak.

Contoh yang Anda berikan sangat jelas untuk kasus 2 dan 3. Untuk nilai ajaib, dapat diperdebatkan apakah ini adalah keputusan desain yang baik atau tidak dalam semua kasus.

Yang NaNdikembalikan oleh Math.Sqrtadalah kasus khusus - mengikuti standar floating point.

Anders Abel
sumber
10
Tidak setuju dengan angka 1. Nilai-nilai ajaib tidak pernah merupakan pilihan yang baik karena mereka membutuhkan pembuat kode berikutnya untuk mengetahui pentingnya nilai sihir. Mereka juga merusak keterbacaan. Saya tidak bisa memikirkan contoh di mana penggunaan nilai sihir lebih baik daripada pola Coba.
0b101010
1
Tapi bagaimana dengan Either<TLeft, TRight>monad?
Sara
2
@ 0b101010: baru saja menghabiskan waktu mencari bagaimana streamreader.read dapat dengan aman mengembalikan -1 ...
jmoreno
33

Anda mencoba untuk berkomunikasi dengan pengguna API tentang apa yang harus mereka lakukan. Jika Anda melemparkan pengecualian, tidak ada yang memaksa mereka untuk menangkapnya, dan hanya membaca dokumentasi yang akan membuat mereka tahu apa semua kemungkinannya. Secara pribadi saya merasa lambat dan membosankan untuk menggali ke dalam dokumentasi untuk menemukan semua pengecualian bahwa metode tertentu mungkin melempar (bahkan jika itu dalam intellisense, saya masih harus menyalinnya secara manual).

Nilai ajaib masih mengharuskan Anda untuk membaca dokumentasi, dan mungkin referensi beberapa consttabel untuk memecahkan kode nilainya. Setidaknya itu tidak memiliki overhead pengecualian untuk apa yang Anda sebut kejadian yang tidak biasa.

Itulah sebabnya meskipun outkadang-kadang parameter disukai, saya lebih suka metode itu, dengan Try...sintaks. Ini kan .ical dan C # sintaks. Anda sedang berkomunikasi dengan pengguna API bahwa mereka harus memeriksa nilai kembali sebelum menggunakan hasilnya. Anda juga dapat memasukkan outparameter kedua dengan pesan kesalahan yang bermanfaat, yang lagi-lagi membantu dengan debugging. Itu sebabnya saya memilih solusi Try...dengan outparameter.

Pilihan lain adalah mengembalikan objek "hasil" khusus, meskipun menurut saya ini lebih membosankan:

interface IMyResult
{
    bool Success { get; }
    // Only access this if Success is true
    MyOtherClass Result { get; }
    // Only access this if Success is false
    string ErrorMessage { get; }
}

Kemudian fungsi Anda terlihat benar karena hanya memiliki parameter input dan hanya mengembalikan satu hal. Hanya saja satu hal yang dikembalikan adalah jenis tuple.

Bahkan, jika Anda menyukai hal-hal seperti itu, Anda dapat menggunakan Tuple<>kelas - kelas baru yang diperkenalkan di. NET 4. Secara pribadi saya tidak suka fakta bahwa makna setiap bidang kurang eksplisit karena saya tidak bisa memberikan Item1dan Item2nama yang bermanfaat.

Scott Whitlock
sumber
3
Secara pribadi, saya sering menggunakan wadah hasil seperti IMyResultpenyebab Anda memungkinkan untuk mengkomunikasikan hasil yang lebih kompleks daripada hanya trueatau falseatau nilai hasil. Try*()hanya berguna untuk hal-hal sederhana seperti string ke konversi int.
Aku sendiri
1
Pos yang bagus. Bagi saya, saya lebih suka idiom struktur Hasil yang Anda sebutkan di atas sebagai lawan pemisahan antara nilai "kembali" dan parameter "keluar". Menjaga agar tetap rapi dan rapi.
Ocean Airdrop
2
Masalah dengan parameter kedua adalah ketika Anda memiliki 50 fungsi jauh di dalam program yang kompleks, bagaimana Anda kemudian mengkomunikasikan pesan kesalahan itu kembali ke pengguna? Melempar pengecualian jauh lebih sederhana daripada memiliki lapisan kesalahan pengecekan. Saat Anda mendapatkannya, lempar saja dan tidak masalah seberapa dalam Anda.
bergulung
@rolls - Saat kita menggunakan objek output, kita mengira bahwa pemanggil langsung akan melakukan apa pun yang dia inginkan dengan output: memperlakukannya secara lokal, mengabaikannya, atau menggelembung dengan melemparkan yang dibungkus menjadi pengecualian. Ini yang terbaik dari kedua kata - penelepon dapat dengan jelas melihat semua output yang mungkin (dengan enum, dll), dapat memutuskan apa yang harus dilakukan dengan kesalahan, dan tidak perlu mencoba menangkap setiap panggilan. Jika Anda sebagian besar berharap untuk menangani hasilnya segera atau mengabaikannya, mengembalikan objek lebih mudah. Jika Anda ingin membuang semua kesalahan ke lapisan atas, pengecualian lebih mudah.
drizin
2
Ini hanya menciptakan kembali pengecualian yang diperiksa di C # dengan cara yang jauh lebih membosankan daripada pengecualian yang diperiksa di Jawa.
Musim dingin
17

Seperti yang ditunjukkan oleh contoh Anda, setiap kasing harus dievaluasi secara terpisah dan terdapat spektrum abu-abu yang cukup besar antara "keadaan luar biasa" dan "kontrol aliran", terutama jika metode Anda dimaksudkan untuk dapat digunakan kembali, dan dapat digunakan dalam pola yang sangat berbeda. daripada yang awalnya dirancang untuk. Jangan berharap kita semua di sini setuju tentang arti "tidak luar biasa", terutama jika Anda segera mendiskusikan kemungkinan menggunakan "pengecualian" untuk mengimplementasikannya.

Kami mungkin juga tidak menyetujui desain apa yang membuat kode paling mudah dibaca dan dipelihara, tetapi saya akan berasumsi bahwa perancang perpustakaan memiliki visi pribadi yang jelas tentang itu dan hanya perlu menyeimbangkannya dengan pertimbangan lain yang terlibat.

Jawaban singkat

Ikuti perasaan Anda kecuali ketika Anda merancang metode yang sangat cepat dan mengharapkan kemungkinan penggunaan kembali yang tidak terduga.

Jawaban panjang

Setiap penelepon masa depan dapat dengan bebas menerjemahkan antara kode kesalahan dan pengecualian sesuai keinginan di kedua arah; ini membuat dua pendekatan desain hampir setara kecuali untuk kinerja, keramahan debugger dan beberapa konteks interoperabilitas terbatas. Ini biasanya bermuara pada kinerja, jadi mari kita fokus pada hal itu.

  • Sebagai aturan praktis, berharap bahwa melempar pengecualian adalah 200x lebih lambat dari pengembalian reguler (pada kenyataannya, ada perbedaan yang signifikan dalam hal itu).

  • Sebagai aturan praktis lainnya, melempar pengecualian mungkin sering memungkinkan kode yang lebih bersih dibandingkan dengan nilai sihir yang paling kasar, karena Anda tidak bergantung pada pemrogram yang menerjemahkan kode kesalahan ke dalam kode kesalahan lain, karena ia berjalan melalui beberapa lapis kode klien menuju suatu titik di mana ada konteks yang cukup untuk menanganinya dengan cara yang konsisten dan memadai. (Kasus khusus: nullcenderung lebih baik di sini daripada nilai sihir lainnya karena kecenderungannya untuk secara otomatis menerjemahkan dirinya ke NullReferenceExceptiondalam kasus beberapa, tetapi tidak semua jenis cacat; biasanya, tetapi tidak selalu, cukup dekat dengan sumber cacat. )

Jadi apa pelajarannya?

Untuk fungsi yang dipanggil hanya beberapa kali selama masa aplikasi (seperti inisialisasi aplikasi), gunakan apa pun yang memberi Anda kode yang lebih bersih dan mudah dipahami. Kinerja tidak bisa menjadi perhatian.

Untuk fungsi membuang, gunakan apa pun yang memberi Anda kode bersih. Kemudian lakukan beberapa profil (jika diperlukan sama sekali) dan ubah pengecualian untuk mengembalikan kode jika mereka termasuk di antara kemacetan teratas yang dicurigai berdasarkan pada pengukuran atau keseluruhan struktur program.

Untuk fungsi yang dapat digunakan kembali yang mahal, gunakan apa pun yang memberi Anda kode bersih. Jika pada dasarnya Anda selalu harus menjalani pulang-pergi jaringan atau mem-parsing file XML pada disk, biaya overhead untuk melempar pengecualian mungkin dapat diabaikan. Lebih penting untuk tidak kehilangan detail kegagalan apa pun, bahkan tidak sengaja, daripada kembali dari "kegagalan luar biasa" ekstra cepat.

Fungsi lean yang dapat digunakan kembali membutuhkan lebih banyak pemikiran. Dengan menggunakan pengecualian, Anda memaksa sesuatu seperti 100 kali melambat pada penelepon yang akan melihat pengecualian pada setengah dari (banyak) panggilan mereka, jika tubuh fungsi dijalankan dengan sangat cepat. Pengecualian masih merupakan opsi desain, tetapi Anda harus memberikan alternatif overhead rendah untuk penelepon yang tidak mampu membayar ini. Mari kita lihat sebuah contoh.

Anda mencantumkan contoh yang bagus Dictionary<,>.Item, yang, secara longgar, berubah dari mengembalikan nullnilai menjadi melempar KeyNotFoundExceptionantara. NET 1.1 dan .NET 2.0 (hanya jika Anda mau dianggap Hashtable.Itemsebagai pelopor non-generik yang praktis). Alasan "perubahan" ini bukan tanpa minat di sini. Optimalisasi kinerja tipe nilai (tidak ada lagi tinju) menjadikan nilai sulap asli ( null) bukan pilihan; outparameter hanya akan membawa sebagian kecil dari biaya kinerja kembali. Pertimbangan kinerja yang terakhir ini benar-benar dapat diabaikan dibandingkan dengan overhead dari melempar a KeyNotFoundException, tetapi desain pengecualian masih lebih unggul di sini. Mengapa?

  • parameter ref / out dikenakan biaya setiap kali, tidak hanya dalam kasus "kegagalan"
  • Siapa pun yang peduli dapat menelepon Containssebelum panggilan apa pun ke pengindeks, dan pola ini dibaca secara alami. Jika pengembang ingin tetapi lupa menelepon Contains, tidak ada masalah kinerja yang dapat merambah; KeyNotFoundExceptioncukup keras untuk diperhatikan dan diperbaiki.
Jirka Hanika
sumber
Saya pikir 200x optimis tentang kinerja pengecualian ... lihat blogs.msdn.com/b/cbrumme/archive/2003/10/01/51524.aspx bagian "Kinerja dan Tren" sebelum komentar.
gbjbaanb
@ gbjbaanb - Yah, mungkin. Artikel itu menggunakan tingkat kegagalan 1% untuk membahas topik yang tidak sama sekali berbeda. Pikiran saya sendiri dan pengukuran yang samar-samar diingat akan berasal dari konteks C ++ yang diterapkan tabel (lihat Bagian 5.4.1.2 dari laporan ini , di mana satu masalah adalah bahwa pengecualian pertama dari jenisnya kemungkinan dimulai dengan kesalahan halaman (drastis dan variabel). tapi diamortisasi satu kali overhead) .Tetapi saya akan melakukan dan memposting percobaan dengan .NET 4 dan mungkin mengubah nilai rata-rata ini. Saya sudah menekankan variansnya.
Jirka Hanika
Apakah biaya untuk parameter ref / out setinggi itu? Bagaimana? Dan menelepon Containssebelum panggilan ke pengindeks mungkin menyebabkan kondisi balapan yang tidak harus hadir TryGetValue.
Daniel AA Pelsmaeker
@ gbjbaanb - Sudah selesai percobaan. Saya malas dan menggunakan mono di Linux. Pengecualian memberi saya ~ 563000 lemparan dalam 3 detik. Pengembalian memberi saya ~ 10900000 pengembalian dalam 3 detik. Itu 1:20, bahkan 1: 200. Saya masih merekomendasikan berpikir 1: 100+ untuk kode yang lebih realistis. (di luar varian, seperti yang saya perkirakan, memiliki biaya yang dapat diabaikan - saya sebenarnya curiga bahwa jitter mungkin telah mengoptimalkan panggilan secara lengkap dalam contoh minimalis saya jika tidak ada pengecualian yang dilemparkan, terlepas dari tanda tangannya.)
Jirka Hanika
@Virtlink - Menyetujui keamanan utas secara umum, tetapi mengingat Anda merujuk .NET 4 secara khusus, gunakan ConcurrentDictionaryuntuk akses multi-utas dan Dictionaryuntuk akses utas tunggal untuk kinerja optimal. Artinya, tidak menggunakan `` Berisi` TIDAK membuat thread kode aman dengan Dictionarykelas khusus ini .
Jirka Hanika
10

Apa hal terbaik untuk dilakukan dalam situasi yang relatif tidak biasa untuk menunjukkan kegagalan, dan mengapa?

Anda seharusnya tidak membiarkan kegagalan.

Saya tahu, tangan-bergelombang dan idealis, tetapi dengarkan saya. Saat melakukan desain, ada beberapa kasus di mana Anda memiliki kesempatan untuk memilih versi yang tidak memiliki mode kegagalan. Alih-alih memiliki 'FindAll' yang gagal, LINQ menggunakan klausa mana yang hanya mengembalikan enumerable kosong. Alih-alih memiliki objek yang perlu diinisialisasi sebelum digunakan, biarkan konstruktor menginisialisasi objek (atau menginisialisasi ketika yang tidak diinisialisasi terdeteksi). Kuncinya adalah menghapus cabang kegagalan dalam kode konsumen. Ini masalahnya, jadi fokuslah.

Strategi lain untuk ini adalah KeyNotFoundskenario. Di hampir setiap basis kode yang saya kerjakan sejak 3.0, sesuatu seperti metode ekstensi ini ada:

public static class DictionaryExtensions {
    public static V GetValue<K, V>(this IDictionary<K, V> arg, K key, Func<K,V> ifNotFound) {
        if (!arg.ContainsKey(key)) {
            return ifNotFound(key);
        }

        return arg[key];
    }
}

Tidak ada mode kegagalan nyata untuk ini. ConcurrentDictionarymemiliki GetOrAddbuilt in yang serupa .

Semua yang dikatakan, akan selalu ada saat di mana itu tidak dapat dihindari. Ketiganya memiliki tempat masing-masing, tetapi saya akan memilih opsi pertama. Terlepas dari semua yang dibuat dari bahaya nol, itu terkenal dan cocok dengan banyak 'item tidak ditemukan' atau 'hasil tidak berlaku' skenario yang membentuk set "tidak luar biasa kegagalan". Terutama ketika Anda membuat tipe nilai yang dapat dibatalkan, arti penting dari 'ini mungkin gagal' sangat eksplisit dalam kode dan sulit untuk dilupakan / dikacaukan.

Opsi kedua cukup baik ketika pengguna Anda melakukan sesuatu yang bodoh. Memberi Anda string dengan format yang salah, mencoba mengatur tanggal ke 42 Desember ... sesuatu yang Anda ingin hal-hal meledak dengan cepat dan spektakuler selama pengujian sehingga kode buruk diidentifikasi dan diperbaiki.

Pilihan terakhir adalah yang saya semakin tidak suka. Parameter keluar canggung, dan cenderung melanggar beberapa praktik terbaik saat membuat metode, seperti berfokus pada satu hal dan tidak memiliki efek samping. Plus, param luar biasanya hanya bermakna selama kesuksesan. Yang mengatakan, mereka sangat penting untuk operasi tertentu yang biasanya dibatasi oleh masalah konkurensi, atau pertimbangan kinerja (di mana Anda tidak ingin melakukan perjalanan kedua ke DB misalnya).

Jika nilai kembali dan param tidak non-sepele, maka saran Scott Whitlock tentang objek hasil lebih disukai (seperti Matchkelas Regex ).

Telastyn
sumber
7
Pet peeve here: outparameter benar-benar ortogonal terhadap masalah efek samping. Mengubah refparameter adalah efek samping, dan memodifikasi keadaan objek yang Anda lewati melalui parameter input adalah efek samping, tetapi outparameter hanya cara canggung untuk membuat fungsi mengembalikan lebih dari satu nilai. Tidak ada efek samping, hanya beberapa nilai balik.
Scott Whitlock
Saya bilang cenderung , karena bagaimana orang menggunakannya. Seperti yang Anda katakan, mereka hanyalah beberapa nilai pengembalian.
Telastyn
Tetapi jika Anda tidak menyukai parameter dan menggunakan pengecualian untuk meledakkan secara spektakuler ketika formatnya salah ... bagaimana Anda menangani kasus di mana format Anda adalah input pengguna? Kemudian salah satu pengguna dapat meledakkan segalanya, atau seseorang dikenakan penalti kinerja melempar dan kemudian menangkap pengecualian. Baik?
Daniel AA Pelsmaeker
@virtlink dengan memiliki metode validasi yang berbeda. Anda perlu lagian untuk memberikan pesan yang tepat untuk UI sebelum mereka kirimkan.
Telastyn
1
Ada pola yang sah untuk parameter keluar, dan itu adalah fungsi yang memiliki kelebihan yang menghasilkan tipe yang berbeda. Resolusi kelebihan tidak akan berfungsi untuk jenis kembali, tetapi itu akan keluar parameter.
Robert Harvey
2

Selalu lebih suka melempar pengecualian. Itu punya antarmuka yang seragam di antara semua fungsi yang bisa gagal, dan ini menunjukkan kegagalan yang berisik mungkin - properti yang sangat diinginkan.

Perhatikan itu Parsedan TryParsetidak benar-benar hal yang sama selain dari mode kegagalan. Fakta yang TryParsejuga bisa mengembalikan nilainya agak ortogonal, sungguh. Pertimbangkan situasi di mana, misalnya, Anda memvalidasi beberapa input. Anda sebenarnya tidak peduli berapa nilainya, asalkan nilainya. Dan tidak ada yang salah dengan menawarkan semacam IsValidFor(arguments)fungsi. Tapi itu tidak pernah bisa menjadi utama modus operasi.

DeadMG
sumber
4
Jika Anda berurusan dengan sejumlah besar panggilan ke suatu metode, pengecualian dapat memiliki efek negatif yang sangat besar pada kinerja. Pengecualian harus dicadangkan untuk kondisi luar biasa, dan akan sangat diterima untuk memvalidasi input formulir, tetapi tidak untuk parsing angka dari file besar.
Robert Harvey
1
Itu kebutuhan yang lebih spesialis, bukan kasus umum.
DeadMG
2
Jadi katamu. Tapi Anda memang menggunakan kata "selalu." :)
Robert Harvey
@DeadMG, setuju dengan RobertHarvey meskipun saya pikir jawabannya terlalu ditolak, jika dimodifikasi untuk mencerminkan "sebagian besar waktu" dan kemudian menunjukkan pengecualian (tidak ada kata pun dimaksudkan) untuk kasus umum ketika datang ke sering digunakan kinerja tinggi panggilan untuk mempertimbangkan opsi lain.
Gerald Davis
Pengecualian tidak mahal. Menangkap pengecualian yang terlempar sangat mahal karena sistem harus melepaskan tumpukan ke titik kritis terdekat. Tapi itu "mahal" relatif kecil dan tidak boleh ditakuti bahkan dalam loop bersarang ketat.
Matthew Whited
2

Seperti yang telah dicatat orang lain, nilai ajaib (termasuk nilai pengembalian boolean) bukanlah solusi yang hebat, kecuali sebagai penanda "end-of-range". Alasan: Semantik tidak eksplisit, bahkan jika Anda memeriksa metode objek. Anda harus benar-benar membaca dokumentasi lengkap untuk seluruh objek hingga "oh yeah, jika mengembalikan -42 itu berarti bla bla bla".

Solusi ini dapat digunakan untuk alasan historis atau untuk alasan kinerja, tetapi sebaliknya harus dihindari.

Ini meninggalkan dua kasus umum: Probing atau pengecualian.

Di sini aturan praktisnya adalah bahwa program tidak boleh bereaksi terhadap pengecualian kecuali untuk menangani ketika program / tidak sengaja / melanggar beberapa kondisi. Probing harus digunakan untuk memastikan bahwa ini tidak terjadi. Oleh karena itu, pengecualian berarti bahwa penyelidikan yang relevan tidak dilakukan sebelumnya, atau bahwa sesuatu yang sama sekali tidak terduga telah terjadi.

Contoh:

Anda ingin membuat file dari jalur yang diberikan.

Anda harus menggunakan objek File untuk mengevaluasi terlebih dahulu apakah jalur ini sah untuk pembuatan atau penulisan file.

Jika program Anda entah bagaimana masih berakhir dengan mencoba menulis ke jalur yang ilegal atau tidak dapat ditulis, Anda harus mendapatkan kutipan. Ini bisa terjadi karena kondisi balapan (beberapa pengguna lain menghapus direktori, atau membuatnya hanya-baca, setelah Anda bermasalah)

Tugas menangani kegagalan yang tidak terduga (ditandai dengan pengecualian) dan memeriksa apakah kondisinya tepat untuk operasi sebelumnya (menyelidiki) biasanya akan disusun secara berbeda, dan karenanya harus menggunakan mekanisme yang berbeda.

Anders Johansen
sumber
0

Saya pikir Trypolanya adalah pilihan terbaik, ketika kode hanya menunjukkan apa yang terjadi. Saya benci param dan suka objek nullable. Saya telah membuat kelas berikut

public sealed class Bag<TValue>
{
    public Bag(TValue value, bool hasValue = true)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Bag<TValue> Empty
    {
        get { return new Bag<TValue>(default(TValue), false); }
    }

    public bool HasValue { get; private set; }
    public TValue Value { get; private set; }
}

jadi saya bisa menulis kode berikut

    public static Bag<XElement> GetXElement(this XElement element, string elementName)
    {
        try
        {
            XElement result = element.Element(elementName);
            return result == null
                       ? Bag<XElement>.Empty
                       : new Bag<XElement>(result);
        }
        catch (Exception)
        {
            return Bag<XElement>.Empty;
        }
    }

Sepertinya dapat dibatalkan tetapi tidak hanya untuk tipe nilai

Contoh lain

    public static Bag<string> TryParseString(this XElement element, string attributeName)
    {
        Bag<string> attributeResult = GetString(element, attributeName);
        if (attributeResult.HasValue)
        {
            return new Bag<string>(attributeResult.Value);
        }
        return Bag<string>.Empty;
    }

    private static Bag<string> GetString(XElement element, string attributeName)
    {
        try
        {
            string result = element.GetAttribute(attributeName).Value;
            return new Bag<string>(result);
        }
        catch (Exception)
        {
            return Bag<string>.Empty;
        }
    }
GSerjo
sumber
3
try catchakan mendatangkan malapetaka pada kinerja Anda jika Anda menelepon GetXElement()dan gagal berkali-kali.
Robert Harvey
kadang itu tidak masalah. Lihatlah panggilan Tas. Terima kasih atas pengamatan Anda
clas Bag <T> Anda hampir identik dengan System.Nullable <T> alias "nullable object"
aeroson
ya, hampir public struct Nullable<T> where T : structperbedaan utama dalam kendala. btw versi terbaru ada di sini github.com/Nelibur/Nelibur/blob/master/Source/Nelibur.Sword/…
GSerjo
0

Jika Anda tertarik dengan rute "nilai ajaib", cara lain untuk menyelesaikannya adalah dengan membebani tujuan kelas Lazy. Meskipun Malas dimaksudkan untuk menunda instantiasi, tidak ada yang benar-benar mencegah Anda menggunakan seperti Maybe atau Option. Misalnya:

    public static Lazy<TValue> GetValue<TValue, TKey>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key)
    {
        TValue retVal;
        if (dictionary.TryGetValue(key, out retVal))
        {
            var retValRef = retVal;
            var lazy = new Lazy<TValue>(() => retValRef);
            retVal = lazy.Value;
            return lazy;
        }

        return new Lazy<TValue>(() => default(TValue));
    }
zumalifeguard
sumber