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

8

Durante l'analisi dell'input dell'utente, è generalmente consigliato non per generare e intercettare eccezioni, ma piuttosto per utilizzare metodi di convalida. In .NET BCL, questa sarebbe la differenza tra, ad esempio, int.Parse (genera un'eccezione su dati non validi) e int.TryParse (restituisce false su dati non validi).

Sto progettando il mio

Foo.TryParse(string s, out Foo result)

metodo e non sono sicuro del valore restituito. Potrei usare bool come il metodo TryParse di .NET, ma ciò non darebbe alcuna indicazione sul tipo di errore, sul motivo esatto perché s potrebbe non essere analizzato in Foo . (Ad esempio, s potrebbe avere una parentesi non corrispondente, o il numero errato di caratteri, o un Bar senza un corrispondente Baz , ecc.)

Come utente delle API, non mi piacciono molto i metodi che restituiscono un successo / fallimento Booleano senza dirmi perché l'operazione non è riuscita. Questo rende il debug di un gioco di indovinelli, e non voglio imporlo neanche ai client della mia biblioteca.

Posso pensare a un sacco di soluzioni 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 aspetti negativi, e voglio anche rimanere coerente con le convenzioni di .NET Framework .

Quindi, la mia domanda è la seguente:

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

    
posta Heinzi 22.01.2018 - 16:19
fonte

4 risposte

3

Raccomando di utilizzare il modello monad per il tipo di ritorno.

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

Nota anche, non dovrebbe essere la responsabilità di Foo di capire come dovrebbe essere analizzato dall'input dell'utente poiché questo lega direttamente il tuo livello di dominio al tuo livello di interfaccia utente, e viola anche il singolo principio di responsabilità.

Puoi anche creare una classe di risultati dell'analisi specifica su Foo invece di utilizzare i generici, a seconda del tuo caso d'uso.

Una classe risultato parse specifica di foo potrebbe essere simile a questa:

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

Ecco la versione di 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.

    
risposta data 22.01.2018 - 16:38
fonte
1

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

Detto questo, non penso che ci sia uno schema ricorrente per questo nel .net BCL, poiché le eccezioni sono - nel bene o nel male - il modello stabilito per riportare le condizioni di errore in .net. Penso che dovresti solo andare avanti e implementare la tua soluzione adattandoti al tuo problema, ad esempio una classe ParseResult con due sottoclassi, SuccessfulParse e FailedParse , dove SuccessfulParse ha una proprietà con il valore analizzato e FailedParse ha una proprietà di messaggio di errore. Combinare questo con il pattern matching in C # 7 potrebbe essere piuttosto elegante.

    
risposta data 22.01.2018 - 16:49
fonte
0

Per prima cosa, comprendi i motivi dei consigli che stai citando nella tua domanda.

Il motivo per cui è consigliabile non generare eccezioni durante l'analisi è perché lanciare un'eccezione è costoso. Se stai analizzando un set di dati con un ciclo annidato (un ciclo per righe, un ciclo per colonne) e stai creando un'eccezione nel ciclo interno perché una delle colonne ha un carattere alfanumerico, la tua routine di analisi si interromperà fino a fermarsi.

In questo caso, hai tre scelte:

  1. Lancia presto e fallisci velocemente o
  2. Utilizza TryParse. Se si verifica un errore, ripetere con Parse, lanciare una volta per ottenere le informazioni sull'errore e continuare senza analizzare i dati nella colonna non valida.
  3. Scrivi il tuo metodo di analisi che utilizza inizialmente TryParse e, se si verifica un errore, disponi di una logica personalizzata in grado di determinare il motivo per cui l'analisi non è riuscita senza generare un'eccezione.

Come hanno osservato altre risposte, è abbastanza facile creare un oggetto che contenga un valore o un errore. È anche abbastanza facile modellare il tuo metodo TryParseWithErrorInformation() utilizzando questa tattica, a patto che tu ne sia consapevole.

    
risposta data 22.01.2018 - 17:02
fonte
-1

Quanto spesso hai una classe che analizzerà dalla stringa? Puoi Nuovo e avere un costruttore di stringhe.

Se guardi sotto le coperte non c'è molto altro che gettare l'eccezione.

Sarebbe abbastanza facile che TryParseInt32 restituisca il motivo come output.

Spesso un'eccezione è davvero un'eccezione e Try Catch è ciò che le persone vogliono usare.

Not ware di qualsiasi analisi .NET che restituisce bool e reason e non lancia un'eccezione.

Un TryParse separato ha il lato negativo di più metodi da testare. TryParseReason è un altro metodo da testare.

    [System.Security.SecuritySafeCritical]  // auto-generated
    internal unsafe static Int32 ParseInt32(String s, NumberStyles style, NumberFormatInfo info) {

        Byte * numberBufferBytes = stackalloc Byte[NumberBuffer.NumberBufferBytes];
        NumberBuffer number = new NumberBuffer(numberBufferBytes);
        Int32 i = 0;

        StringToNumber(s, style, ref number, info, false);

        if ((style & NumberStyles.AllowHexSpecifier) != 0) {
            if (!HexNumberToInt32(ref number, ref i)) { 
                throw new OverflowException(Environment.GetResourceString("Overflow_Int32"));
            }
        }
        else {
            if (!NumberToInt32(ref number, ref i)) {
                throw new OverflowException(Environment.GetResourceString("Overflow_Int32"));
            }
        }
        return i;           
    }


    [System.Security.SecuritySafeCritical]  // auto-generated
    internal unsafe static Boolean TryParseInt32(String s, NumberStyles style, NumberFormatInfo info, out Int32 result) {

        Byte * numberBufferBytes = stackalloc Byte[NumberBuffer.NumberBufferBytes];
        NumberBuffer number = new NumberBuffer(numberBufferBytes);
        result = 0;

        if (!TryStringToNumber(s, style, ref number, info, false)) {
            return false;
        }

        if ((style & NumberStyles.AllowHexSpecifier) != 0) {
            if (!HexNumberToInt32(ref number, ref result)) { 
                return false;
            }
        }
        else {
            if (!NumberToInt32(ref number, ref result)) {
                return false;
            }
        }
        return true;           
    }
    
risposta data 23.01.2018 - 22:34
fonte

Leggi altre domande sui tag