2009-03-21 15 views
29

Un problema común en cualquier idioma es afirmar que los parámetros enviados a un método cumplen con sus requisitos, y si no lo hacen, enviar mensajes de error agradables e informativos. Este tipo de código se repite una y otra vez, y a menudo intentamos crear ayuda para él. Sin embargo, en C#, parece que esos ayudantes se ven obligados a lidiar con alguna duplicación impuesta sobre nosotros por el lenguaje y el compilador. Para mostrar lo que quiero decir, permítanme presentar algunos códigos en bruto sin ayudantes, seguidos de un posible ayudante. Luego, señalaré la duplicación en el ayudante y formularé mi pregunta con precisión.¿Cuál es la sintaxis más suave y atractiva que ha encontrado para afirmar la corrección de parámetros en C#?

En primer lugar, el código sin ningún ayudantes:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
    if(firstName == null) 
    { 
      throw new WhateverException("The value for firstName cannot be null."); 
    } 

    if(lastName == null) 
    { 
      throw new WhateverException("The value for lastName cannot be null."); 
    } 

    // Same kind of code for age, making sure it is a reasonable range (< 150, for example). 
    // You get the idea 
} 

}

Ahora, el código con un intento razonable de un ayudante:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
     Helper.Validate(x=> x !=null, "firstName", firstName); 
     Helper.Validate(x=> x!= null, "lastName", lastName); 
} 

La principal la pregunta es esta: Observe cómo debe pasar el código s el valor del parámetro y el nombre del parámetro ("firstName" y firstName). Esto es para que el mensaje de error pueda decir: "Bla, bla, bla, el valor del parámetro firstName ". ¿Has encontrado alguna forma de evitar esto usando el reflejo o cualquier otra cosa? ¿O una forma de hacerlo menos doloroso?

Y, en términos más generales, ¿ha encontrado otras formas de simplificar esta tarea de validación de parámetros mientras reduce la duplicación de código?

EDIT: He leído a personas que hablan acerca de hacer uso de la propiedad Parámetros, pero que nunca han encontrado una forma de evitar la duplicación. Alguien tiene suerte con eso?

Gracias!

+0

El uso de 'no se recomienda ApplicationException' clase. –

+0

Nitpicking: _Direct_ no se recomienda el uso de 'ApplicationException' –

+0

OK, lo cambié a Exception, solo para evitar distracciones.Por otro lado, si la recomendación se basa en los comentarios de los chicos que escribieron el libro "Pautas marco", entonces no estoy muy convencido (aunque esos tipos han escrito algunas cosas buenas). Pero esa es una discusión para otro día. –

Respuesta

6
+0

Gracias! ¡Esto condujo a 2 enfoques que me han impresionado! Y puedo ver el uso de ambos. –

+0

Esa implementación de validación con fluidez es bastante hábil eh. – core

+0

Sí, es muy resbaladizo. No me había dado cuenta de que puede llamar a un método de extensión en una variable que es nula. No sé si Anders quería que eso sucediera o si es un efecto secundario. De cualquier manera, es muy útil. –

0

En este caso, en lugar de utilizar su propio tipo de excepción, o tipos muy generales como ApplicationException .. creo que lo mejor es utilizar el construido en tipos de excepción que están diseñadas específicamente para este uso:

Entre los .. System.ArgumentException, System.ArgumentNullException ...

+0

OK, pero por favor ... no nos centremos en el tipo de excepción. Eso es accesorio al problema central aquí y por lo tanto, escribí lo primero que me vino a la cabeza. –

+0

Bien ... pero creo que cada vez que se lanzan excepciones, arrojar un tipo de excepción útil es absolutamente esencial. – markt

15

Debe consultar Code Contracts; ellos hacen casi exactamente lo que estás preguntando. Ejemplo:

[Pure] 
public static double GetDistance(Point p1, Point p2) 
{ 
    CodeContract.RequiresAlways(p1 != null); 
    CodeContract.RequiresAlways(p2 != null); 
    // ... 
} 
+0

