2010-07-28 19 views
16

Estoy reuniendo un método que se supone que evalúa la entrada y devuelve verdadero si se cumplen todas las condiciones o es falso si alguna prueba falla. También me gustaría tener algún tipo de mensaje de estado disponible para la persona que llama si hay una falla.¿es una mejor práctica devolver un objeto complejo o utilizar parámetros de referencia/salida?

Diseños que he encontrado incluyen devolver el bool y utilizar un parámetro de salida (o ref) para el mensaje, devolver una instancia de una clase (específicamente diseñada) con las propiedades bool y string, o incluso devolver una enumeración pase o un error específico. ¿Cuál es la mejor manera de obtener toda la información del método? ¿Alguno de estos es "bueno"? ¿Alguien tiene otras recomendaciones?

Respuesta

13

Normalmente trato de devolver un objeto complejo y recurro a usar un parámetro out cuando tengo que hacerlo.

Pero observa los métodos TryParse en las conversiones de .NET, y siguen el patrón de devolver un bool y un parámetro de salida del valor convertido. Por lo tanto, no creo que sea malo tener parámetros fuera, realmente depende de lo que estás tratando de hacer.

+0

Me gusta la parte del objeto complejo, pero ¿cuándo 'tienes que' usar un parámetro out? –

+2

Me parece muy raro. Usualmente donde tengo que devolver dos tipos diferentes y prefiero no crear un tercer tipo para contener esos dos solo para los propósitos de la operación. Sé que suena vago, pero probablemente podría contar con las dos manos las veces que 'tuve que' hacer eso. Intento evitarlo si puedo. –

+0

Es posible que desee echar un vistazo a mi respuesta para ver mi discusión sobre cuándo los parámetros de salida tienen sentido. Tenga en cuenta que mi punto principal es que el uso de objetos complejos y el uso de parámetros de salida no son mutuamente excluyentes. –

1

Prefiero un objeto para parms principalmente porque puede hacer más comprobación de valores válidos en el "Conjunto" para las propiedades.

Esto a menudo se pasa por alto en las aplicaciones web, y una medida de seguridad básica.

Las directrices OWASP establecen que los valores deben validarse (utilizando la validación de la lista blanca) cada vez que se asignan. Si vas a utilizar estos valores en más de un lugar, es mucho más fácil crear un objeto o una estructura para contener los valores, y verificar allí, en lugar de verificar cada vez que tienes un código par afuera.

2

Supongo que depende de su definición de bueno.

Para mí, prefiero devolver el bool con el parámetro out. Creo que es más legible Seoncd a eso (y mejor en algunos casos) es el Enum. Como una opción personal, no me gusta devolver una clase solo para los datos del mensaje; abstrae lo que hago solo un nivel demasiado lejos para mi gusto.

4

Prefiero devolver directamente el tipo ya que algunos lenguajes .NET pueden no ser compatibles con los parámetros de ref y out.

Aquí hay un good explanation de MS de por qué.

+3

Eso es más importante para el software shrinkwrap donde se va a vender/acción, pero si el control de toda la pila por eso mismo con un tendón de la corva artificial restricción? YAGNI en este, gran momento. – mattmc3

+0

Incluso entonces, si su pila usa múltiples lenguajes .NET. Pero tienes razón: si sabes que nunca incluirás otro idioma, adelante. – ConsultUtah

2

Este tipo de cosas se representa fácilmente por Int32.Parse() y Int32.TryParse(). Para devolver un estado si falla, o un valor si no requiere que pueda devolver dos tipos diferentes, de ahí el parámetro out en TryParse(). Devolver un objeto especializado (en mi opinión) solo desordena su espacio de nombres con tipos innecesarios. Por supuesto, siempre podría arrojar una excepción con su mensaje dentro de él también. Personalmente, prefiero el método TryParse() porque puede verificar un estado sin tener que generar una excepción, que es bastante pesada.

+0

+1 'TryParse' es un buen ejemplo, pero creo que difiere ligeramente en que el' bool' devuelto es un indicador de seguridad para el valor real que desea, mientras que aquí me preocupa el valor de retorno real y el "mensaje "parte es secundaria. también puede complicarse si está en la situación de necesitar que se devuelvan muchos valores. – lincolnk

1

Esto puede ser subjetivo.

