Acabo de discutir sobre una elección de diseño después de una revisión de código. Me pregunto cuáles son tus opiniones.
Existe esta Preferences
clase, que es un cubo para pares clave-valor. Los valores nulos son legales (eso es importante). Esperamos que ciertos valores aún no se guarden, y queremos manejar estos casos automáticamente al inicializarlos con un valor predeterminado predefinido cuando se solicite.
La solución discutida utilizó el siguiente patrón (NOTA: este no es el código real, obviamente, está simplificado con fines ilustrativos):
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();
}
}
}
Fue criticado como un anti-patrón, abusando de las excepciones para controlar el flujo . KeyNotFoundException
- traído a la vida solo para ese caso de uso - nunca se verá fuera del alcance de esta clase.
Esencialmente, son dos métodos jugando con él simplemente para comunicarse entre sí.
La clave que no está presente en la base de datos no es algo alarmante o excepcional: esperamos que esto ocurra cada vez que se agregue una nueva configuración de preferencia, de ahí el mecanismo que la inicializa con gracia con un valor predeterminado si es necesario.
El argumento en contra era que getValueByKey
- el método privado - como se define en este momento no tiene forma natural de informar al público sobre el método tanto el valor, y si la clave estaba allí. (Si no fuera así, debe agregarse para que el valor pueda actualizarse).
Volver null
sería ambiguo, ya que null
es un valor perfectamente legal, por lo que no se sabe si eso significaba que la clave no estaba allí, o si había un null
.
getValueByKey
tendría que devolver algún tipo de a Tuple<Boolean, String>
, el bool establecido en verdadero si la clave ya está allí, para que podamos distinguir entre (true, null)
y (false, null)
. (Se out
podría usar un parámetro en C #, pero eso es Java).
¿Es esta una mejor alternativa? Sí, tendrías que definir alguna clase de un solo uso para el efecto de Tuple<Boolean, String>
, pero luego nos estamos deshaciendo de él KeyNotFoundException
, por lo que ese tipo de equilibrio se equilibra. También estamos evitando la sobrecarga de manejar una excepción, aunque no es importante en términos prácticos: no hay consideraciones de rendimiento de las que hablar, es una aplicación cliente y no es que las preferencias del usuario se recuperen millones de veces por segundo.
Una variación de este enfoque podría usar la guayaba Optional<String>
(la guayaba ya se usa en todo el proyecto) en lugar de alguna costumbre Tuple<Boolean, String>
, y luego podemos diferenciar entre Optional.<String>absent()
"apropiado" null
. Sin embargo, todavía se siente hackear, por razones que son fáciles de ver: la introducción de dos niveles de "nulidad" parece abusar del concepto que estaba detrás de la creación de Optional
s en primer lugar.
Otra opción sería verificar explícitamente si la clave existe (agregue un boolean containsKey(String key)
método y llame getValueByKey
solo si ya hemos afirmado que existe).
Finalmente, uno también podría incorporar el método privado, pero el actual getByKey
es algo más complejo que mi ejemplo de código, por lo que la inclusión en línea lo haría parecer bastante desagradable.
Puedo estar dividiendo pelos aquí, pero tengo curiosidad por saber qué apostarías a ser el más cercano a las mejores prácticas en este caso. No encontré una respuesta en las guías de estilo de Oracle o Google.
¿Usar excepciones como en el ejemplo de código es un antipatrón, o es aceptable dado que las alternativas tampoco son muy limpias? Si es así, ¿en qué circunstancias estaría bien? ¿Y viceversa?
getValueByKey
también es pública.