Domanda

Visto che Java non ha tipi nullable, né ha TryParse (), come gestite la convalida dell'input senza generare eccezioni?

Nel solito modo:

String userdata = /*value from gui*/
int val;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException nfe)
{
   // bad data - set to sentinel
   val = Integer.MIN_VALUE;
}

Potrei usare una regex per verificare se è analizzabile, ma sembra anche un sacco di spese generali.

Qual è la migliore pratica per gestire questa situazione?

MODIFICA: Motivazione: Su SO si è parlato molto della gestione delle eccezioni e l'atteggiamento generale è che le eccezioni dovrebbero essere utilizzate solo per scenari imprevisti. Tuttavia, penso che l'input dell'utente errato sia ATTESO, non raro. Sì, è davvero un punto accademico.

Ulteriori modifiche:

Alcune delle risposte dimostrano esattamente cosa c'è di sbagliato in SO. Ignori la domanda che ti viene posta e rispondi a un'altra domanda che non ha nulla a che fare con essa. La domanda non si pone sulla transizione tra i livelli. La domanda non sta chiedendo cosa restituire se il numero non è analizzabile. Per quanto ne sai, val = Integer.MIN_VALUE; è esattamente l'opzione giusta per l'applicazione da cui è stato preso questo frammento di codice completamente contestuale.

È stato utile?

Soluzione

È praticamente tutto, sebbene restituire MIN_VALUE sia in qualche modo discutibile, a meno che tu non sia sicuro che sia la cosa giusta da usare per quello che stai essenzialmente usando come codice di errore. Almeno documenterei il comportamento del codice di errore, tuttavia.