Pero como siempre, yo diría que depende mucho y que no hay una manera "correcta" de hacerlo. Por ejemplo, el patrón TryGet() utilizado por los diccionarios devuelve un bool (que a menudo se consume en un if de inmediato) y el tipo de devolución efectiva como out. Esto tiene mucho sentido en ese escenario.

Sin embargo, si enumera los artículos, obtiene KeyValuePair<,>, lo cual también tiene sentido ya que puede necesitar tanto la clave como el valor como un "paquete".

En su caso específico, que puede estar tentado a esperar una realidad bool como resultado y pasar un accesorio opcional (null permitido) ICollection<ErrorMessage> instancia que recibe los errores (ErrorMessageString sólo puede ser así si eso es suficiente). Esto tiene el beneficio de permitir el reporte de múltiples errores.

3

La devolución de objetos es más legible y requiere menos código. No hay diferencias de rendimiento, excepto el tuyo mientras estás saltando los aros que requieren los "parámetros de salida".

1

Uso las enumeraciones, pero úselas como un patrón de bandera/bit. De esa manera puedo indicar múltiples condiciones fallidas.

[Flags] 
enum FailState 
{ 
    OK = 0x1; //I do this instead of 0 so that I can differentiate between 
       // an enumeration that hasn't been set to anything 
       //and a true OK status. 
    FailMode1 = 0x2; 
    FailMode2 = 0x4; 
} 

Así que en mi método acabo de hacer esta

FailState fail; 

if (failCondition1) 
{ 
    fail |= FailState.FailMode1; 
} 
if (failCondition2) 
{ 
    fail |= FailState.FailMode2; 
} 

if (fail == 0x0) 
{ 
    fail = FailState.OK; 
} 

return fail; 

Lo molesto de este enfoque es que la determinación de si un bit se establece en la enumeración se parece a esto

if (FailState.FailMode1 == (FailState.FailMode1 && fail)) 
{ 
    //we know that we failed with FailMode1; 
} 

Utilizo métodos de extensión para darme un método IsFlagSet() en mis enumeraciones.

public static class EnumExtensions 
{ 
    private static void CheckIsEnum<T>(bool withFlags) 
    { 
     if (!typeof(T).IsEnum) 
      throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName)); 
     if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute))) 
      throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName)); 
    } 

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct 
    { 
     CheckIsEnum<T>(true); 
     long lValue = Convert.ToInt64(value); 
     long lFlag = Convert.ToInt64(flag); 
     return (lValue & lFlag) != 0; 
    } 
} 

he copiado el código de otra respuesta aquí en SO, pero no tengo ni idea de dónde respuesta es en este punto ...

Esto me permite sólo decir

if (fail.IsFlagSet(FailState.Ok)) 
{ 
    //we're ok 
} 
+0

gracias por el excelente ejemplo. esto parece un enfoque pesado, pero lo consideraría en una situación en la que quería ser más exhaustivo de lo que soy actualmente. – lincolnk

+0

Sí, parece que hay un montón de código, pero es una forma bastante liviana de indicar varias condiciones de falla ya que solo se trata de una enumeración. El PITA es el hecho de que no hay una manera simple de verificar si un bit está configurado en una enumeración, de ahí todo el crud extra para obtener el método 'IsSet' ... Realmente es exagerado si no necesita indicar múltiples condiciones fallidas , aunque. –

+3

Err .. ¿no es esto para lo que ** ** son las excepciones? –

2

Recomiendo encarecidamente que devuelva un objeto (complejo o no), porque sin duda puede saber en el futuro que debe devolver información adicional y si utilizó una combinación de un tipo de devolución simple con una referencia de salida o dos, estará atascado en la adición de parámetros adicionales que serán innecesarios Es necesario aglomerar la firma de su método.

Si utiliza un objeto, puede modificarlo fácilmente en el futuro para brindarle la información adicional que necesita.

Me mantendría alejado de las enumeraciones a menos que sea MUY simplista y no vaya a cambiar en el futuro. A la larga, tendrás menos dolores de cabeza y las cosas serán más simples si devuelves un objeto. El argumento de que saturará el espacio de nombres es débil si le das a los objetos de retorno su propio espacio de nombres, pero también a cada uno.

