2008-10-08 5 views
8

Esta pregunta es una continuación de How to indicate that a method was unsuccessful. El patrón xxx() Tryxxx() es algo que puede ser muy útil en muchas bibliotecas. Me pregunto cuál es la mejor manera de ofrecer ambas implementaciones sin duplicar mi código.patrón "TryParse/Parse like": cuál es la mejor manera de implementarlo

lo que es mejor:

public int DoSomething(string a) 
{ 
    // might throw an exception 
} 
public bool TrySomething(string a, out result) 
{ 
    try 
    { 
     result = DoSomething(a) 
     return true; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 

o

public int DoSomething(string a) 
{ 
    int result; 
    if (TrySomething(a, out result)) 
    { 
     return result; 
    } 
    else 
    { 
     throw Exception(); // which exception? 
    } 
} 
public bool TrySomething(string a, out result) 
{ 
    //... 
} 

Me instintivamente por hecho que el primer ejemplo es más correcto (usted sabe exactamente lo que sucedió excepción), pero se le fue el intento/¿La captura es demasiado cara? ¿Hay alguna forma de detectar la excepción en el segundo ejemplo?

+0

Estoy de acuerdo con su instinto (evite la excepción para el caso TryX si es posible) siempre que su implementación de TryX no tenga que atrapar una excepción (es decir, la excepción está siendo generada por su código, no el código llama). –

+0

Además, no estoy seguro de lo que quiere decir con "¿Hay alguna manera de detectar la excepción en el segundo ejemplo?" - atrapa qué excepción?Desea lanzar desde DoSomething (aunque lanzaría una excepción específica de lo sucedido, no la general). –

+0

@Jonathan: Quise decir "volver a lanzar la excepción que ocurrió en el procesamiento interno de una manera que la persona que llama puede saber qué causó el error" – Luk

Respuesta

13

Haciendo TrySomething acaba de coger y tragar la excepción es una muy mala idea. La mitad del punto del patrón TryXXX es evitar el golpe de rendimiento de las excepciones.

Si no necesita mucha información en la excepción, puede hacer que el método DoSomething solo llame a TrySomething y lanzar una excepción si falla. Si necesita detalles en la excepción, puede necesitar algo más elaborado. No he calculado el momento en que la mayor parte del rendimiento de las excepciones es - si es el lanzamiento en lugar de la creación, puede escribir un método privado que tiene una firma similar a TrySomething, pero que arrojó una excepción o nulo:

public int DoSomething(string input) 
{ 
    int ret; 
    Exception exception = DoSomethingImpl(input, out ret); 
    if (exception != null) 
    { 
     // Note that you'll lose stack trace accuracy here 
     throw exception; 
    } 
    return ret; 
} 

public bool TrySomething(string input, out int ret) 
{ 
    Exception exception = DoSomethingImpl(input, out ret); 
    return exception == null; 
} 

private Exception DoSomethingImpl(string input, out int ret) 
{ 
    ret = 0; 
    if (input != "bad") 
    { 
     ret = 5; 
     return null; 
    } 
    else 
    { 
     return new ArgumentException("Some details"); 
    } 
} 

¡Sin embargo, antes de comprometerse con esto!

+0

Me gusta esta implementación, probablemente la probaré más cuando pueda – Luk

2

El primer ejemplo es correcto si solo va a atrapar la excepción y no hace nada más que devolver falso con ella.

Puede cambiar TrySomething para que se vea como a continuación.

public bool TrySomething(string a, out result, bool throwException) 
{ 
    try 
    { 
    // Whatever 
    } 
    catch 
    { 
    if(throwException) 
    { 
     throw; 
    } 
    else 
    { 
     return false; 
    } 
    } 

} 

public bool TrySomething(string a, out result) 
{ 
    return TrySomething(a, out result, false); 
} 

Así HacerAlgo se vería

public int DoSomething(string a) 
{ 
    int result; 

    // This will throw the execption or 
    // change to false to not, or don't use the overloaded one. 
    TrySomething(a, out result, true) 

    return result;  
} 

Si no desea TrySomething con ThrowException expuesta al público se puede hacer un miembro privado.

Las excepciones pueden ser costosas y usted puede hacer algunas comprobaciones de RegEx en la cadena para evitar que una sea lanzada. Depende de lo que estés tratando de hacer.

+0

No tendría la versión con el argumento "throwException" como pública; esto ya está implícito en su elección de llamar TrySomething vs Something. Sin embargo, eso podría estar bien como método privado que se encuentra detrás de ambas implementaciones públicas. –

+0

No es bueno tener argumentos como bool throwException en los métodos públicos: consulte las Pautas de diseño de .NET Framework. Además, no es bueno tragarse excepciones :) – nightcoder

1

Suponiendo que esta es C#, yo diría que el segundo ejemplo

public bool TrySomething(string a, out result) 
{ 
    try 
    { 
     result = DoSomething(a) 
     return true; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

que imita el construido en int.TryParse(string s, out int result), y en mi opinión, es mejor permanecer consistente con el lenguaje/entorno.

+2

Es coherente con la interfaz, pero no con la intención de evitar el impacto en el rendimiento de las excepciones. También incorrectamente (IMO) se traga * todas * excepciones, en lugar de aquellas que DoSomething normalmente arroja. Por ejemplo, no me gustaría que se trague OutOfMemoryException. –

3

Normalmente uso este patrón. Depende de cómo se implementa el método interno en cuanto a si esto tiene sentido o no. Si usted tiene que utilizar bloques catch condicional que puede ser un poco desagradable ...

public object DoSomething(object input){ 
    return DoSomethingInternal(input, true); 
} 

public bool TryDoSomething(object input, out object result){ 
    result = DoSomethingInternal(input, false); 
    return result != null; 
} 

private object DoSomethingInternal(object input, bool throwOnError){ 
    /* do your work here; only throw if you cannot proceed and throwOnError is true */ 
} 
+0

Sí, pero eso significa que DoSomethingInternal nunca debe lanzar una excepción si throwOnError es falso, lo que podría hacer que sea costoso de implementar, ¿no? – Luk

+0

Como dije, puede ser desagradable, dependiendo de la implementación del método interno. En otras palabras, si no está en la parte inferior de la pila de llamadas y llama a métodos que pueden arrojar excepciones, entonces puede que no sea el mejor patrón. – Will

+0

La mayoría de las veces, sin embargo, encontrará que puede evitar arrojar excepciones que podrían haber sido arrojadas usando los otros ejemplos aquí, lo que le ahorrará tiempo de desenrollado de la pila. – Will

Cuestiones relacionadas