Depende. Decidir dónde colocar la validación debe basarse en descripción y la solidez del contrato implícito (o documentado) por el método. La validación es una buena manera de reforzar la adhesión a un contrato específico. Si por alguna razón el método tiene un contrato muy estricto, entonces sí, depende de usted verificar antes de llamar.
Este es un concepto especialmente importante cuando crea un método público , porque básicamente está anunciando que algún método realiza alguna operación. ¡Es mejor que hagas lo que dices!
Tome el siguiente método como ejemplo:
public void DeletePerson(Person p)
{
_database.Delete(p);
}
¿Qué implica el contrato DeletePerson
? El programador solo puede suponer que si Person
se pasa alguno , se eliminará. Sin embargo, sabemos que esto no siempre es cierto. ¿Qué pasa si p
es un null
valor? Y sip
no existe en la base de datos? ¿Qué pasa si la base de datos está desconectada? Por lo tanto, DeletePerson no parece cumplir bien su contrato. A veces, elimina a una persona, y a veces arroja una NullReferenceException, o una DatabaseNotConnectedException, o a veces no hace nada (como si la persona ya está eliminada).
Las API como esta son notoriamente difíciles de usar, porque cuando llama a este "recuadro negro" de un método, pueden ocurrir todo tipo de cosas terribles.
Aquí hay un par de formas en que puede mejorar el contrato:
Agregue validación y agregue una excepción al contrato. Esto fortalece el contrato , pero requiere que la persona que llama realice la validación. La diferencia, sin embargo, es que ahora conocen sus requisitos. En este caso, comunico esto con un comentario XML de C #, pero en su lugar podría agregar un throws
(Java), usar una Assert
herramienta de contrato o una herramienta de contrato como Code Contracts.
///<exception>ArgumentNullException</exception>
///<exception>ArgumentException</exception>
public void DeletePerson(Person p)
{
if(p == null)
throw new ArgumentNullException("p");
if(!_database.Contains(p))
throw new ArgumentException("The Person specified is not in the database.");
_database.Delete(p);
}
Nota al margen: El argumento en contra de este estilo es a menudo que causa una validación previa excesiva por parte de todos los códigos de llamada, pero en mi experiencia, este no suele ser el caso. Piense en un escenario en el que está tratando de eliminar una Persona nula. ¿Cómo pasó eso? ¿De dónde vino la Persona nula? Si se trata de una IU, por ejemplo, ¿por qué se manejó la tecla Eliminar si no hay una selección actual? Si ya se hubiera eliminado, ¿no debería haberse eliminado ya de la pantalla? Obviamente, hay excepciones a esto, pero a medida que crezca un proyecto, a menudo agradecerá a un código como este por evitar que los errores penetren profundamente en el sistema.
Agregue validación y código a la defensiva. Esto hace que el contrato sea más flexible , porque ahora este método hace más que simplemente eliminar a la persona. Cambié el nombre del método para reflejar esto, pero podría no ser necesario si eres coherente en tu API. Este enfoque tiene sus pros y sus contras. La ventaja es que ahora puede llamar TryDeletePerson
pasar todos los tipos de entradas no válidas y nunca preocuparse por las excepciones. La desventaja, por supuesto, es que los usuarios de su código probablemente llamarán demasiado a este método, o podría dificultar la depuración en los casos en que p sea nulo. Esto podría considerarse una violación leve del Principio de responsabilidad única , así que tenga en cuenta si estalla una guerra de llamas.
public void TryDeletePerson(Person p)
{
if(p == null || !_database.Contains(p))
return;
_database.Delete(p);
}
Combinar enfoques. A veces desea un poco de ambos, donde desea que las personas que llaman externas sigan las reglas de cerca (para obligarlos a codificar de manera responsable), pero desea que su código privado sea flexible.
///<exception>ArgumentNullException</exception>
///<exception>ArgumentException</exception>
public void DeletePerson(Person p)
{
if(p == null)
throw new ArgumentNullException("p");
if(!_database.Contains(p))
throw new ArgumentException("The Person specified is not in the database.");
TryDeletePerson(p);
}
internal void TryDeletePerson(Person p)
{
if(p == null || !_database.Contains(p))
return;
_database.Delete(p);
}
En mi experiencia, concentrarse en los contratos que implicó en lugar de una regla estricta funciona mejor. La codificación defensiva parece funcionar mejor en casos donde es difícil o difícil para la persona que llama determinar si una operación es válida. Los contratos estrictos parecen funcionar mejor cuando se espera que la persona que llama solo haga llamadas a métodos cuando realmente tienen sentido.
Es una cuestión de convención, documentación y caso de uso.
No todas las funciones son iguales. No todos los requisitos son iguales. No toda la validación es igual.
Por ejemplo, si su proyecto Java intenta evitar punteros nulos siempre que sea posible (consulte las recomendaciones de estilo Guava , por ejemplo), ¿aún valida todos los argumentos de función para asegurarse de que no sea nulo? Probablemente no sea necesario, pero es probable que aún lo haga para que sea más fácil encontrar errores. Pero puede usar una afirmación donde previamente lanzó una NullPointerException.
¿Qué pasa si el proyecto está en C ++? La convención / tradición en C ++ es documentar las condiciones previas, pero solo verificarlas (si es que las hay) en las compilaciones de depuración.
En cualquier caso, tiene una condición previa documentada en su función: ningún argumento puede ser nulo. En su lugar, podría ampliar el dominio de la función para incluir nulos con comportamiento definido, por ejemplo, "si algún argumento es nulo, arroja una excepción". Por supuesto, esa es mi herencia de C ++ hablando aquí: en Java, es bastante común documentar las condiciones previas de esta manera.
Pero no todas las condiciones previas ni siquiera se pueden verificar razonablemente. Por ejemplo, un algoritmo de búsqueda binaria tiene la condición previa de que la secuencia a buscar debe ser ordenada. Pero verificar que definitivamente es así es una operación O (N), por lo que hacer eso en cada llamada anula el punto de usar un algoritmo O (log (N)) en primer lugar. Si está programando a la defensiva, puede hacer comprobaciones menores (por ejemplo, verificar que para cada partición que busca, se ordenan los valores inicial, medio y final), pero eso no detecta todos los errores. Por lo general, solo tendrá que confiar en que se cumpla la condición previa.
El único lugar real donde necesita verificaciones explícitas es en los límites. ¿Entrada externa a su proyecto? Validar, validar, validar. Un área gris son los límites de la API. Realmente depende de cuánto desea confiar en el código del cliente, cuánto daño hace la entrada no válida y cuánta asistencia desea proporcionar para encontrar errores. Cualquier límite de privilegio debe contar como externo, por supuesto, las llamadas al sistema, por ejemplo, se ejecutan en un contexto de privilegio elevado y, por lo tanto, deben tener mucho cuidado de validar. Cualquier validación de este tipo debe ser interna a la llamada al sistema.
fuente
La validación de parámetros debería ser la preocupación de la función que se llama. La función debe saber qué se considera entrada válida y qué no. Las personas que llaman pueden no saber esto, especialmente cuando no saben cómo se implementa internamente la función. Se debe esperar que la función maneje cualquier combinación de valores de parámetros de las personas que llaman.
Debido a que la función es responsable de validar los parámetros, puede escribir pruebas unitarias contra esta función para asegurarse de que se comporta según lo previsto con valores de parámetros válidos e inválidos.
fuente
Dentro de la función misma. Si la función se usa más de una vez, no querrá verificar el parámetro para cada llamada a la función.
Además, si la función se actualiza de tal manera que afectará la validación del parámetro, debe buscar cada aparición de la validación de la persona que llama para actualizarlos. No es encantador :-).
Puede consultar la cláusula de guardia
Actualizar
Vea mi respuesta para cada escenario que haya proporcionado.
cuando el tratamiento de una variable no válida puede variar, es bueno validarlo en el lado de la persona que llama (por ejemplo, la
sqrt()
función; en algunos casos, es posible que desee trabajar con un número complejo, por lo que trato la condición en la persona que llama)Responder
La mayoría de los lenguajes de programación admite números enteros y reales por defecto, no números complejos, por lo tanto, su implementación
sqrt
solo acepta números no negativos. El único caso en el que tiene unasqrt
función que devuelve un número complejo es cuando usa un lenguaje de programación orientado a las matemáticas, como MathematicaAdemás,
sqrt
para la mayoría de los lenguajes de programación ya está implementado, por lo tanto, no puede modificarlo, y si intenta reemplazar la implementación (vea parches de mono), sus colaboradores se sorprenderán por quésqrt
repentinamente acepta números negativos.Si quería uno, puede ajustarlo a su
sqrt
función personalizada que maneja un número negativo y devuelve un número complejo.Cuando la condición de verificación es la misma en todas las personas que llaman, es mejor verificarla dentro de la función, para evitar duplicaciones
Responder
Sí, esta es una buena práctica para evitar dispersar la validación de parámetros en su código.
La validación del parámetro de entrada en la persona que llama tiene lugar solo una antes de llamar a muchas funciones con este parámetro. Por lo tanto, la validación de un parámetro en cada función no es efectiva
Responder
Sería bueno si la persona que llama es una función, ¿no te parece?
Si otra persona utiliza las funciones dentro de la persona que llama, ¿qué le impide validar el parámetro dentro de las funciones llamadas por la persona que llama?
la solución correcta depende del caso particular
Responder
Apunta al código mantenible. Mover su validación de parámetros asegura una fuente de verdad sobre lo que la función puede aceptar o no.
fuente
Una función debe indicar sus condiciones previas y posteriores.
Las condiciones previas son las condiciones que debe cumplir la persona que llama antes de que pueda usar correctamente la función y pueda (y a menudo lo haga) incluir la validez de los parámetros de entrada.
Las condiciones posteriores son las promesas que la función hace a sus llamantes.
Cuando la validez de los parámetros de una función es parte de las condiciones previas, es responsabilidad del llamante asegurarse de que esos parámetros sean válidos. Pero eso no significa que cada persona que llama tiene que verificar explícitamente cada parámetro antes de la llamada. En la mayoría de los casos, no se necesitan pruebas explícitas porque la lógica interna y las condiciones previas de la persona que llama ya garantizan que los parámetros sean válidos.
Como medida de seguridad contra errores de programación (errores), puede verificar que los parámetros pasados a una función realmente cumplan con las condiciones previas establecidas. Como estas pruebas pueden ser costosas, es una buena idea poder desactivarlas para las versiones de lanzamiento. Si estas pruebas fallan, entonces el programa debe finalizar, ya que probablemente se ha encontrado con un error.
Aunque a primera vista el cheque en la persona que llama parece invitar a la duplicación de código, en realidad es al revés. La verificación en la persona que llama da como resultado la duplicación de código y se realiza un montón de trabajo innecesario.
Solo piense en ello, con qué frecuencia pasa los parámetros a través de varias capas de funciones, realizando solo pequeños cambios en algunos de ellos en el camino. Si aplica constantemente el método check-in-callee , cada una de esas funciones intermedias tendrá que volver a hacer la verificación para cada uno de los parámetros. Con la verificación en la persona que llama, solo la primera función debería asegurarse de que la lista esté realmente ordenada. Todos los demás saben que la lista ya está ordenada (ya que eso es lo que dijeron en su condición previa) y pueden pasarla sin más controles.
Y ahora imagine que uno de esos parámetros se supone que es una lista ordenada.
fuente
En la mayoría de los casos, no puede saber quién, cuándo y cómo llamará a la función que escribió. Es mejor asumir lo peor: se llamará a su función con parámetros no válidos. Así que definitivamente deberías cubrir eso.
Sin embargo, si el idioma que usa admite excepciones, es posible que no verifique ciertos errores y esté seguro de que se lanzará una excepción, pero en este caso debe asegurarse de describir el caso en la documentación (necesita tener documentación). La excepción le dará a la persona que llama suficiente información sobre lo que sucedió y también dirigirá la atención a los argumentos no válidos.
fuente