Bagaimana saya merancang metode TryParse yang menyediakan informasi terperinci jika terjadi kesalahan penguraian?

9

Saat mem-parsing input pengguna, umumnya disarankan untuk tidak melempar dan menangkap pengecualian melainkan menggunakan metode validasi. Di .NET BCL, ini akan menjadi perbedaan antara, misalnya, int.Parse(melempar pengecualian pada data tidak valid) dan int.TryParse(mengembalikan falsedata tidak valid).

Saya merancang sendiri

Foo.TryParse(string s, out Foo result)

metode dan saya tidak yakin tentang nilai pengembalian. Saya bisa menggunakan metode boolseperti .NET sendiri TryParse, tetapi itu tidak akan memberikan indikasi tentang jenis kesalahan, tentang alasan yang tepat mengapa s tidak dapat diuraikan menjadi Foo. (Misalnya, sbisa memiliki tanda kurung yang tidak cocok, atau jumlah karakter yang salah, atau Bartanpa yang sesuai Baz, dll.)

Sebagai pengguna API, saya sangat tidak suka metode yang hanya mengembalikan Boolean sukses / gagal tanpa memberi tahu saya mengapa operasi gagal. Ini menjadikan debugging permainan tebak-tebakan, dan saya juga tidak ingin memaksakan itu pada klien perpustakaan saya.

Saya dapat memikirkan banyak solusi untuk masalah ini (mengembalikan kode status, mengembalikan string kesalahan, menambahkan string kesalahan sebagai parameter keluar), tetapi mereka semua memiliki kelemahan masing-masing, dan saya juga ingin tetap konsisten dengan konvensi Framework .NET .

Jadi, pertanyaan saya adalah sebagai berikut:

Apakah ada metode dalam .NET Framework yang (a) mengurai input tanpa melempar pengecualian dan (b) masih mengembalikan informasi kesalahan yang lebih terperinci daripada Boolean benar / salah sederhana?

Heinzi
sumber
1
Tautan itu tidak menyimpulkan bahwa tidak disarankan untuk melempar dan menangkap pengecualian. Ada kalanya cara terbaik adalah menggunakan Parse().
paparazzo

Jawaban:

5

Saya akan merekomendasikan menggunakan pola monad untuk tipe pengembalian Anda.

ParseResult<Foo> foo = FooParser.Parse("input");

Perhatikan juga, seharusnya bukan tanggung jawab Foo untuk mencari tahu bagaimana seharusnya diuraikan dari input pengguna karena ini secara langsung mengikat lapisan domain Anda ke lapisan UI Anda, dan juga melanggar prinsip tanggung jawab tunggal.

Anda juga bisa membuat kelas hasil parse khusus untuk Foodaripada menggunakan generik, tergantung pada kasus penggunaan Anda.

Kelas hasil parse spesifik foo mungkin terlihat seperti ini:

class FooParseResult
{
     Foo Value { get; set; }
     bool PassedRequirement1 { get; set; }
     bool PassedRequirement2 { get; set; }
}

Ini adalah versi Monad:

class ParseResult<T>
{
     T Value { get; set; }
     string ParseErrorMessage { get; set; }
     bool WasSuccessful { get; set; }
}

Saya tidak mengetahui adanya metode dalam kerangka kerja .net yang mengembalikan informasi kesalahan parse terperinci.

TheCatWhisperer
sumber
Saya mengerti komentar Anda tentang pengikatan lapisan UI, tetapi dalam hal ini ada representasi string standar, kanonik Foo, jadi masuk akal untuk memiliki Foo.ToStringdan Foo.Parse.
Heinzi
Dan, coba pertanyaan saya yang berani, dapatkah Anda memberi saya contoh dari .NET BCL yang menggunakan pola ini?
Heinzi
4
Bagaimana itu monad?
JacquesB
@Heinzi: Metode apa pun yang mengembalikan a Func<T>akan memenuhi kriteria itu, jika Anda memasukkan Tinformasi yang Anda butuhkan. Mengembalikan informasi kesalahan terperinci sebagian besar terserah Anda. Sudahkah Anda mempertimbangkan untuk menggunakan Maybe<T>? Lihat mikhail.io/2016/01/monads-explained-in-csharp
Robert Harvey
@ JacquesB: Saya agak bertanya-tanya hal yang sama. Metode signature kompatibel dengan perilaku modan, tetapi hanya itu saja.
Robert Harvey
1

Anda bisa melihat ModelState dalam kerangka kerja MVC. Ini mewakili percobaan beberapa input dan mungkin memiliki kumpulan kesalahan.

Yang mengatakan, saya tidak berpikir ada pola berulang untuk ini di. SM BCL, karena pengecualian adalah - untuk lebih baik atau lebih buruk - pola yang ditetapkan untuk melaporkan kondisi kesalahan dalam .net. Saya pikir Anda harus melanjutkan dan mengimplementasikan solusi Anda sendiri yang sesuai dengan masalah Anda, misalnya ParseResultkelas dengan dua subclass, SuccessfulParsedan FailedParse, di mana SuccessfulParsememiliki properti dengan nilai parsed dan FailedParsememiliki properti pesan kesalahan. Menggabungkan ini dengan pencocokan pola di C # 7 bisa sangat elegan.

JacquesB
sumber
1

Saya mengalami masalah serupa dengan keinginan untuk menggunakan TryParse/Convert/etc.metode di mana saya kadang - kadang perlu tahu bagaimana dan mengapa itu gagal.

Saya akhirnya mengambil inspirasi dari bagaimana beberapa serializers menangani kesalahan dan menggunakan acara. Dengan cara ini sintaks untuk TryX(..., out T)metode saya terlihat bersih seperti yang lain dan andal mengembalikan sederhana falseseperti yang tersirat pola.

Namun, ketika saya ingin memerlukan detail lebih lanjut, saya hanya menambahkan Event Handler dan mendapatkan hasil apa pun yang saya butuhkan dalam paket yang kompleks atau sederhana seperti yang saya inginkan (di MyEventArgsbawah). Tambahkan ke daftar string, tambahkan ExceptionDispatchInfodan tangkap Pengecualian; biarkan penelepon memutuskan jika dan bagaimana ia ingin berurusan dengan apa pun yang salah.

public class Program
{
    public static void Main()
    {
        var c = new MyConverter();

        //here's where I'm subscibing to errors that occur
        c.Error += (sender, args) => Console.WriteLine(args.Details);

        c.TryCast<int>("5", out int i);
    }
}

//here's our converter class
public class MyConverter
{
    //invoke this event whenever something goes wrong and fill out your EventArgs with details
    public event EventHandler<MyEventArgs> Error;

    //intentionally stupid implementation
    public bool TryCast<T>(object input, out T output)
    {
        bool success = true;
        output = default (T);

        //try-catch here because it's an easy way to demonstrate my example
        try
        {
            output = (T)input;
        }
        catch (Exception ex)
        {
            success = false;
            Error?.Invoke(this, new MyEventArgs{Details = ex.ToString()});
        }

        return success;
    }
}

//stores whatever information you want to make available
public class MyEventArgs : EventArgs
{
    public string Details {get; set;}
}
Chakrava
sumber