Oye, eso es genial ... especialmente que estará integrado en el lenguaje en C# 4.0. Pero aún así ... ¿qué podemos hacer mientras tanto? –

+0

Bueno, se horneará en .NET 4, pero todavía puedes usarlos ahora mismo. No dependen de nada específico de .NET 4, por lo que puede volver a 2.0 si lo desea. El proyecto está aquí: http://research.microsoft.com/en-us/projects/contracts/ –

+0

Puede trabajar en sus propias clases que se parecen a eso, pero arrojar excepciones para las bibliotecas. Bastante simple para escribir uno. – user7116

0

no sabemos si esta técnica de las transferencias de C/C++ a C#, pero he hecho esto con macros:

 

    #define CHECK_NULL(x) { (x) != NULL || \ 
      fprintf(stderr, "The value of %s in %s, line %d is null.\n", \ 
      #x, __FILENAME__, __LINE__); } 
+0

Eso es exactamente el tipo de cosa que estoy esperando, pero no creo que se pueda hacer directamente en C#. –

+0

El #x aquí se llama el operador de stringisizing y no, no tenemos nada parecido en C# –

+0

C# no es compatible con el tipo de trucos de preprocesador que se encuentran en los compiladores de C++. –

1

Mi preferencia sería evaluar simplemente la condición y pasa el resultado en lugar de pasar una expresión para ser evaluada y el parámetro para evaluarla. Además, prefiero tener la capacidad de personalizar todo el mensaje. Tenga en cuenta que estas son simplemente preferencias, no digo que su muestra sea incorrecta, pero hay algunos casos en que esto es muy útil.

Helper.Validate(firstName != null || !string.IsNullOrEmpty(directoryID), 
       "The value for firstName cannot be null if a directory ID is not supplied."); 
5

Usando mi biblioteca The Helper Trinity:

public void SomeMethod(string firstName, string lastName, int age) 
{ 
    firstName.AssertNotNull("firstName"); 
    lastName.AssertNotNull("lastName"); 

    ... 
} 

también apoya afirmando que los parámetros de enumeración son correctos, colecciones y sus contenidos son no null, parámetros de cadena son etcétera que no esté vacía. Consulte la documentación del usuario here para ver ejemplos detallados.

+0

Agradable. Lo comprobaré. –

+0

+1. Me gusta el uso de métodos de extensión para minimizar la cantidad de palabras. Llamaría al método MustNotBeNull o algo en su lugar, pero eso es solo preferencia personal –

+0

¿Qué sucede en ese código si firstName es nulo? ¿No se produciría un NullRef? Nunca intenté ver si los métodos de extensión aún pueden adjuntarse a un puntero nulo. –

7

Abordé este problema exacto hace unas semanas, después de pensar que es extraño cómo las bibliotecas de prueba parecen necesitar un millón de versiones diferentes de Assert para hacer que sus mensajes sean descriptivos.

Here's my solution.

Breve resumen - dado este trozo de código:

int x = 3; 
string t = "hi"; 
Assert(() => 5*x + (2/t.Length) < 99); 

Mi función Assert puede imprimir el siguiente resumen de lo que se pasa a la misma:

(((5 * x) + (2/t.Length)) < 99) == True where 
{ 
    ((5 * x) + (2/t.Length)) == 16 where 
    { 
    (5 * x) == 15 where 
    { 
     x == 3 
    } 
    (2/t.Length) == 1 where 
    { 
     t.Length == 2 where 
     { 
     t == "hi" 
     } 
    } 
    } 
} 

Así que todos los nombres y valores de identificador , y la estructura de la expresión, podría incluirse en el mensaje de excepción, sin tener que volver a expresarlos en cadenas entrecomilladas.

+0

Guau, eso es excelente. No sé por qué no pensé en eso (las buenas ideas a menudo provocan esa sensación). Estoy escribiendo una aplicación de cálculo para la cual esto podría ser muy bueno en términos de explicar las respuestas a las que se llegó. –

+0

La combinación de árboles de reflexión y expresión permite muchas cosas que te toman por sorpresa, especialmente si (como yo) vienes de un fondo C/C++ donde el tiempo de ejecución es inexistente. ¡Tenemos que reinventar un montón de cosas que Lispers ha estado haciendo durante medio siglo! –

+0

¡Buena idea, y también para usar para una aplicación calc! – flq

11

Vaya, he encontrado algo muy interesante aquí. Chris arriba dio un enlace a otra pregunta de Desbordamiento de pila. Una de las respuestas no apuntaban a una entrada de blog que describe cómo obtener un código como éste:

public static void Copy<T>(T[] dst, long dstOffset, T[] src, long srcOffset, long length) 
{ 
    Validate.Begin() 
      .IsNotNull(dst, “dst”) 
      .IsNotNull(src, “src”) 
      .Check() 
      .IsPositive(length) 
      .IsIndexInRange(dst, dstOffset, “dstOffset”) 
      .IsIndexInRange(dst, dstOffset + length, “dstOffset + length”) 
      .IsIndexInRange(src, srcOffset, “srcOffset”) 
      .IsIndexInRange(src, srcOffset + length, “srcOffset + length”) 
      .Check(); 

    for (int di = dstOffset; di < dstOffset + length; ++di) 
     dst[di] = src[di - dstOffset + srcOffset]; 
} 

No estoy convencido de que es la mejor respuesta todavía, pero sin duda es interesante. Here's the blog post, de Rick Brewster.

6

Bien chicos, soy yo otra vez, y encontré algo más que es asombroso y delicioso. Es otra publicación de blog a la que se hace referencia desde la otra pregunta de SO que Chris mencionó anteriormente.

enfoque de este tipo permite escribir esto:

public class WebServer 
    { 
     public void BootstrapServer(int port, string rootDirectory, string serverName) 
     { 
      Guard.IsNotNull(() => rootDirectory); 
      Guard.IsNotNull(() => serverName); 

      // Bootstrap the server 
     } 
    } 

Tenga en cuenta que no hay una cadena que contiene "RootDirectory" y ninguna cadena que contiene "servidor" !! Y sin embargo, sus mensajes de error pueden decir algo como "El parámetro de directorio raíz no debe ser nulo".

Esto es exactamente lo que quería y más de lo que esperaba. Here's the link to the guy's blog post.

Y la aplicación es bastante simple, de la siguiente manera:

public static class Guard 
    { 
     public static void IsNotNull<T>(Expression<Func<T>> expr) 
     { 
      // expression value != default of T 
      if (!expr.Compile()().Equals(default(T))) 
       return; 

      var param = (MemberExpression) expr.Body; 
      throw new ArgumentNullException(param.Member.Name); 
     } 
    } 

Tenga en cuenta que esto hace uso de "reflexión estática", por lo que en un bucle estrecho o algo así, es posible que desee utilizar el enfoque de Rick Brewster anteriormente .

Tan pronto como publique esto voy a votar a Chris, y la respuesta a la otra pregunta de SO. ¡Estas son algunas cosas buenas!

+1

Hola, llegué un poco tarde notando esto, pero creo que se está perdiendo un poco el poder general de las expresiones.Mire mi respuesta de nuevo: no necesita escribir una función separada para cada tipo de afirmación que desee realizar. Simplemente escriba una expresión en la sintaxis normal de C#: 'X.Assert (() => serverName! = Null);' No hay una buena razón para acumular un conjunto de métodos especiales (IsNull, IsNotNull, AreEqual, etc.) uno para cada tipo de afirmación, porque podemos obtener una descripción completa de cualquier expresión y todos los valores en ella, cualquiera que sea la expresión que sea. –

+0

Earwicker, esa es una observación interesante. Me gusta, aunque no es una clavada y podría haber casos en los que adoptaría el enfoque de muchos métodos (IsNull, IsNotNull, etc.) O al menos debatiría conmigo mismo al respecto. El mensaje de error puede ser más bonito diciendo "FirstName no puede ser nulo" que "La aserción (FirstName! = Null) falló". Además, si tienes cientos de lugares donde declaras (¡algo! = Nulo), eso se siente como duplicación de código. No estoy seguro si es una duplicación dañina o no. Depende probablemente Pero observación genial, gracias. –

0

No se aplica en todas partes, pero podría ayudar en muchos casos:

supongo que "SomeMethod" está llevando a cabo alguna operación de comportamiento en los datos "apellidos", "nombre" y "edad" . Evalúa tu diseño de código actual. Si los tres datos están llorando por una clase, póngalos en una clase. En esa clase también puedes poner tus cheques. Esto liberaría "SomeMethod" de la comprobación de entrada.

El resultado final sería algo como esto:

public void SomeMethod(Person person) 
{ 
    person.CheckInvariants(); 
    // code here ... 
} 

La llamada sería algo como esto (si se utiliza .NET 3.5):

SomeMethod(new Person { FirstName = "Joe", LastName = "White", Age = 12 }); 

bajo el supuesto de que la clase lo haría mira esto:

public class Person 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 

    public void CheckInvariants() 
    { 
     assertNotNull(FirstName, "first name"); 
     assertNotNull(LastName, "last name"); 
    } 

    // here are your checks ... 
    private void assertNotNull(string input, string hint) 
    { 
     if (input == null) 
     { 
      string message = string.Format("The given {0} is null.", hint); 
      throw new ApplicationException(message); 
     } 
    } 

En lugar del azúcar sintáctico de .NET 3.5 también puedes usar el constructor argumentos para crear un objeto Persona.

0

Al igual que un contraste, this publicado por Miško Hevery en el Google Testing Blog sostiene que este tipo de comprobación de parámetros no siempre es una buena cosa. El debate resultante en los comentarios también plantea algunos puntos interesantes.

+0

Hay verdad en este punto de vista. No necesariamente estoy hablando de contratos de aplicación religiosa (creo que eso es bueno en algunos casos, pero no en todos). Pero aún así, algunos métodos necesitan realizar ciertas comprobaciones, y a menudo terminan queriendo ayudantes para esos casos. –

+0

Ciertamente, este tipo de comprobaciones son útiles en muchas situaciones, solo quería una opinión contrastante para las generaciones futuras :) –

