Come segnalare più errori come risultato della convalida?

4

Ho una classe che trasforma un modello complesso, ad esempio un albero sintattico astratto o un modello intermedio. Il modello può essere valido, non valido o parzialmente non valido, ovvero contiene errori ma alcune parti di esso sono valide e possono essere ulteriormente elaborate.

La segnalazione degli errori più semplice consiste nell'usare eccezioni:

public class Transformer {
    public TargetModel transform(SourceModel model)
      throws InvalidModelException {
        // ...
    }
}

Ovviamente questo non consente di riportare più errori (almeno se non si allegano ulteriori informazioni all'eccezione) e le eccezioni dovrebbero essere per situazioni eccezionali.

Martin Fowler ha affrontato questo problema nel suo articolo Sostituendo le eccezioni di lancio con la notifica nelle convalide . Se applichi il suo metodo al problema, hai qualcosa di simile a questo:

public abstract class Problem {
    // ...
}

public final class ProblemA {
    // ...
}

public final class ProblemB {
    // ...
}

public class Transformer {
    public static final class TransformationResult {
        private Collection<Problem> problems;
        private Optional<TargetModel> targetModel;
        // ...
    }

    public TransformationResult transform(SourceModel model) {
        // ...
    }
}

Quindi puoi utilizzare il pattern visitor o instanceof checks per distinguere gli errori. È un compromesso tra sicurezza del tipo e verbosità.

Un'altra possibile soluzione sarebbe quella di utilizzare il modello di osservatore:

public class Transformer {
    public interface ProblemListener {
        public void onProblemA(...);
        public void onProblemB(...);
        // ...
    }

    public void addProblemListener(ProblemListener listener) {
        // ...
    }

    public void removeProblemListener(ProblemListener listener) {
        // ...
    }

    public TargetModel transform(SourceModel model) {
        // ...
    }
}

L'utilizzo del pattern di osservatore ha il vantaggio che non è necessario accumulare gli errori in memoria e che non sono necessari eccessivi controlli di instanceof o visitatori. D'altra parte oscura il flusso di controllo anche più del modello di visitatore.

Penso che tutte le soluzioni non portino a codice leggibile. In un linguaggio funzionale e con una memoria sufficiente, utilizzerei la seconda soluzione, ma nella corrispondenza del modello strutturale Java implica o controlli instanceof o l'utilizzo del modello visitatore che sono entrambi dettagliati. C'è qualcosa che ho trascurato? O implicazioni che non ho considerato? Qual è la tua esperienza con una qualsiasi delle soluzioni?

    
posta user3998276 16.03.2016 - 13:14
fonte

3 risposte

1

The most simple error reporting is to use exceptions [...] this doesn't allow to report multiple errors (at least if you don't attach further information to the exception)

Allora ... perché non farlo?

public class InvalidModelException {
    private Collection<Problem> problems;
}
    
risposta data 20.04.2018 - 23:22
fonte
0

InvalidModelException dovrebbe essere astratto e sottoclassato da un'implementazione specializzata, che conterrà dettagli specifici. Ogni problema può essere gestito in un blocco separato:

TargetModel target;
try {
    target = transform(source);
} catch (ProblemA ex) {
    ex.getDetailsA();
} catch (ProblemB ex) {
    ex.getDetailsB();
} catch (InvalidModelException ex) {
    ex.getGenericDetails();
}

In alternativa, restituisci l'oggetto composto:

public Result<TargetModel, Diagnostic> transform(SourceModel model);

if (result.isInvalid()) {
    if (result.getDiagnostic() instanceof ...) 
       ...
    // or
    switch (result.getProblemType()) { 
    case Problem.A:
        break;
    case Problem.B:
        break;
    default:
        break;
    }
    throw new Exception();
}
target = result.getTarget();
    
risposta data 21.03.2018 - 22:08
fonte
0

Potresti descrivere Validazione come una funzione, che prende i dati come input e ti restituisce una tupla

inputdata -> (outpudata, Errors) o forse inputdata -> (outpudata, errors, warnings)

Dove sarà ovvio, che inputdata è i dati, vuoi nutrirti.

A seconda del tuo caso d'uso

  • outputdata è uguale a inputdata

  • outputdata è un qualche tipo di ValidationResult dove c'è data che è il tuo inputdata e forse validatedData che rappresenta il sottoinsieme che è valido (nel caso in cui tu voglia accettare risultati parziali ).

Il punto dell'articolo di Fowlers è che l'approccio ingenuo di lanciare le eccezioni ha diversi aspetti negativi. Un'eccezione è una specie di early exit . A seconda del tuo caso d'uso, non rivela tutto, ma si verifica solo il primo errore. Supponiamo che tu stia scrivendo un'app per moduli complessi, vuoi che l'utente informi su tutti di elementi in cui è necessaria un'azione, a meno che tu non voglia spingere i tuoi utenti a impazzire. Secondo: i dati non validi non sono realmente exceptionally , a volte ci si aspetta che i dati non siano corretti. Le eccezioni sono un po 'eccessivo in questo scenario. Terzo: le eccezioni potrebbero portare a una sorta di struttura di programmazione simile a goto , che potrebbe complicare la comprensione del flusso di programmazione. Eccezioni non dovrebbero essere utilizzate per questo.

In Java si farebbero errors e warnings membri di ValidationResult e scriverebbero hasErrors e hasWarnings e getErrors e getWarnings resp. addErrors e addWarnungs di conseguenza.

Il tuo problema si riduce a:

funzione di convalida generale: inputdata -> (outpudata, errors, warnings)

chiama un gruppo di funzioni di convalida

single valiator: data -> (errors, warnings)

o utilizzando ValidationResult come tipo di ritorno di una singola convalida

single valiator: inputdata -> (outpudata, errors, warnings)

dopo ogni chiamata, aggiungi tutto errors e warnings alle raccolte di ValidationResult o implementa un semplice merge() -Method su ValidationResult .

Alla fine restituisci l'ultimo ValidationResult

Fatto.

    
risposta data 21.04.2018 - 09:44
fonte

Leggi altre domande sui tag