(Nota: si desea utilizar una enumeración, en pocas palabras que dentro de su objeto de regresar, esto le dará la simplicidad de enumeradores combinadas con la flexibilidad de un objeto capaz de lo que sea necesario.)

2

EDIT: estoy agregando un resumen de mi punto en la parte superior, para facilitar la lectura:

  • Si el método se vuelve múltiples piezas de datos que son lógicamente todos parte de la misma "cosa", entonces definitivamente componerlos en un objeto complejo independientemente de si está devolviendo ese objeto como el valor de retorno o un parámetro de salida.

  • Si su método necesita devolver algún tipo de estado (éxito/falla/cuántos registros se actualizaron/etc.) Además de los datos, y luego considerar regresar a sus datos como un parámetro de salida y utilizando el valor de retorno para volver al estado

Hay dos variaciones de las situaciones a las que se aplica esta pregunta:

  1. Necesidad de devolver los datos que consta de varios atributos

  2. Necesitando para devolver datos junto con un estado de la acción utilizado para obtener que los datos

Para el n. ° 1, mi opinión es que si tiene datos que constan de múltiples atributos que van todos juntos, entonces deben estar compuestos en un solo tipo como clase o estructura, y debe devolverse un solo objeto como el valor de retorno de un método o como un parámetro de salida.

Para el n. ° 2, creo que este es el caso donde los parámetros de salida realmente tienen sentido. Conceptualmente, los métodos típicamente realizan una acción; en este caso, prefiero usar el valor de retorno del método para indicar el estado de la acción. Eso podría ser un booleano simple para indicar el éxito o el fracaso, o podría ser algo más complicado, como una enumeración o una cadena, si existen múltiples estados posibles para describir la acción que realizó el método.

Si utilizo los parámetros de salida, recomendaría que una persona use solo uno (consulte el punto n. ° 1), a menos que haya una razón específica para usar más de uno. No use múltiples parámetros de salida simplemente porque los datos que necesita devolver consisten en múltiples atributos. Solo use múltiples parámetros de salida si la semántica de su método lo dicta específicamente. A continuación se muestra un ejemplo en el que creo múltiples parámetros de salida tienen sentido:

// an acceptable use of multiple output parameters 
    bool DetermineWinners(IEnumerable<Player> players, out Player first, out Player second, out Player third) 
    { 
     // ... 
    } 

Por el contrario, aquí se muestra un ejemplo en el que creo múltiples parámetros de salida no tienen sentido.

// Baaaaad 
    bool FindPerson(string firstName, string lastName, out int personId, out string address, out string phoneNumber) 
    { 
     // ... 
    } 

Los atributos de los datos (personId, dirección, y phoneNumber) debe ser parte de un objeto Person devuelto por el método. Una mejor versión sería la siguiente:

// better 
    bool FindPerson(string firstName, string lastName, out Person person) 
    { 
     // ... 
    } 
0

El método TryParse (...) no es el mejor ejemplo en este caso. TryParse solo señala si el número fue analizado o no, pero no especifica por qué falló el análisis. No es necesario porque la única razón por la que puede fallar se debe al "formato incorrecto".

Cuando se te ocurran situaciones en las que no hay una buena firma de método obvio para lo que estás tratando de hacer, deberías darte un paso atrás y preguntarte si el método tal vez está haciendo demasiado.

¿Qué pruebas está ejecutando? ¿Se relacionan en absoluto? ¿Hay pruebas en este mismo método que fallarían debido a razones completamente diferentes y no relacionadas que debe seguir para poder brindar una retroalimentación significativa al usuario/consumidor?

¿Refacturaría su código de ayuda? ¿Puede agrupar las pruebas lógicamente en métodos separados que solo pueden fallar debido a una razón (principio de propósito único) y llamarlos a todos según su sitio original en lugar del único método multipropósito que está considerando?

¿Es una prueba fallida una situación normal que solo necesita registrarse en algún archivo de registro o es obligatorio que informe al usuario de alguna manera impropia, probablemente deteniendo el flujo normal de la aplicación? Si es el último, ¿sería posible usar excepciones personalizadas y capturarlas en consecuencia?

Existen muchas posibilidades, pero necesitamos más información sobre lo que está tratando de hacer para brindarle consejos más precisos.

0

