Questo per me suona come un problema ragionevolmente comune che gli sviluppatori da junior a intermedi tendono ad affrontare ad un certo punto: o non conoscono o non si fidano dei contratti a cui partecipano e superano difensivamente i null. Inoltre, quando scrivono il proprio codice, tendono a fare affidamento sulla restituzione di valori null per indicare qualcosa che richiede quindi al chiamante di verificare la presenza di valori null.
In altre parole, ci sono due casi in cui viene visualizzato il controllo null:
Dove null è una risposta valida in termini di contratto; e
Dove non è una risposta valida.
(2) è facile. Utilizzare assert
istruzioni (asserzioni) o consentire errori (ad esempio NullPointerException ). Le asserzioni sono una funzionalità Java molto sottoutilizzata che è stata aggiunta in 1.4. La sintassi è:
assert <condition>
o
assert <condition> : <object>
dove <condition>
è un'espressione booleana ed <object>
è un oggetto il cui toString()
output del metodo verrà incluso nell'errore.
Un'istruzione assert
genera un Error
( AssertionError
) se la condizione non è vera. Per impostazione predefinita, Java ignora le asserzioni. È possibile abilitare le asserzioni passando l'opzione -ea
alla JVM. È possibile abilitare e disabilitare le asserzioni per singole classi e pacchetti. Ciò significa che è possibile convalidare il codice con le asserzioni durante lo sviluppo e il test e disabilitarle in un ambiente di produzione, anche se i miei test hanno mostrato quasi nessun impatto sulle prestazioni da parte delle asserzioni.
Non usare le asserzioni in questo caso è OK perché il codice fallirà, che è ciò che accadrà se si usano le asserzioni. L'unica differenza è che con le asserzioni potrebbe accadere prima, in modo più significativo e possibilmente con informazioni aggiuntive, il che potrebbe aiutarti a capire perché è successo se non te lo aspettavi.
(1) è un po 'più difficile. Se non hai alcun controllo sul codice che stai chiamando, sei bloccato. Se null è una risposta valida, devi verificarla.
Se è il codice che controlli, tuttavia (e questo è spesso il caso), allora è una storia diversa. Evitare di utilizzare null come risposta. Con i metodi che restituiscono raccolte, è facile: restituire raccolte vuote (o matrici) invece di null praticamente sempre.
Con le non-collezioni potrebbe essere più difficile. Considera questo come esempio: se hai queste interfacce:
public interface Action {
void doSomething();
}
public interface Parser {
Action findAction(String userInput);
}
dove Parser accetta input utente non elaborati e trova qualcosa da fare, forse se stai implementando un'interfaccia della riga di comando per qualcosa. Ora è possibile rendere nullo il contratto se non viene eseguita alcuna azione appropriata. Questo porta al controllo nullo di cui stai parlando.
Una soluzione alternativa è non restituire mai null e utilizzare invece il modello Null Object :
public class MyParser implements Parser {
private static Action DO_NOTHING = new Action() {
public void doSomething() { /* do nothing */ }
};
public Action findAction(String userInput) {
// ...
if ( /* we can't find any actions */ ) {
return DO_NOTHING;
}
}
}
Confrontare:
Parser parser = ParserFactory.getParser();
if (parser == null) {
// now what?
// this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
// do nothing
} else {
action.doSomething();
}
per
ParserFactory.getParser().findAction(someInput).doSomething();
che è un design molto migliore perché porta a un codice più conciso.
Detto questo, forse è del tutto appropriato per il metodo findAction () generare un'eccezione con un messaggio di errore significativo, specialmente in questo caso in cui si fa affidamento sull'input dell'utente. Sarebbe molto meglio per il metodo findAction generare un'eccezione piuttosto che per il metodo chiamante esplodere con una semplice NullPointerException senza alcuna spiegazione.
try {
ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
userConsole.err(anfe.getMessage());
}
Oppure, se ritieni che il meccanismo try / catch sia troppo brutto, piuttosto che Non fare nulla, l'azione predefinita dovrebbe fornire un feedback all'utente.
public Action findAction(final String userInput) {
/* Code to return requested Action if found */
return new Action() {
public void doSomething() {
userConsole.err("Action not found: " + userInput);
}
}
}