Quando estou tentando criar uma interface para um programa específico, geralmente estou tentando evitar exceções que dependem de entradas não validadas.
Então, o que geralmente acontece é que eu pensei em um pedaço de código como este (este é apenas um exemplo para o exemplo, não se importe com a função que ele executa, por exemplo, em Java):
public static String padToEvenOriginal(int evenSize, String string) {
if (evenSize % 2 == 1) {
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
OK, digamos que evenSize
seja realmente derivado da entrada do usuário. Então, eu não tenho certeza se é mesmo. Mas não quero chamar esse método com a possibilidade de uma exceção ser lançada. Então, eu faço a seguinte função:
public static boolean isEven(int evenSize) {
return evenSize % 2 == 0;
}
mas agora eu tenho duas verificações que executam a mesma validação de entrada: a expressão na if
instrução e a verificação explícita isEven
. Código duplicado, nada legal, então vamos refatorar:
public static String padToEvenWithIsEven(int evenSize, String string) {
if (!isEven(evenSize)) { // to avoid duplicate code
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
OK, isso resolveu, mas agora entramos na seguinte situação:
String test = "123";
int size;
do {
size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)
agora temos uma verificação redundante: já sabemos que o valor é par, mas padToEvenWithIsEven
ainda realiza a verificação do parâmetro, que sempre retornará verdadeiro, como já chamamos essa função.
Agora isEven
, é claro, não representa um problema, mas se a verificação de parâmetros for mais complicada, isso poderá resultar em um custo muito alto. Além disso, realizar uma chamada redundante simplesmente não parece certo.
Às vezes, podemos contornar isso introduzindo um "tipo validado" ou criando uma função onde esse problema não pode ocorrer:
public static String padToEvenSmarter(int numberOfBigrams, String string) {
int size = numberOfBigrams * 2;
if (string.length() >= size) {
return string;
}
StringBuilder sb = new StringBuilder(size);
sb.append(string);
for (int i = string.length(); i < size; i++) {
sb.append('x');
}
return sb.toString();
}
mas isso requer um pensamento inteligente e um refator bastante grande.
Existe uma maneira (mais) genérica pela qual podemos evitar chamadas redundantes isEven
e executar a verificação de duplo parâmetro? Eu gostaria que a solução não chamasse padToEven
com um parâmetro inválido, acionando a exceção.
Sem exceções, não quero dizer programação livre de exceções, quero dizer que a entrada do usuário não aciona uma exceção por design, enquanto a própria função genérica ainda contém a verificação de parâmetros (apenas para proteger contra erros de programação).
fonte
padToEvenWithIsEven
não executa a validação da entrada do usuário. Ele executa uma verificação de validade em sua entrada para se proteger contra erros de programação no código de chamada. A extensão dessa validação depende de uma análise de custo / risco em que você coloca o custo da verificação no risco de que a pessoa que está escrevendo o código de chamada passe no parâmetro errado.Respostas:
No caso do seu exemplo, a melhor solução é usar uma função de preenchimento mais geral; se o chamador quiser ter um tamanho uniforme, ele poderá verificar isso sozinho.
Se você executar repetidamente a mesma validação em um valor ou desejar permitir apenas um subconjunto de valores de um tipo, os microtipos / tipos minúsculos podem ser úteis. Para utilitários de uso geral, como o preenchimento, essa não é uma boa ideia, mas se o seu valor desempenhar um papel específico no modelo de domínio, usar um tipo dedicado em vez de valores primitivos pode ser um grande passo à frente. Aqui, você pode definir:
Agora você pode declarar
e não precisa fazer nenhuma validação interna. Para um teste tão simples, envolver um int dentro de um objeto provavelmente será mais caro em termos de desempenho em tempo de execução, mas usar o sistema de tipos como vantagem pode reduzir bugs e esclarecer seu design.
Usar esses tipos minúsculos pode até ser útil, mesmo quando eles não realizam validação, por exemplo, para desambiguar uma string representando a
FirstName
de aLastName
. Eu freqüentemente uso esse padrão em linguagens estaticamente tipadas.fonte
EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
unsafePadStringToEven
operação que não realiza nenhuma verificação, mas parece uma má idéia apenas para evitar a validação.Como uma extensão da resposta do @ amon, podemos combiná-lo
EvenInteger
com o que a comunidade de programação funcional pode chamar de "Construtor Inteligente" - uma função que envolve o construtor burro e garante que o objeto esteja em um estado válido (fazemos o burro classe de construtor ou em módulo / pacote de linguagens não baseadas em classe privado (para garantir que apenas os construtores inteligentes sejam usados). O truque é retornar umOptional
(ou equivalente) para tornar a função mais compostável.Podemos então facilmente usar
Optional
métodos padrão para escrever a lógica de entrada:Você também pode escrever
keepAsking()
em um estilo Java mais idiomático com um loop do-while.No restante do seu código, você pode confiar,
EvenInteger
com certeza, que ele realmente será equilibrado e que nosso cheque par foi escrito apenas uma vezEvenInteger::of
.fonte
Fazer a validação duas vezes é um problema se o resultado da validação for o mesmo E a validação estiver sendo feita na mesma classe. Esse não é o seu exemplo. No seu código refatorado, a primeira verificação isEven feita é uma validação de entrada; a falha resulta em uma nova entrada sendo solicitada. A segunda verificação é completamente independente da primeira, como em um método público padToEvenWithEven que pode ser chamado de fora da classe e tem um resultado diferente (a exceção).
Seu problema é semelhante ao de código acidentalmente idêntico sendo confundido por não-seco. Você está confundindo a implementação com o design. Eles não são os mesmos, e só porque você tem uma ou uma dúzia de linhas iguais, não significa que elas estejam servindo ao mesmo propósito e possam ser consideradas para sempre intercambiáveis. Além disso, provavelmente a sua turma faz muito, mas pule isso, pois esse provavelmente é apenas um exemplo de brinquedo ...
Se esse é um problema de desempenho, você pode resolvê-lo criando um método privado, que não faz nenhuma validação, que seu padToEvenWithEven público chamou depois de fazer sua validação e que outro método chamaria. Se não for um problema de desempenho, deixe seus métodos diferentes fazerem as verificações necessárias para executar as tarefas atribuídas.
fonte