Jak zaprojektować metodę TryParse, która zapewnia szczegółowe informacje w przypadku błędu analizy?

9

Podczas analizowania danych wejściowych od użytkownika ogólnie zaleca się, aby nie zgłaszać wyjątków i wychwytywać wyjątki, ale raczej stosować metody sprawdzania poprawności. W .NET BCL byłaby to różnica między, na przykład int.Parse(zgłasza wyjątek w przypadku nieprawidłowych danych) i int.TryParse(zwraca falsew przypadku nieprawidłowych danych).

Projektuję własne

Foo.TryParse(string s, out Foo result)

i nie jestem pewien co do wartości zwracanej. Mógłbym użyć boolwłasnej TryParsemetody .NET , ale nie dałoby to żadnego wskazania na temat rodzaju błędu, ani o dokładny powód, dla s którego nie można go przeanalizować jako Foo. (Na przykład smoże mieć niedopasowany nawias, niewłaściwą liczbę znaków lub znak „ Barbez” Bazitp.)

Jako użytkownik interfejsów API zdecydowanie nie lubię metod, które zwracają tylko logiczne powodzenie / niepowodzenie, nie mówiąc mi, dlaczego operacja się nie powiodła. To sprawia, że ​​debugowanie jest zgadywaniem i nie chcę narzucać tego również klientom mojej biblioteki.

Mogę wymyślić wiele obejść tego problemu (zwracaj kody stanu, zwracaj ciąg błędów, dodaj ciąg błędów jako parametr wyjściowy), ale wszystkie mają swoje wady, a także chcę pozostać zgodny z konwencjami .NET Framework .

Zatem moje pytanie jest następujące:

Czy w systemie .NET Framework istnieją metody, które (a) analizują dane wejściowe bez zgłaszania wyjątków i (b) nadal zwracają bardziej szczegółowe informacje o błędach niż zwykły logiczny prawda / fałsz?

Heinzi
źródło
1
Ten link nie prowadzi do wniosku, że nie zaleca się zgłaszania wyjątków. Są chwile, które najlepiej wykorzystać Parse().
paparazzo

Odpowiedzi:

5

Polecam użycie wzorca monady jako typu zwrotu.

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

Należy również pamiętać, że Foo nie powinno być odpowiedzialne za ustalenie, w jaki sposób należy je przeanalizować z danych wejściowych użytkownika, ponieważ bezpośrednio wiąże to warstwę domeny z warstwą interfejsu użytkownika, a także narusza zasadę pojedynczej odpowiedzialności.

W Foozależności od przypadku użycia można także ustawić klasę wyników analizy jako specyficzną dla zamiast używać ogólnych.

Specjalna klasa wyników analizy składni foo może wyglądać mniej więcej tak:

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

Oto wersja Monady:

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

Nie znam żadnych metod w .NET, które zwracają szczegółowe informacje o błędach analizy.

TheCatWhisperer
źródło
Rozumiem twój komentarz na temat wiązania warstwy interfejsu użytkownika, ale w tym przypadku istnieje znormalizowana, kanoniczna reprezentacja ciągu Foo, więc sensowne jest mieć Foo.ToStringi Foo.Parse.
Heinzi
I, wrt moje śmiałe pytanie, czy możesz podać mi przykład z .NET BCL, który używa tego wzorca?
Heinzi
4
Jak to monada?
JacquesB
@Heinzi: Każda metoda, która zwraca a Func<T>, spełniałaby te kryteria, jeśli podasz wymagane Tinformacje. Zwracanie szczegółowych informacji o błędach zależy w dużej mierze od Ciebie. Czy zastanawiałeś się nad użyciem Maybe<T>? Zobacz mikhail.io/2016/01/monads-explained-in-csharp
Robert Harvey
@JacquesB: Zastanawiałem się nad tym samym. Podpis metody jest zgodny z zachowaniem modanicznym, ale o to chodzi.
Robert Harvey
1

Możesz spojrzeć na ModelState w frameworku MVC. Reprezentuje próbę parsowania niektórych danych wejściowych i może zawierać zbiór błędów.

To powiedziawszy, nie sądzę, że istnieje powtarzający się wzorzec tego w .net BCL, ponieważ wyjątki to - na lepsze lub gorsze - ustalony wzorzec zgłaszania błędów w .net. Myślę, że powinieneś po prostu wdrożyć własne rozwiązanie odpowiadające Twojemu problemowi, na przykład ParseResultklasę z dwiema podklasami SuccessfulParsei FailedParsegdzie SuccessfulParsema właściwość z przeanalizowaną wartością i FailedParsewłaściwość komunikatu o błędzie. Połączenie tego z dopasowaniem wzoru w C # 7 może być dość eleganckie.

JacquesB
źródło
1

Mam podobne problemy z chęcią skorzystania z TryParse/Convert/etc.metody, w której czasami muszę wiedzieć, jak i dlaczego zawiodła.

Zacząłem czerpać inspirację z tego, jak niektóre serializatory radzą sobie z błędami i wykorzystują zdarzenia. W ten sposób składnia mojej TryX(..., out T)metody wygląda tak samo czysto jak każda inna i niezawodnie zwraca prostą, falsejak sugeruje wzorzec.

Jednak gdy chcę potrzebować więcej szczegółów, po prostu dodaję moduł obsługi zdarzeń i uzyskuję potrzebne wyniki w pakiecie tak złożonym lub prostym, jak chcę ( MyEventArgsponiżej). Dodaj go do listy ciągów, dodaj ExceptionDispatchInfoi przechwyć wyjątki; pozwól dzwoniącemu zdecydować, czy i jak chce poradzić sobie ze wszystkim, co pójdzie nie tak.

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
źródło