Potrebbe anche essere utile (a seconda dell'applicazione) per registrare l'input errato in modo da poterlo tracciare.

Altri suggerimenti

Ho chiesto a se c'erano open source librerie di utilità che avevano metodi per eseguire questa analisi per te e la risposta è sì!

Da Apache Commons Lang puoi utilizzare NumberUtils.toInt :

// returns defaultValue if the string cannot be parsed.
int i = org.apache.commons.lang.math.NumberUtils.toInt(s, defaultValue);

Da Google Guava puoi utilizzare Ints.tryParse :

// returns null if the string cannot be parsed
// Will throw a NullPointerException if the string is null
Integer i = com.google.common.primitives.Ints.tryParse(s);

Non è necessario scrivere i propri metodi per analizzare i numeri senza generare eccezioni.

Per i dati forniti dall'utente, Integer.parseInt è di solito il metodo sbagliato perché non supporta l'internazionalizzazione. Il pacchetto java.text è il tuo amico (dettagliato).

try {
    NumberFormat format = NumberFormat.getIntegerInstance(locale);
    format.setParseIntegerOnly(true);
    format.setMaximumIntegerDigits(9);
    ParsePosition pos = new ParsePosition(0);
    int val = format.parse(str, pos).intValue();
    if (pos.getIndex() != str.length()) {
        // ... handle case of extraneous characters after digits ...
    }
    // ... use val ...
} catch (java.text.ParseFormatException exc) {
    // ... handle this case appropriately ...
}

Qual è il problema con il tuo approccio? Non penso che farlo in questo modo danneggerà affatto le prestazioni della tua applicazione. Questo è il modo corretto di farlo. Non ottimizzare prematuramente .

Sono sicuro che sia una cattiva forma, ma ho una serie di metodi statici su una classe Utilities che fanno cose come Utilities.tryParseInt (valore String) che restituisce 0 se String non è analizzabile e Utilities.tryParseInt (valore stringa, int defaultValue) che consente di specificare un valore da utilizzare se parseInt () genera un'eccezione.

Credo che ci siano momenti in cui è perfettamente accettabile restituire un valore noto su input errati. Un esempio molto ingegnoso: chiedi all'utente una data nel formato AAAAMMGG e ti danno un input errato. Potrebbe essere perfettamente accettabile fare qualcosa come Utilities.tryParseInt (data, 19000101) o Utilities.tryParseInt (data, 29991231); a seconda dei requisiti del programma.

Riaffermerò il punto che la puzzolente stava sollevando verso il fondo del post:

Un approccio generalmente ben accettato che convalida l'input dell'utente (o l'input dai file di configurazione, ecc ...) è quello di utilizzare la validazione prima di elaborare effettivamente i dati. Nella maggior parte dei casi , questa è una buona mossa di progettazione, anche se può provocare più chiamate agli algoritmi di analisi.

Dopo aver saputo di aver validato correttamente l'input dell'utente, quindi è sicuro analizzarlo e ignorarlo, registrarlo o convertirlo in RuntimeException in NumberFormatException.

Nota che questo approccio richiede di considerare il tuo modello in due parti: il modello di business (dove effettivamente ci interessa avere valori in formato int o float) e il modello di interfaccia utente (dove vogliamo davvero consentire all'utente di mettere in quello che vogliono).

Affinché i dati migrino dal modello dell'interfaccia utente al modello aziendale, devono passare attraverso una fase di convalida (ciò può avvenire campo per campo, ma la maggior parte degli scenari richiede la convalida sull'intero oggetto che è in fase di configurazione).

Se la convalida fallisce, all'utente viene presentato un feedback che lo informa di ciò che ha fatto di sbagliato e gli viene data la possibilità di risolverlo.

Le librerie vincolanti come JGoodies Binding e JSR 295 rendono questo tipo di cose molto più facile da implementare di quanto possa sembrare - e molti framework Web forniscono costrutti che separano l'input dell'utente dal modello di business reale, popolando solo oggetti di business al termine della convalida .

In termini di convalida dei file di configurazione (l'altro caso d'uso presentato in alcuni dei commenti), è una cosa specificare un valore predefinito se un determinato valore non è affatto specificato - ma se i dati sono formattati in modo errato (qualcuno digita un 'oh' invece di uno 'zero' o hanno copiato da MS Word e tutti i back-tick hanno un carattere unicode funky), quindi è necessaria una sorta di feedback del sistema (anche se sta semplicemente fallendo l'app lanciando un eccezione di runtime).

Ecco come lo faccio:

public Integer parseInt(String data) {
  Integer val = null;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Quindi il null segnala dati non validi. Se si desidera un valore predefinito, è possibile modificarlo in:

public Integer parseInt(String data,int default) {
  Integer val = default;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Penso che la migliore pratica sia il codice che mostri.

Non sceglierei l'alternativa regex a causa del sovraccarico.

Prova org.apache.commons.lang.math.NumberUtils.createInteger (String s) . Questo mi ha aiutato molto. Esistono metodi simili per raddoppiare, longs ecc.

È possibile utilizzare un numero intero, che può essere impostato su null se si dispone di un valore errato. Se stai usando java 1.6, ti fornirà auto boxing / unboxing per te.

Semantica più pulita (Java 8 OptionalInt)

Per Java 8+, probabilmente userei RegEx per pre-filtrare (per evitare l'eccezione come hai notato) e quindi avvolgere il risultato in un facoltativo primitivo (per gestire il problema di "quotazione di default"):

public static OptionalInt toInt(final String input) {
    return input.matches("[+-]?\\d+") 
            ? OptionalInt.of(Integer.parseInt(input)) 
            : OptionalInt.empty();
}

Se hai molti input String, potresti considerare di restituire un IntStream invece di OptionalInt in modo da poter flatMap () .

Riferimenti

Il codice sopra riportato è errato perché è equivalente al seguente.

// this is bad
int val = Integer.MIN_VALUE;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException ignoreException) { }

L'eccezione viene ignorata completamente. Inoltre, il token magico è errato perché un utente può passare -2147483648 (Integer.MIN_VALUE).

La generica domanda analizzabile non è vantaggiosa. Piuttosto, dovrebbe essere rilevante per il contesto. La tua applicazione ha un requisito specifico. Puoi definire il tuo metodo come

private boolean isUserValueAcceptable(String userData)
{
   return (    isNumber(userData)    
          &&   isInteger(userData)   
          &&   isBetween(userData, Integer.MIN_VALUE, Integer.MAX_VALUE ) 
          );
}

Dove è possibile documentare il requisito e creare regole ben definite e verificabili.

Se puoi evitare le eccezioni testando in anticipo come hai detto (isParsable ()), potrebbe essere meglio - ma non tutte le librerie sono state progettate tenendo presente questo aspetto.

Ho usato il tuo trucco e fa schifo perché le tracce dello stack sul mio sistema incorporato vengono stampate indipendentemente dal fatto che le prendi o meno :(

Il meccanismo di eccezione è prezioso, in quanto è l'unico modo per ottenere un indicatore di stato in combinazione con un valore di risposta. Inoltre, l'indicatore di stato è standardizzato. Se si verifica un errore, si ottiene un'eccezione. In questo modo non devi pensare a un indicatore di errore tu stesso. La controversia non è tanto con le eccezioni, ma con le Checked Eccezioni (ad esempio quelle che devi catturare o dichiarare).

Personalmente ritengo che tu abbia scelto uno degli esempi in cui le eccezioni sono davvero preziose. È un problema comune l'utente inserisce il valore errato e in genere è necessario tornare all'utente per il valore corretto. Normalmente non ripristini il valore predefinito se chiedi all'utente; che dà all'utente l'impressione che i suoi input contino.

Se non si desidera gestire l'eccezione, racchiuderla in una RuntimeException (o classe derivata) e ciò consentirà di ignorare l'eccezione nel codice (e uccidere l'applicazione quando si verifica; va bene anche a volte ).

Alcuni esempi su come gestire le eccezioni NumberFormat: Nei dati di configurazione dell'app Web:

loadCertainProperty(String propVal) {
  try
  {
    val = Integer.parseInt(userdata);
    return val;
  }
  catch (NumberFormatException nfe)
  { // RuntimeException need not be declared
    throw new RuntimeException("Property certainProperty in your configuration is expected to be " +
                               " an integer, but was '" + propVal + "'. Please correct your " +
                               "configuration and start again");
    // After starting an enterprise application the sysadmin should always check availability
    // and can now correct the property value
  }
}

In una GUI:

public int askValue() {
  // TODO add opt-out button; see Swing docs for standard dialog handling
  boolean valueOk = false;
  while(!valueOk) {
    try {
      String val = dialog("Please enter integer value for FOO");
      val = Integer.parseInt(userdata);
      return val; 
    } catch (NumberFormatException nfe) {
      // Ignoring this; I don't care how many typo's the customer makes
    }
  }
}

In un modulo Web: restituisce il modulo all'utente con un messaggio di errore utile e la possibilità di corretta. La maggior parte dei framework offre un modo standardizzato di validazione.

Integer.MIN_VALUE come NumberFormatException è una cattiva idea.

Puoi aggiungere una proposta a Project Coin per aggiungere questo metodo a Integer

@Nullable public static Integer parseInteger (String src) ... restituirà null per input errato

Quindi inserisci il link alla tua proposta qui e tutti voteremo per essa!

PS: guarda questo http://msdn.microsoft.com/en-us/library/bb397679.aspx questo è quanto brutto e gonfio potrebbe essere

Metti alcune dichiarazioni if ??di fronte. if (null! = userdata)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top