Wie würde ich eine TryParse-Methode entwerfen, die im Falle eines Analysefehlers detaillierte Informationen liefert?

9

Beim Parsen von Benutzereingaben wird generell empfohlen , keine Ausnahmen auszulösen und abzufangen, sondern Validierungsmethoden zu verwenden. In der .NET-BCL ist dies beispielsweise der Unterschied zwischen int.Parse(löst eine Ausnahme für ungültige Daten aus) und int.TryParse(Rückgabe falsefür ungültige Daten).

Ich entwerfe meine eigenen

Foo.TryParse(string s, out Foo result)

Methode und ich bin mir nicht sicher über den Rückgabewert. Ich könnte boolwie .NETs eigene TryParseMethode verwenden, aber das würde keinen Hinweis auf die Art des Fehlers geben, auf den genauen Grund, warum s nicht in a analysiert werden konnte Foo. (Zum Beispiel skönnte Bareine nicht übereinstimmende Klammer oder die falsche Anzahl von Zeichen oder eine ohne entsprechende Bazusw. vorhanden sein.)

Als Benutzer von APIs mag ich Methoden nicht, die nur einen Booleschen Wert für Erfolg / Misserfolg zurückgeben, ohne mir zu sagen, warum der Vorgang fehlgeschlagen ist. Dies macht das Debuggen zu einem Ratespiel, und das möchte ich auch den Clients meiner Bibliothek nicht aufzwingen.

Ich kann mir viele Problemumgehungen für dieses Problem vorstellen (Statuscodes zurückgeben, eine Fehlerzeichenfolge zurückgeben, eine Fehlerzeichenfolge als out-Parameter hinzufügen), aber alle haben ihre jeweiligen Nachteile, und ich möchte auch mit den Konventionen von konsistent bleiben das .NET Framework .

Meine Frage lautet also wie folgt:

Gibt es in .NET Framework Methoden, die (a) Eingaben analysieren, ohne Ausnahmen auszulösen, und (b) immer noch detailliertere Fehlerinformationen zurückgeben als ein einfacher wahr / falsch-Boolescher Wert?

Heinzi
quelle
1
Dieser Link schließt nicht, dass es nicht empfohlen wird, Ausnahmen zu werfen und zu fangen. Es gibt Zeiten, die am besten zu nutzen sind Parse().
Paparazzo

Antworten:

5

Ich würde empfehlen, das Monadenmuster für Ihren Rückgabetyp zu verwenden.

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

Beachten Sie auch, dass es nicht in der Verantwortung von Foo liegen sollte, herauszufinden, wie es aus Benutzereingaben analysiert werden soll, da dies Ihre Domain-Schicht direkt an Ihre UI-Schicht bindet und auch gegen das Prinzip der Einzelverantwortung verstößt.

FooAbhängig von Ihrem Anwendungsfall können Sie auch eine Analyseergebnisklasse erstellen, die spezifisch für die Verwendung von Generika ist, anstatt diese zu verwenden.

Eine foo-spezifische Analyseergebnisklasse könnte ungefähr so ​​aussehen:

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

Hier ist die Monadenversion:

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

Mir sind keine Methoden im .net-Framework bekannt, die detaillierte Analysefehlerinformationen zurückgeben.

TheCatWhisperer
quelle
Ich verstehe Ihren Kommentar zur UI-Ebenenbindung, aber in diesem Fall gibt es eine standardisierte, kanonische Zeichenfolgendarstellung von Foo, daher ist es sinnvoll, Foo.ToStringund zu haben Foo.Parse.
Heinzi
Und können Sie mir anhand meiner kühnen Frage ein Beispiel aus der .NET BCL geben, das dieses Muster verwendet?
Heinzi
4
Wie ist das eine Monade?
JacquesB
@Heinzi: Jede Methode, die a zurückgibt, Func<T>würde diese Kriterien erfüllen, wenn Sie sie in Tdie benötigten Informationen aufnehmen. Die Rückgabe detaillierter Fehlerinformationen liegt weitgehend bei Ihnen. Haben Sie darüber nachgedacht, ein zu verwenden Maybe<T>? Siehe mikhail.io/2016/01/monads-explained-in-csharp
Robert Harvey
@ JacquesB: Ich habe mich irgendwie das Gleiche gefragt. Die Methodensignatur ist mit dem modanischen Verhalten kompatibel, aber das war es auch schon.
Robert Harvey
1

Sie können sich ModelState im MVC-Framework ansehen . Es stellt eine versuchte Analyse einiger Eingaben dar und kann eine Sammlung von Fehlern enthalten.

Ich glaube jedoch nicht, dass es in der .net-BCL ein wiederkehrendes Muster dafür gibt, da Ausnahmen - zum Guten oder Schlechten - das etablierte Muster für die Meldung von Fehlerbedingungen in .net sind. Ich denke, Sie sollten einfach Ihre eigene Lösung implementieren, die zu Ihrem Problem passt, zum Beispiel eine ParseResultKlasse mit zwei Unterklassen, SuccessfulParseund FailedParsewo SuccessfulParseeine Eigenschaft mit dem analysierten Wert und FailedParseeine Fehlermeldungseigenschaft hat. Die Kombination mit dem Mustervergleich in C # 7 könnte ziemlich elegant sein.

JacquesB
quelle
1

Ich habe ähnliche Probleme mit dem Wunsch, eine TryParse/Convert/etc.Methode zu verwenden , bei der ich manchmal wissen muss, wie und warum sie fehlgeschlagen ist.

Am Ende habe ich mich davon inspirieren lassen, wie einige Serializer mit Fehlern umgehen und Ereignisse verwenden. Auf diese Weise TryX(..., out T)sieht die Syntax für meine Methode so sauber aus wie jede andere und gibt zuverlässig eine einfache zurück, falsewie das Muster impliziert.

Wenn ich jedoch weitere Details benötigen möchte, füge ich einfach einen Ereignishandler hinzu und erhalte die gewünschten Ergebnisse in einem Paket, das so komplex oder einfach ist, wie ich möchte (siehe MyEventArgsunten). Fügen Sie es einer Liste von Zeichenfolgen hinzu, fügen Sie eine hinzu ExceptionDispatchInfound erfassen Sie Ausnahmen. Lassen Sie den Anrufer entscheiden, ob und wie er mit etwas umgehen möchte, das schief geht.

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
quelle