c# - que - ¿Debo lanzar parámetros nulos en métodos privados/internos?
sobrecarga de metodos c# (7)
Si no eres un desarrollador de bibliotecas, no te pongas a la defensiva en tu código
Escribe pruebas unitarias en lugar
De hecho, incluso si estás desarrollando una biblioteca, la mayoría de las veces, lanzar es BAD
1. La prueba null
en int
nunca debe hacerse en c #:
Surge una advertencia CS4072 , porque siempre es falso.
2. Lanzar una excepción significa que es excepcional: anormal y raro.
Nunca debe subir en código de producción. Especialmente porque el recorrido de seguimiento de la pila de excepciones puede ser una tarea intensiva de la CPU. Y nunca estará seguro de dónde se capturará la excepción, si se captura y se registra o simplemente se ignora en silencio (después de matar uno de sus hilos de fondo) porque no controla el código de usuario. No hay una "excepción comprobada" en c # (como en java), lo que significa que nunca se sabe, si no está bien documentado, qué excepciones podría generar un método determinado. Por cierto, ese tipo de documentación debe mantenerse sincronizada con el código que no siempre es fácil de hacer (aumentar los costos de mantenimiento).
3. Las excepciones aumentan los costos de mantenimiento.
Como las excepciones se producen en el tiempo de ejecución y bajo ciertas condiciones, podrían detectarse muy tarde en el proceso de desarrollo. Como ya sabrá, cuanto más tarde se detecte un error en el proceso de desarrollo, más costosa será la solución. Incluso he visto que el código de aumento de excepciones se abrió camino hacia el código de producción y no se aumentó durante una semana, solo para aumentar todos los días en adelante (matando a la producción. ¡Ups!).
4. Lanzar en una entrada no válida significa que no controlas la entrada .
Es el caso de los métodos públicos de las bibliotecas. Sin embargo, si puede comprobarlo en tiempo de compilación con otro tipo (por ejemplo, un tipo no anulable como int), entonces es el camino a seguir. Y, por supuesto, como son públicos, es su responsabilidad verificar las aportaciones.
Imagine al usuario que usa lo que piensa como datos válidos y luego, por un efecto secundario, un método que se encuentra en el seguimiento de la pila genera una excepción ArgumentNullException
.
- ¿Cuál será su reacción?
- ¿Cómo puede hacer frente a eso?
- ¿Será fácil para usted proporcionar un mensaje de explicación?
5. Los métodos privados e internos nunca deben lanzar excepciones relacionadas con su entrada.
Puede lanzar excepciones en su código porque un componente externo (tal vez Base de datos, un archivo u otro) se está comportando mal y no puede garantizar que su biblioteca continuará ejecutándose correctamente en su estado actual.
Hacer público un método no significa que deba (solo que pueda) ser llamado desde fuera de su biblioteca ( Look at Public versus versus from Martin Fowler ). Use IOC, interfaces, fábricas y publique solo lo que necesita el usuario, mientras hace que todas las clases de biblioteca estén disponibles para la prueba de unidad. (O puede utilizar el mecanismo InternalsVisibleTo
).
6. Lanzar excepciones sin ningún mensaje de explicación es burlarse del usuario.
No es necesario recordar qué sentimientos puede tener uno cuando se rompe una herramienta, sin tener idea de cómo solucionarlo. Sí, lo sé. Usted viene a SO y hace una pregunta ...
7. La entrada no válida significa que rompe tu código
Si su código puede producir una salida válida con el valor, entonces no es válido y su código debería administrarlo. Añadir una prueba de unidad para probar este valor.
8. Piensa en términos de usuario:
¿Te gusta cuando una biblioteca que utilizas arroja excepciones para romper tu cara? Como: "¡Oye, no es válido, deberías haberlo sabido!"
Incluso si, desde su punto de vista, con su conocimiento de los aspectos internos de la biblioteca , la entrada no es válida, cómo puede explicárselo al usuario ( sea amable y cortés ):
- La documentación clara (en el documento XML y un resumen de la arquitectura puede ayudar).
- Publicar el documento XML con la biblioteca.
- Borre la explicación del error en la excepción si la hay.
- Dé la opción:
Mira la clase de diccionario, ¿qué prefieres? ¿Qué llamada crees que es la más rápida? ¿Qué llamada puede provocar excepción?
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string res;
dictionary.TryGetValue("key", out res);
o
var other = dictionary["key"];
9. ¿Por qué no usar contratos de código ?
Es una forma elegante de evitar lo feo if then throw
y aísla el contrato de la implementación, permitiendo reutilizar el contrato para diferentes implementaciones al mismo tiempo. Incluso puede publicar el contrato a su usuario de la biblioteca para explicarle con más detalle cómo usar la biblioteca.
Como conclusión, incluso si puede usar fácilmente throw
, incluso si puede experimentar el aumento de excepciones cuando usa .Net Framework, eso no significa que se pueda usar sin precaución.
Estoy escribiendo una biblioteca que tiene varias clases y métodos públicos, así como varias clases y métodos privados o internos que la biblioteca utiliza.
En los métodos públicos tengo un cheque nulo y un lanzamiento como este:
public int DoSomething(int number)
{
if (number == null)
{
throw new ArgumentNullException(nameof(number));
}
}
Pero luego, esto me hizo pensar: ¿a qué nivel debería agregar el parámetro nulo a los métodos? ¿También empiezo a agregarlos a métodos privados? ¿Debería hacerlo solo por métodos públicos?
La interfaz pública de su biblioteca merece un control estricto de las condiciones previas, porque debe esperar que los usuarios de su biblioteca cometan errores y violen las condiciones previas por accidente. Ayúdalos a entender lo que está pasando en tu biblioteca.
Los métodos privados en su biblioteca no requieren tal verificación en tiempo de ejecución porque los llama usted mismo. Usted tiene el control total de lo que está pasando. Si desea agregar cheques porque tiene miedo de desordenar, use aserciones. Captarán sus propios errores, pero no impidan el rendimiento durante el tiempo de ejecución.
A pesar de que ha marcado el language-agnostic
, me parece que probablemente no exista una respuesta general .
En particular, en su ejemplo, usted insinuó el argumento: por lo tanto, con un lenguaje que acepte insinuación, se activará un error tan pronto como ingrese la función, antes de que pueda realizar cualquier acción.
En tal caso, la única solución es haber comprobado el argumento antes de llamar a su función ... ¡pero ya que está escribiendo una biblioteca, eso no tiene sentido!
Por otro lado, sin sugerencia, sigue siendo realista verificar dentro de la función.
Así que en este paso de la reflexión, ya sugeriría dejar de insinuar.
Ahora volvamos a su pregunta precisa: ¿a qué nivel debería verificarse? Para una pieza de datos dada, ocurriría solo en el nivel más alto donde puede "ingresar" (puede haber varias apariciones para los mismos datos), por lo que lógicamente solo se refiere a métodos públicos.
Eso es para la teoría. Pero tal vez usted planea una biblioteca enorme y compleja, por lo que puede que no sea fácil tener la certeza de registrar todos los "puntos de entrada".
En este caso, sugeriría lo contrario: considere simplemente aplicar sus controles en todas partes, luego solo omítalo cuando vea claramente que está duplicado.
Espero que esto ayude.
Aquí están mis opiniones:
Casos generales
En términos generales, es mejor verificar si hay entradas no válidas antes de procesarlas en un método por razones de robustez , ya sea private, protected, internal, protected internal, or public
. Aunque hay algunos costos de rendimiento pagados por este enfoque, en la mayoría de los casos, vale la pena hacerlo en lugar de pagar más tiempo para depurar y parchear los códigos más adelante.
Hablando estrictamente, sin embargo ...
Estrictamente hablando, sin embargo, no siempre es necesario hacerlo . Algunos métodos, generalmente private
, se pueden dejar sin ninguna verificación de entrada siempre que tenga plena garantía de que no hay una sola llamada para el método con entradas no válidas . Esto puede darle algún beneficio de rendimiento , especialmente si el método se llama con frecuencia para realizar algún cálculo / acción básicos . Para tales casos, la verificación de la validez de entrada puede afectar significativamente el rendimiento.
Métodos Públicos
Ahora el método public
es más complicado. Esto se debe a que, más estrictamente hablando, aunque el modificador de acceso solo puede decir quién puede usar los métodos, no puede decir quién usará los métodos. Además, tampoco puede decir cómo se utilizarán los métodos (es decir, si los métodos se llamarán con entradas no válidas en los ámbitos dados o no).
El último factor determinante
Aunque los modificadores de acceso para los métodos en el código pueden sugerir cómo usar los métodos, en última instancia, son los humanos quienes usarán los métodos, y depende de los humanos cómo los van a usar y con qué información. Por lo tanto, en algunos casos raros, es posible tener un método public
que solo se llama en un ámbito private
y en ese ámbito private
, se garantiza que las entradas para los métodos public
son válidas antes de que se llame al método public
.
En tales casos, incluso si el modificador de acceso es public
, no hay ninguna necesidad real de verificar las entradas no válidas, excepto por una sólida razón de diseño. ¿Y por qué es esto así? ¡Porque hay humanos que saben completamente cuándo y cómo se llamarán los métodos!
Aquí podemos ver, no hay ninguna garantía de que public
método public
siempre requiera verificar las entradas no válidas. Y si esto es cierto para public
métodos public
, también debe serlo para public
métodos protected, internal, protected internal, and private
.
Conclusiones
Entonces, para concluir, podemos decir un par de cosas para ayudarnos a tomar decisiones:
- En general , es mejor tener verificaciones de entradas no válidas por razones de diseño sólidas, siempre que el rendimiento no esté en juego. Esto es cierto para cualquier tipo de modificadores de acceso.
- La verificación de las entradas no válidas se podría omitir si la ganancia de rendimiento pudiera mejorarse significativamente al hacerlo , siempre que también se pueda garantizar que el alcance donde se llaman los métodos siempre proporciona entradas válidas a los métodos.
-
private
métodoprivate
suele ser donde omitimos dicha comprobación, pero no hay ninguna garantía de que no podamos hacerlo parapublic
métodopublic
- Los seres humanos son los que finalmente usan los métodos. Independientemente de cómo los modificadores de acceso pueden insinuar el uso de los métodos, cómo se usan y llaman realmente los métodos dependen de los codificadores. Por lo tanto, solo podemos decir sobre la práctica general / buena, sin restringirla para que sea la única manera de hacerlo.
En última instancia, no hay un consenso uniforme sobre esto. Entonces, en lugar de dar una respuesta de sí o no, intentaré enumerar las consideraciones para tomar esta decisión:
Los cheques nulos inflan su código. Si sus procedimientos son concisos, los guardias nulos al principio de ellos pueden formar una parte significativa del tamaño total del procedimiento, sin expresar el propósito o el comportamiento de ese procedimiento.
Los controles nulos expresamente establecen una condición previa. Si un método va a fallar cuando uno de los valores es nulo, tener un chequeo nulo en la parte superior es una buena manera de demostrarlo a un lector casual sin que tengan que buscar el lugar donde se ha desreferido. Para mejorar esto, las personas a menudo usan métodos de ayuda con nombres como
Guard.AgainstNull
, en lugar de tener que escribir el cheque cada vez.Las verificaciones en métodos privados no son verificables. Al introducir una rama en su código que no tiene forma de atravesar completamente, hace imposible probar completamente ese método. Esto entra en conflicto con el punto de vista que las pruebas documentan el comportamiento de una clase y que el código de esa clase existe para proporcionar ese comportamiento.
La severidad de dejar pasar un nulo depende de la situación. A menudo, si un nulo entra en el método, se eliminará de la referencia unas pocas líneas más tarde y obtendrá una
NullReferenceException
. Esto realmente no es mucho menos claro que lanzar una excepciónArgumentNullException
. Por otro lado, si esa referencia se transmite bastante antes de ser desreferida, o si lanzar una NRE dejará las cosas en un estado desordenado, entonces lanzarlas antes es mucho más importante.Algunas bibliotecas, como los Contratos de Código de .NET, permiten un grado de análisis estático, que puede agregar un beneficio adicional a sus cheques.
Si está trabajando en un proyecto con otros, es posible que haya estándares de proyecto o de equipo existentes que cubran esto.
En mi opinión, SIEMPRE debería verificar los datos "no válidos", independientemente de si se trata de un método privado o público.
Visto de otra manera ... ¿por qué debería poder trabajar con algo no válido solo porque el método es privado? No tiene sentido, ¿verdad? Siempre intente usar programación defensiva y será más feliz en la vida ;-)
Esta es una cuestión de preferencia. Pero considere en su lugar por qué está comprobando si está nulo o, en cambio, está comprobando si hay entradas válidas. Probablemente se deba a que desea que el consumidor de su biblioteca sepa cuándo la está utilizando incorrectamente.
Imaginemos que hemos implementado una lista de elementos de clase en una biblioteca. Esta lista solo puede contener objetos del tipo Person
. También hemos implementado algunas operaciones en nuestra PersonList
y, por lo tanto, no queremos que contenga ningún valor nulo.
Considere las dos implementaciones siguientes del método Add
para esta lista:
Implementación 1
public void Add(Person item)
{
if(_size == _items.Length)
{
EnsureCapacity(_size + 1);
}
_items[_size++] = item;
}
Implementación 2
public void Add(Person item)
{
if(item == null)
{
throw new ArgumentNullException("Cannot add null to PersonList");
}
if(_size == _items.Length)
{
EnsureCapacity(_size + 1);
}
_items[_size++] = item;
}
Digamos que vamos con la implementación 1
- Ahora se pueden agregar valores nulos en la lista
- Todas las operaciones implementadas en la lista deberán manejar estos valores nulos
- Si deberíamos verificar y lanzar una excepción en nuestra operación, se le notificará al consumidor sobre la excepción cuando llame a una de las operaciones y en este estado no estará claro qué ha hecho mal (simplemente lo hará). No tiene ningún sentido ir por este enfoque).
Si, por el contrario, optamos por utilizar la implementación 2, nos aseguramos de que la entrada a nuestra biblioteca tenga la calidad que necesitamos para que nuestra clase opere. Esto significa que solo necesitamos manejar esto aquí y luego podemos olvidarlo mientras estamos implementando nuestras otras operaciones.
También resultará más claro para el consumidor que él / ella está utilizando la biblioteca de manera incorrecta cuando obtiene una .Sort
ArgumentNullException
en .Sort
en lugar de en .Sort
o similair.
Para resumir, mi preferencia es buscar un argumento válido cuando lo proporciona el consumidor y no lo manejan los métodos privados / internos de la biblioteca. Básicamente, esto significa que tenemos que verificar los argumentos en constructores / métodos que son públicos y toman parámetros. ¡Nuestros métodos private
/ internal
solo pueden ser llamados desde nuestros públicos y ya han verificado la entrada, lo que significa que estamos listos!
El uso de contratos de código también debe considerarse al verificar la entrada.