Come progetterei un metodo TryParse che fornisce informazioni dettagliate in caso di errore di analisi?


9

Quando si analizza l'input dell'utente, si consiglia generalmente di non generare eccezioni ma piuttosto di utilizzare metodi di convalida. Nel .NET BCL, questa sarebbe la differenza tra, ad esempio, int.Parse(genera un'eccezione su dati non validi) e int.TryParse(restituisce falsedati non validi).

Sto progettando il mio

Foo.TryParse(string s, out Foo result)

e non sono sicuro del valore restituito. Potrei usare boolcome il TryParsemetodo di .NET , ma questo non darebbe alcuna indicazione sul tipo di errore, sul motivo esatto per cui s non è stato possibile analizzare in un Foo. (Ad esempio, spotrebbe avere una parentesi senza pari, o un numero errato di caratteri o un Barsenza un corrispondente Baz, ecc.)

Come utente delle API, non mi piacciono i metodi che restituiscono un booleano di successo / fallimento senza dirmi perché l'operazione non è riuscita. Questo rende il debug un gioco d'ipotesi e non voglio nemmeno imporlo sui client della mia biblioteca.

Posso pensare a molte soluzioni alternative a questo problema (restituire codici di stato, restituire una stringa di errore, aggiungere una stringa di errore come parametro out), ma hanno tutti i loro lati negativi e voglio anche rimanere coerente con le convenzioni di .NET Framework .

Pertanto, la mia domanda è la seguente:

Esistono metodi in .NET Framework che (a) analizzano l'input senza generare eccezioni e (b) restituiscono comunque informazioni di errore più dettagliate di un semplice booleano true / false?


1
Tale collegamento non conclude che non è consigliabile generare eccezioni. Ci sono volte che il modo migliore è usare Parse().
paparazzo,

Risposte:


5

Consiglierei di usare il modello monade per il tuo tipo di ritorno.

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

Si noti inoltre che non dovrebbe essere responsabilità di Foo capire come dovrebbe essere analizzato dall'input dell'utente in quanto ciò lega direttamente il livello del dominio al livello dell'interfaccia utente e viola anche il principale responsabile.

È inoltre possibile effettuare una classe di risultati di analisi specifica Fooanziché utilizzare generici, a seconda del caso d'uso.

Una classe di risultati di analisi specifica potrebbe essere simile a questa:

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

Ecco la versione Monad:

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

Non sono a conoscenza di alcun metodo nel framework .net che restituisca informazioni dettagliate sull'errore di analisi.


Capisco il tuo commento sull'associazione del layer dell'interfaccia utente, ma in questo caso esiste una rappresentazione di stringhe canonica standardizzata di Foo, quindi ha senso avere Foo.ToStringe Foo.Parse.
Heinzi,

E, scrivendo la mia domanda in grassetto, puoi darmi un esempio dal .NET BCL che utilizza questo modello?
Heinzi,

4
Com'è quella monade?
Jacques B

@Heinzi: qualsiasi metodo che restituisca un Func<T>soddisferebbe tali criteri, se includi Tle informazioni di cui hai bisogno. La restituzione di informazioni dettagliate sull'errore dipende in gran parte da te. Hai considerato di usare un Maybe<T>? Vedi mikhail.io/2016/01/monads-explained-in-csharp
Robert Harvey,

@JacquesB: mi chiedevo quasi la stessa cosa. La firma del metodo è compatibile con il comportamento modanico, ma questo è tutto.
Robert Harvey,

1

Puoi guardare ModelState nel framework MVC. Rappresenta un tentativo di analisi di alcuni input e potrebbe contenere una raccolta di errori.

Detto questo, non credo che ci sia un modello ricorrente per questo nel BCL .net, poiché le eccezioni sono - nel bene e nel male - il modello stabilito per riportare le condizioni di errore in .net. Penso che dovresti semplicemente andare avanti e implementare la tua soluzione adatta al tuo problema, ad esempio una ParseResultclasse con due sottoclassi SuccessfulParsee FailedParse, dove SuccessfulParseha una proprietà con il valore analizzato e FailedParseha una proprietà del messaggio di errore. Combinarlo con la corrispondenza dei motivi in ​​C # 7 potrebbe essere piuttosto elegante.


1

Ho riscontrato problemi simili nel voler utilizzare un TryParse/Convert/etc.metodo in cui a volte ho bisogno di sapere come e perché non è riuscito.

Alla fine mi sono ispirato al modo in cui alcuni serializzatori gestiscono gli errori e utilizzano gli eventi. In questo modo la sintassi per il mio TryX(..., out T)metodo appare pulita come qualsiasi altra e restituisce in modo affidabile un semplice falsecome suggerisce il modello.

Tuttavia, quando voglio ulteriori dettagli, devo solo aggiungere un gestore eventi e ottenere tutti i risultati di cui ho bisogno in un pacchetto complesso o semplice come voglio (il MyEventArgsseguito). Aggiungilo a un elenco di stringhe, aggiungi ExceptionDispatchInfoe acquisisci Eccezioni; lascia che il chiamante decida se e come vuole gestire qualsiasi cosa vada storta.

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;}
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.