Test accurato di più precondizioni prima di lanciare un IllegalArgumentException in modo che tutti i guasti siano rappresentati

1

La motivazione;

I want to report on all the possible problems at once instead of having to peel the onion of every possible problem over and over. It also makes testing much easier because I can have one test for success and one test that handles any/all failures. Much like you do form validation on the client side before you submit to the back end. But instead the back end is letting clients out of its control know what is wrong in total instead of peeling the onion one validation failure at a time.

Sto provando a creare un pattern per testare le precondizioni multiple che devono essere tutte true e più di uno può essere false .

Esempio:

Non essere preso alla lettera come il problema da risolvere, sto solo mostrando come esce la roba di Guava . Potrebbe essere un gruppo di istruzioni if/elseif/then/else o un gruppo di Predicates che sono tutte concatenate insieme a .and() . Questo è solo un strawman per mostrare come l'oscilloscopio sfugge al controllo.

public boolean process(@Nonnull final String left, @Nonnull final String right)
{
    Preconditions.checkArgument(!left.isEmpty());
    Preconditions.checkArgument(!right.isEmpty());
    Preconditions.checkArgument(/* left meets some regex */)
    Preconditions.checkArgument(/* right meets some regex */)
    /* more, more, more, etc. */
    /* actual logic goes here */
}

Potrei ovviamente completare ogni singolo controllo con un blocco try/catch creare un test List<IllegalArgumentException> se !isEmpty() e poi creare un ChainedException con tutte le cause principali e lanciarlo, ma questo è un gran numero di codice boilerplate inserire tutti i metodi che hanno più Precondition requirements.java

Now imagine that I have a lot more preconditions than this, and it is in a Builder.build() method. The client side form validation idiom makes good sense in these cases, like in the case of a Microservice that takes a payload and validates it before it acts on it. If it has dozens of preconditions it gets ugly fast. You do not want to have to make N calls with error codes for N validation failures, you want to send back all the problems in one error response instead of possibly dozens of back and forth.

public boolean save(@Nonnull final SomeDomainObjectWithLotsOfFields sdowlof) 
{ /* lots and lots of boilerplate to handle all the preconditions */ }

So che ci deve essere un qualche tipo di pattern ChainablePreconditions che non riesco a trovare e volevo assicurarmi che non mi manchi qualcosa prima di re-inventare la ruota e scrivere la mia su ChainOfResponsibliy di implementazione.

Altrimenti pubblicherò una risposta con un link a GitHub per qualsiasi altra idea che offro.

    
posta Jarrod Roberson 15.07.2018 - 18:42
fonte

1 risposta

4

Ci sono due tipi di errori molto diversi:

  • errori sul livello del dominio problematico su cui stai lavorando, ad es. errori di convalida del modulo e
  • errori logici all'interno del tuo programma, ad es. eccezioni del puntatore nullo.

Entrambi possono essere comunicati tramite eccezioni ma sono fondamentalmente diversi:

  • Gli errori del dominio dei problemi devono essere gestiti in modo che il tuo programma possa continuare. Se questi errori non vengono rilevati e gestiti, il tuo programma è sbagliato, ed eventualmente sottostimato.
  • Gli errori logici non sono recuperabili: il tuo programma è corrotto e non ha senso continuare. In alcuni casi un crash completo non è accettabile, quindi l'applicazione si riavvierà da sola. Il tuo programma è rotto indipendentemente dal fatto che tu stia verificando o meno questi errori, ma generalmente il loro rilevamento anticipato è preferibile a continuare a essere eseguito con la corruzione silenziosa.

Guava Preconditions è inteso come una sorta di asserzione che protegge da errori logici. Se falliscono, non ha senso continuare e sollevano immediatamente un IllegalArgumentException o altre eccezioni, a seconda dei casi. Il programma dovrebbe essere ancora corretto se queste asserzioni sono state rimosse.

Ciò che ottieni facendo una singola eccezione che aggrega tutti i guasti di precondizione non è alcun cambiamento nella correttezza, ma solo una migliore esperienza di sviluppo. Questo può essere un obiettivo degno! Soprattutto se stai creando una libreria che verrà utilizzata da molti sviluppatori, i grandi messaggi di errore sono abbastanza preziosi. Nel codice dell'applicazione, il valore è più dubbio. Riceverai comunque i messaggi di errore in entrambi i modi, quindi non ci sarà molto tempo per il debugging salvato. Tutti gli sviluppatori hanno probabilmente familiarità sufficiente con la base di codice per sapere dove cercare quando ci sono problemi.

Se vuoi veramente usare questi messaggi di asserzione raggruppati, dovrai scrivere le tue funzioni di asserzione per quello. Lei menziona un modello di Chain Of Responsibility, che è un possibile approccio. Tuttavia, potrebbe essere difficile da configurare.

Un approccio che suggerirei se è possibile utilizzare Java 8 è quello di creare una funzione che richiede più callback di convalida e si occupa di rilevare le eccezioni, se necessario. Circa:

@FunctionalInterface Precondition { void check(); }
static void checkAll(Precondition... preconditions) {
  List<Throwable> errors = new ArrayList<>();
  for (Precondition p : preconditions) {
    try { p.check(); }
    catch (IllegalArgumentException | NullPointerException | ... e) {
      errors.add(e);
    }
  }
  throw ...;
}

Chiamato come

checkAll(
  () -> Preconditions.checkNotNull(a),
  () -> Preconditions.checkNotNull(b, "b must be present"),

Tuttavia, quel macchinario in più potrebbe avere un overhead di runtime proibitivo. Le precondizioni dovrebbero essere il più economiche possibile e l'introduzione di lambda non sarà di aiuto qui.

Anche questo non è generalizzato se ci sono delle dipendenze tra i controlli di precondizione. Prendi in considerazione ad esempio il codice:

Preconditions.checkNotNull(a);
Preconditions.checkNotNull(map);
Preconditions.checkNotNull(key);
Preconditions.checkNotNull(map.get(key));

Tutto sommato, costruisci delle belle astrazioni se ti aiutano, ma versare troppi sforzi in un caso speciale dell'esperienza degli sviluppatori fornisce solo un valore molto limitato.

    
risposta data 15.07.2018 - 19:27
fonte

Leggi altre domande sui tag