Cuando intento crear una interfaz para un programa específico, generalmente trato de evitar lanzar excepciones que dependen de entradas no validadas.
Entonces, lo que sucede a menudo es que he pensado en un fragmento de código como este (este es solo un ejemplo por el bien de un ejemplo, no importa la función que realiza, ejemplo en 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();
}
Bien, digamos que en evenSize
realidad se deriva de la entrada del usuario. Así que no estoy seguro de que sea uniforme. Pero no quiero llamar a este método con la posibilidad de que se produzca una excepción. Entonces hago la siguiente función:
public static boolean isEven(int evenSize) {
return evenSize % 2 == 0;
}
pero ahora tengo dos comprobaciones que realizan la misma validación de entrada: la expresión en la if
declaración y la comprobación explícita isEven
. Código duplicado, no agradable, así que refactoricemos:
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, eso lo resolvió, pero ahora nos metemos en la siguiente situación:
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)
ahora tenemos una verificación redundante: ya sabemos que el valor es par, pero padToEvenWithIsEven
aún así realiza la verificación de parámetros, que siempre devolverá verdadero, como ya llamamos a esta función.
Ahora, por isEven
supuesto, no plantea un problema, pero si la verificación de parámetros es más engorrosa, esto puede generar un costo excesivo. Además de eso, realizar una llamada redundante simplemente no se siente bien.
A veces podemos solucionar este problema introduciendo un "tipo validado" o creando una función donde este problema no pueda ocurrir:
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();
}
pero esto requiere un pensamiento inteligente y un refactor bastante grande.
¿Hay alguna forma (más) genérica en la que podamos evitar las llamadas redundantes isEven
y realizar una verificación de doble parámetro? Me gustaría que la solución no llame realmente padToEven
con un parámetro no válido, desencadenando la excepción.
Sin excepciones, no me refiero a la programación sin excepciones, quiero decir que la entrada del usuario no desencadena una excepción por diseño, mientras que la función genérica en sí misma contiene la verificación de parámetros (aunque solo sea para proteger contra errores de programación).
fuente
padToEvenWithIsEven
no realiza la validación de la entrada del usuario. Realiza una verificación de validez en su entrada para protegerse contra errores de programación en el código de llamada. La extensión que debe tener esta validación depende de un análisis de costo / riesgo en el que se compara el costo de la verificación con el riesgo de que la persona que escribe el código de llamada pase el parámetro incorrecto.Respuestas:
En el caso de su ejemplo, la mejor solución es usar una función de relleno más general; Si la persona que llama quiere rellenar a un tamaño uniforme, puede comprobarlo por sí mismo.
Si realiza repetidamente la misma validación en un valor, o desea permitir solo un subconjunto de valores de un tipo, los microtipos / tipos pequeños pueden ser útiles. Para utilidades de uso general como el relleno, esta no es una buena idea, pero si su valor juega un papel particular en su modelo de dominio, usar un tipo dedicado en lugar de valores primitivos puede ser un gran paso adelante. Aquí, puedes definir:
Ahora puedes declarar
y no tiene que hacer ninguna validación interna. Para una prueba tan simple, envolver un int dentro de un objeto probablemente sea más costoso en términos de rendimiento de tiempo de ejecución, pero usar el sistema de tipos a su favor puede reducir errores y aclarar su diseño.
El uso de estos tipos pequeños incluso puede ser útil incluso cuando no realizan ninguna validación, por ejemplo, para desambiguar una cadena que representa a
FirstName
de aLastName
. Frecuentemente uso este patrón en lenguajes estáticamente escritos.fuente
EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
unsafePadStringToEven
operación que no realice ninguna verificación, pero parece una mala idea solo para evitar la validación.Como una extensión de la respuesta de @ amon, podemos combinar la suya
EvenInteger
con lo que la comunidad de programación funcional podría llamar un "Constructor inteligente", una función que envuelve al constructor tonto y se asegura de que el objeto esté en un estado válido (hacemos que el tonto clase de constructor o en módulo / paquete de idiomas no basados en clases privado para asegurarse de que solo se usen los constructores inteligentes). El truco consiste en devolver unOptional
(o equivalente) para que la función sea más composible.Entonces podemos usar fácilmente
Optional
métodos estándar para escribir la lógica de entrada:También podría escribir
keepAsking()
en un estilo Java más idiomático con un bucle do-while.Luego, en el resto de su código, puede confiar
EvenInteger
con la certeza de que realmente será uniforme y nuestro cheque par solo se escribió una vez, enEvenInteger::of
.fuente
Hacer la validación dos veces es un problema si el resultado de la validación es el mismo Y la validación se realiza en la misma clase. Ese no es tu ejemplo. En su código refactorizado, la primera comprobación de IsEven que se realiza es una validación de entrada, la falla da como resultado que se solicite una nueva entrada. La segunda comprobación es completamente independiente de la primera, ya que se encuentra en un método público padToEvenWithEven que se puede llamar desde fuera de la clase y tiene un resultado diferente (la excepción).
Su problema es similar al problema del código accidentalmente idéntico que se confunde por no seco. Estás confundiendo la implementación, con el diseño. No son lo mismo, y solo porque tenga una o una docena de líneas que sean iguales, no significa que estén cumpliendo el mismo propósito y que siempre puedan considerarse intercambiables. Además, es probable que tu clase haga demasiado, pero omite eso, ya que esto probablemente sea solo un ejemplo de juguete ...
Si se trata de un problema de rendimiento, puede resolver el problema creando un método privado, que no haga ninguna validación, que su padToEvenWithEven público llamó después de hacer su validación y que su otro método llamaría en su lugar. Si no se trata de un problema de rendimiento, deje que sus diferentes métodos hagan las comprobaciones que requieren para realizar sus tareas asignadas.
fuente