Ho appena avuto una discussione su una scelta di design dopo una revisione del codice. Mi chiedo quali sono le tue opinioni.
Esiste questa classe Preferences
, che è un bucket per le coppie chiave-valore. I valori nulli sono legali (questo è importante). Prevediamo che alcuni valori potrebbero non essere ancora salvati e vogliamo gestire questi casi automaticamente inizializzandoli con il valore predefinito predefinito quando richiesto.
La soluzione discussa usata seguendo il modello (NOTA: questo è non il codice reale, ovviamente - è semplificato a scopo illustrativo):
public class Preferences {
// null values are legal
private Map<String, String> valuesFromDatabase;
private static Map<String, String> defaultValues;
class KeyNotFoundException extends Exception {
}
public String getByKey(String key) {
try {
return getValueByKey(key);
} catch (KeyNotFoundException e) {
String defaultValue = defaultValues.get(key);
valuesFromDatabase.put(key, defaultvalue);
return defaultValue;
}
}
private String getValueByKey(String key) throws KeyNotFoundException {
if (valuesFromDatabase.containsKey(key)) {
return valuesFromDatabase.get(key);
} else {
throw new KeyNotFoundException();
}
}
}
È stato criticato come anti-pattern - abuso di eccezioni per controllare il flusso . KeyNotFoundException
- portato alla vita solo per quel solo caso d'uso - non verrà mai visto al di fuori dello scopo di questa classe.
Sono essenzialmente due metodi che giocano a fetch semplicemente per comunicare qualcosa l'uno con l'altro.
La chiave non presente nel database non è qualcosa di allarmante, o eccezionale - ci aspettiamo che ciò accada ogni volta che viene aggiunta una nuova impostazione di preferenza, quindi il meccanismo che lo inizializza con grazia con un valore predefinito, se necessario.
Il controargomento era che getValueByKey
- il metodo privato - come definito in questo momento non ha alcun modo naturale di informare il metodo pubblico su entrambi il valore e se la chiave era lì. (In caso contrario, deve essere aggiunto in modo che il valore possa essere aggiornato).
Restituire null
sarebbe ambiguo, poiché null
è un valore perfettamente legale, quindi non si può sapere se significasse che la chiave non era presente, o se c'era un null
.
getValueByKey
dovrebbe restituire una sorta di Tuple<Boolean, String>
, il bool è impostato su true se la chiave è già presente, in modo da poter distinguere tra (true, null)
e (false, null)
. (Un parametro out
potrebbe essere usato in C #, ma quello è Java).
Questa è un'alternativa migliore? Sì, dovresti definire alcune classi monouso con l'effetto di Tuple<Boolean, String>
, ma poi ci sbarazzeremo di KeyNotFoundException
, quindi quel tipo di saldo. Stiamo anche evitando il sovraccarico di gestione di un'eccezione, sebbene non sia significativa in termini pratici - non ci sono considerazioni sulle prestazioni per parlare, è un'app client e non è come se le preferenze dell'utente vengano recuperate milioni di volte al secondo.
Una variante di questo approccio potrebbe utilizzare Optional<String>
di Guava (Guava è già utilizzato in tutto il progetto) invece di Tuple<Boolean, String>
personalizzato, quindi possiamo distinguere tra Optional.<String>absent()
e "proper" null
. Si sente comunque un hacker, tuttavia, per ragioni che sono facili da vedere - l'introduzione di due livelli di "nullità" sembra abusare del concetto che stava dietro creando Optional
s in primo luogo.
Un'altra opzione sarebbe quella di verificare esplicitamente se la chiave esiste (aggiungi un metodo boolean containsKey(String key)
e chiama getValueByKey
solo se abbiamo già affermato che esiste).
Infine, si potrebbe anche inline il metodo privato, ma il getByKey
effettivo è un po 'più complesso del mio esempio di codice, quindi l'inlining lo renderebbe piuttosto brutto.
Potrei essermi diviso i capelli qui, ma sono curioso di sapere cosa scommetterei per essere il più vicino alle migliori pratiche in questo caso. Non ho trovato una risposta nelle guide di stile di Oracle o di Google.
L'uso di eccezioni come nel codice di esempio è un anti-pattern, o è accettabile, dato che anche le alternative non sono molto pulite? Se lo è, in quali circostanze andrebbe bene? E viceversa?