Acabei de discutir sobre uma opção de design após uma revisão de código. Eu me pergunto quais são suas opiniões.
Existe essa Preferences
classe, que é um balde para pares de valores-chave. Valores nulos são legais (isso é importante). Esperamos que determinados valores ainda não sejam salvos e queremos lidar com esses casos automaticamente, inicializando-os com o valor padrão predefinido quando solicitado.
A solução discutida usou o seguinte padrão (NOTA: este não é o código real, obviamente - é simplificado para fins 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();
}
}
}
Foi criticado como um antipadrão - abusando de exceções para controlar o fluxo . KeyNotFoundException
- trazido à vida apenas para esse caso de uso - nunca será visto fora do escopo desta classe.
São essencialmente dois métodos que o buscam, apenas para comunicar algo um ao outro.
A chave que não está presente no banco de dados não é algo alarmante ou excepcional - esperamos que isso ocorra sempre que uma nova configuração de preferência for adicionada, portanto, o mecanismo que a inicializa normalmente com um valor padrão, se necessário.
O contra-argumento é que getValueByKey
- o método privado - conforme definido no momento não tem nenhuma maneira natural de informar o método público sobre tanto o valor, e se a chave estava lá. (Caso contrário, ele deve ser adicionado para que o valor possa ser atualizado).
Retornar null
seria ambíguo, já que null
é um valor perfeitamente legal, portanto não há como dizer se isso significava que a chave não estava lá ou se havia um null
.
getValueByKey
teria que retornar algum tipo de a Tuple<Boolean, String>
, o bool definido como true se a chave já estiver lá, para que possamos distinguir entre (true, null)
e (false, null)
. (Um out
parâmetro pode ser usado em C #, mas isso é Java).
Esta é uma alternativa melhor? Sim, você teria que definir alguma classe de uso único para o efeito de Tuple<Boolean, String>
, mas então estamos nos livrando KeyNotFoundException
, para que esse tipo de equilíbrio seja equilibrado. Também estamos evitando a sobrecarga de lidar com uma exceção, embora não seja significativa em termos práticos - não há considerações de desempenho, é um aplicativo cliente e não é como se as preferências do usuário fossem recuperadas milhões de vezes por segundo.
Uma variação dessa abordagem poderia usar o Goiaba Optional<String>
(o Goiaba já é usado em todo o projeto) em vez de algum costume Tuple<Boolean, String>
, e então podemos diferenciar entre Optional.<String>absent()
"adequado" null
. No entanto, ele ainda parece hackeado por razões fáceis de ver - a introdução de dois níveis de "nulidade" parece abusar do conceito que estava por trás da criação de Optional
s em primeiro lugar.
Outra opção seria verificar explicitamente se a chave existe (adicione um boolean containsKey(String key)
método e chame getValueByKey
apenas se já tivermos afirmado que ela existe).
Por fim, também é possível incorporar o método privado, mas o real getByKey
é um pouco mais complexo do que o meu exemplo de código, portanto, o inlining o faria parecer bastante desagradável.
Talvez eu esteja dividindo os cabelos aqui, mas estou curioso para saber o que você apostaria para estar mais próximo das melhores práticas neste caso. Não encontrei uma resposta nos guias de estilo da Oracle ou do Google.
O uso de exceções, como no exemplo de código, é um antipadrão ou é aceitável, pois as alternativas também não são muito limpas? Se for, em que circunstâncias seria bom? E vice versa?
getValueByKey
também é pública.