2

El Lokad Shared Libraries también tiene una implementación basada en el análisis de IL que evita tener que duplicar el nombre del parámetro en una cadena.

Por ejemplo:

Enforce.Arguments(() => controller,() => viewManager,() => workspace); 

arrojará una excepción con el nombre de parámetro adecuado si alguno de los argumentos mencionados es nulo. También tiene una implementación de reglas realmente ordenada basada en políticas.

p. Ej.

Enforce.Argument(() => username, StringIs.Limited(3, 64), StringIs.ValidEmail); 
+0

Agradable. Gracias por señalarme allí. –

3

Aquí está mi respuesta al problema. Lo llamo "Guard Claws". Utiliza el analizador IL de los Lokad Liberaciones compartidos, pero tiene un enfoque más directo a enunciar las cláusulas de guardia reales:

string test = null; 
Claws.NotNull(() => test); 

se puede ver más ejemplos de su uso en el specs.

Dado que utiliza lambdas reales como entrada y usa el IL Parser solo para generar la excepción en el caso de una violación, debe funcionar mejor en la "ruta feliz" que los diseños basados ​​en Expression en otras partes de estas respuestas.

Los enlaces no están funcionando, aquí está la URL: http://github.com/littlebits/guard_claws/

+0

Cool. ¿Algún plan para usar params args para permitir que se verifiquen varias variables? –

Cuestiones relacionadas