Test di errori generici

0

Ho un programma che convalida il suo input e quindi errori.

Finora per ogni tipo di errore ho creato una nuova classe derivata. Ma questo stava diventando piuttosto ingombrante (l'input è complesso quindi ci sono molti errori possibili), quindi ho reso la base più flessibile e rimosso tutte le classi derivate.

Il problema che ne deriva è che ora quando eseguo il test con determinati input non validi, non sono più sicuro come identificare l'errore "corretto". Se il programma genera un errore sull'input non valido ma è errato, come posso identificare l'errore corretto senza ricorrere efficacemente a dettagli di implementazione dell'errore interno?

Modifica: intendevo anche specificare che il motivo per cui sono andato con una nuova classe derivata per tipo di errore è che ho bisogno di tipi di errore estensibili dinamicamente. Qualcosa come un'enumerazione non sarebbe mai adatta.

    
posta DeadMG 01.03.2015 - 20:42
fonte

4 risposte

1

Credo che l'approccio standard sia utilizzare un codice di errore (di tipo enum) e un messaggio di errore (di tipo stringa). Il "codice" facilita l'identificazione programmatica di un tipo specifico di errore ( if(e.code === DIVIDE_BY_ZERO) ) e il "messaggio" semplifica la visualizzazione di qualcosa di significativo per l'utente ( std::cout << e.message ).

Una nuova classe di eccezioni derivate dovrebbe probabilmente essere riservata a quelle categorie di eccezioni che sai che vuoi gestire in modo diverso dalle altre. In altre parole, se sai di un posto in cui hai il codice che vuoi inserire in un blocco catch (const SomeNewExceptionType & e), allora vale la pena creare SomeNewExceptionType. Questo di solito accade quando il gestore degli errori ha bisogno di molte più informazioni rispetto al tipo di errore. Ad esempio, un'eccezione di sintassi potrebbe voler portare in giro un blocco dell'albero di sintassi astratto su cui stava lavorando quando qualcosa è andato storto.

È anche possibile che tu stia semplicemente categorizzando erroneamente i tuoi errori (ad esempio, creando MissingSemicolonException e TooManyArgumentsException e così via invece di una singola SyntaxException). Potremmo avere bisogno di ulteriori informazioni per aiutarti se questo è il caso.

    
risposta data 01.03.2015 - 20:54
fonte
1

Oltre al tipo, in genere puoi specificare una descrizione testuale. Ad esempio, in un metodo SetPercentage(value) un errore OutOfRangeError generato quando il valore è inferiore a zero, ma anche superiore a cento.

Stesso tipo: due errori diversi.

Il testo dell'errore può quindi specificare cosa è andato storto nello specifico. Ad esempio:

if (value < 0) {
    raise OutOfRangeError("The value should not be inferior to 0.");
}

if (value > 100) {
    raise OutOfRangeError("The value should not be superior to 100.");
}

Descrizione utilizzata esclusivamente per il debug

Questo è ciò che è comunemente usato in linguaggi come Java o C # che usano eccezioni piuttosto che errori . In quelle eccezioni, la descrizione è intesa per essere letta da uno sviluppatore che mantiene (e esegue il debug) l'applicazione; non è previsto che venga mostrato all'utente finale o da utilizzare a livello di programmazione. Non è così utile essere in grado di determinare a livello programmatico quale delle due eccezioni si è verificata (facendo un switch sulla descrizione testuale è ovviamente una pessima idea).

Guarda l'immagine più grande. Immagina che il codice sopra sia nel nostro livello aziendale. Quindi, nel livello di presentazione, abbiamo:

try:
    int percentage = conversions.parseInt(field.value);
    if (percentage < 0) {
        field.highlightInRed();
        // The percentage should be superior to zero.
        form.showError("Le pourcentage doit être supérieur à zéro.");
    }
    else if (percentage > 100) {
        field.highlightInRed();
        // The percentage should be inferior to one hundred.
        form.showError("Le pourcentage doit être inférieur à cent.");
    }

    // Call to the business layer happens here.
    rebate.changeValue(precentage);

catch ConversionError:
    field.highlightInRed();
    // The percentage should be an integer number.
    form.showError("Le pourcentage doit être un nombre entier.");

Qui non aspettiamo che il livello aziendale generi un'eccezione : un'eccezione dovrebbe rimanere eccezionale, mentre gli utenti digitano "13" quando richiesto per inserire una cifra o gli utenti che scrivono "250" quando viene richiesto di inserire un numero compreso tra 0 e 100 non sono eccezionali.

I progettisti di interazione possono persino decidere che la convalida dell'input possa avvenire su ogni tratto chiave. Digitate 3 e 8 , lo sfondo del campo rimane bianco. Digitate un ulteriore 0 - lo sfondo del campo diventa rosso e appare un suggerimento, mostrando che 380 non è un valore valido per questo campo. Non chiederebbe al livello aziendale di generare eccezioni a ogni tratto di chiave, vero?

È solo se non riesco, come sviluppatore, a fornire controlli corretti a livello di presentazione, rispetto a OutOfRangeError nel livello aziendale. Questo probabilmente causerebbe un crash e una segnalazione di bug contenente la traccia dello stack: un debug di persone che troverà e risolverà l'errore nel livello di presentazione.

Se si nota una sorta di duplicazione logica tra il livello aziendale e il livello di presentazione nell'esempio precedente, il refactoring può aiutare:

  1. Nel livello aziendale, value era un numero intero. Cosa succede se creo un tipo Percentage ereditato da una classe appena creata IntInRange :

    abstract class IntInRange
    {
        private int value;
        public abstract readonly IntInRange Min;
        public abstract readonly IntInRange Max;
        public void SetValue(int value)
        {
            // Raise OutOfRangeErrors here.
            this.value = value;
        }
        ...
    }
    
    class Percentage : IntInRange
    {
        public readonly Percentage Min = 0;
        public readonly Percentage Max = 100;
    }
    
  2. Nel livello di presentazione, posso quindi utilizzare l'associazione dati per indicare che un determinato campo è associato alla variabile rebate.percentage di tipo Percentage . Se il framework di associazione dei dati è intelligente, posso collegare la gestione degli errori con le regole descritte in IntInRange type e il framework gestirà tutti gli errori stessi. Se è davvero intelligente, genererà il testo degli errori umanamente leggibile, come "La percentuale dovrebbe essere superiore a 0.", la parola "percentuale" corrispondente al nome della variabile.

Si noti che qui, il testo dell'eccezione non è ancora né usato a livello di programmazione, né mostrato all'utente finale. Il framework di associazione dei dati calcola le informazioni dalla classe stessa e il OutOfRangeError non dovrebbe mai essere colpito (o potrebbe indicare un bug nel framework o nel codice che utilizza questo framework).

Codice utilizzato per le API

Un altro uso che è corretto per gli errori, e non per le eccezioni, consiste nell'incorporare un codice nell'errore. Questo codice viene quindi utilizzato non solo durante il debug, ma anche da sviluppatori di terze parti che accedono a un'API.

Ad esempio, un'API astratta può restituire Error 500371 , che sembra indicare che il valore percentuale è inferiore a zero o Error 500373 , il che significa che il valore percentuale è superiore a cento.

Mentre molte API usano codici di errore criptici (spesso numeri), questo non è sempre il caso. Soprattutto i servizi REST utilizzano sempre più identificatori di testo di un errore:

{
    "error": {
        "id": "percentage-superior-to-100",
        "uri": "http://example.com/errors/percentage-superior-to-100",
        "description": "The specified value of the percentage is out of range. The value should be superior or equal to 0 and inferior or equal to 100."
    }
}

Qui, mentre description può cambiare (e può essere localizzato), il id è garantito per rimanere lo stesso per sempre (fino a quando l'API è obsoleto) e può essere usato a livello di programmazione.

    
risposta data 01.03.2015 - 20:56
fonte
0

In C #, il modo in cui questo viene fatto è aggiungendo metadati ai membri del tuo oggetto sotto forma di attributi di proprietà, ognuno dei quali è una classe. Non ce ne sono molti; incarnano categorie di errori e alcuni di essi prendono dei parametri. Ad esempio, per convalidare una proprietà di classe come un numero compreso tra 0 e 50, potresti decorare la proprietà con un attributo simile al seguente:

[Range(0, 50)]

Quindi utilizzare Reflection per ispezionare i metadati per determinare come convalidare ciascuna proprietà.

Ho capito che C ++ non è così abile in questo tipo di introspezione. Nei vecchi tempi di ASP.NET MVC, non disponevamo di questi attributi, quindi dovremmo semplicemente scrivere un metodo di convalida che restituisca una raccolta di oggetti ValidationError. Questa collezione potrebbe quindi essere restituita alla vista per la visualizzazione all'utente. In un oggetto ViewModel, questo ha perfettamente senso, perché l'unico scopo dell'oggetto ViewModel è quello di visualizzare dati e recuperare dati dall'utente tramite l'interfaccia utente.

    
risposta data 01.03.2015 - 21:14
fonte
0

Potrei anche usare un modello per generare classi derivate tramite tagging piuttosto che generare ogni volta una nuova classe. Qualcosa come

class BaseException {
    ...
};
template<typename T> struct DerivedException : BaseException {
    using BaseException::BaseException;
};
struct CyclicReference {};

Quindi potrei assumere

throw DerivedException<CyclicReference>(...);

Ciò significherebbe che potrei ancora nominare / specificare ogni tipo di eccezione, ma non sarebbe infinitamente laborioso specificare ogni classe derivata individualmente, poiché è solo un tipo di "tag" separato per ognuno, che ritengo sia molto più manutenibile, e gli utenti possono ancora specificare i propri tipi di "tag" o anche solo utilizzare direttamente la base. Quindi durante il test, posso semplicemente controllare il tag. E come bonus doppio, potrei specializzarmi o creare un nuovo derivato se avessi davvero bisogno di più della base.

Triplo bonus: ho appena scoperto che riutilizzare i tag significa quasi sicuramente codice duplicato.

    
risposta data 01.03.2015 - 23:08
fonte

Leggi altre domande sui tag