Hacemos algo muy similar con nuestros servicios WCF que utilizan objetos Request y Response, y las excepciones son burbujeadas hasta el final (no manejadas en el método) y manejadas por un atributo personalizado (escrito en PostSharp) que capta la excepción y devuelve una respuesta 'fracaso', por ejemplo:

[HandleException] 
public Response DoSomething(Request request) 
{ 
    ... 

    return new Response { Success = true }; 
} 

public class Response 
{ 
    ... 
    public bool Success { get; set; } 
    public string StatusMessage { get; set; } 
    public int? ErrorCode { get; set; } 
} 

public class Request 
{ 
    ... 
} 

la lógica de manejo de excepciones en el atributo puede utilizar el mensaje de excepción para establecer el StatusMessage y si usted tiene un conjunto de excepciones personalizadas en su aplicación incluso se puede establecer el ErrorCode cuando sea posible.

0

Creo que depende del nivel de complejidad y de si la operación podría fallar, lo que da como resultado ... ¿qué?

En el caso de los TryParse ejemplos, normalmente no puede devolver un valor no válido (por ejemplo, no hay tal cosa como un "no válido" int) y definitivamente no puede devolver un tipo de valor nulo (los Parse métodos correspondientes no vuelven Nullable<T>). Además, devolver Nullable<T> requeriría la introducción de una variable Nullable<T> en el sitio de la llamada, una comprobación condicional para ver si el valor es null, seguido de un molde al tipo T, que tiene que duplicar la verificación del tiempo de ejecución para ver si el valor es nulo antes de devolver ese valor real no nulo. Esto es menos eficiente que pasar el resultado como un parámetro out e indicar a través del valor de retorno el éxito o el fracaso (obviamente, en este caso, no está interesado por qué falló, solo que hizo )). En los tiempos de ejecución .Net anteriores, la única opción eran los métodos Parse, que arrojaban cualquier tipo de falla. La captura de excepciones de .Net es costosa, especialmente en relación con la devolución de un indicador true/false que indica el estado.

En términos de su propio diseño de interfaz, debe tener en cuenta por qué necesita un parámetro de salida. Tal vez necesite devolver múltiples valores de una función y no pueda usar tipos anónimos. Quizás necesite devolver un tipo de valor, pero no puede generar un valor predeterminado adecuado según las entradas.

out y ref los parámetros no son no-nos, pero su interfaz debe ser considerada cuidadosamente en cuanto a si son necesarios o no, antes de usarlos. Un retorno estándar por referencia debería ser suficiente en más del 99% de los casos, pero consideraría usar los parámetros out o ref antes de definir innecesariamente un nuevo tipo solo para satisfacer la devolución de un objeto "único" en lugar de valores múltiples.

0

"Estoy armando un método que se supone para evaluar la entrada y retorno cierto si se cumplen todas las condiciones o falso si alguna prueba falla. Yo también gustaría tener algún tipo de estado mensaje disponible para la persona que llama si hay una falla."

Si su método tiene condiciones con respecto a la entrada y si no se cumplen, puede ser muy habladores considera lanzar excepciones apropiadas. Al arrojar excepciones adecuadas la persona que llama sabrá qué fue exactamente equivocado. Esto es más maintainble y enfoque escalable. Devolver el código de error no es una buena opción por razones obvias.

"Los diseños con los que me he encontrado incluyen devolver el bool y usar un parámetro de salida (o ref) para el mensaje, devolviendo una instancia de (específicamente diseñada) class con las propiedades bool y string, o incluso devolver un enum que indica pase o un error específico. "

  1. objetos complejos son por supuesto la opción más obvia (clase o estructura)

  2. Out parameter:." Una manera de pensar de parámetros out es que son como valores de retorno adicionales de un método. Son muy convenientes cuando un método devuelve más de un valor, en este ejemplo firstName y lastName. Sin embargo, se pueden abusar de los parámetros. Como una cuestión de buen estilo de programación, si te encuentras escribiendo un método con muchos parámetros, entonces deberías pensar en refactorizar tu código. Una posible solución es envasar todos los valores de retorno en una sola estructura " Demasiados cabo o ref puntos de parámetros a un defecto de diseño

  3. Tuple:.". Agrupar un montón de datos no relacionadas de alguna estructura que es más ligero que una clase"

deben leer: More on tuples:

Cuestiones relacionadas