2011-11-14 11 views
8

Estoy escribiendo una API que se conecta a un servicio que devuelve un simple mensaje de "éxito" o uno de más de 100 diferentes sabores de falla.¿La mejor manera de práctica para indicar que una solicitud del servidor ha fallado?

Originalmente pensé en escribir el método que envía una solicitud a este servicio, de modo que si tuviera éxito el método no devuelve nada, pero si falla por alguna razón, arroja una excepción.

No me importó mucho este diseño, pero por el otro lado hoy estaba leyendo "How to Design a Good API and Why it Matters" de Joshua Bloch, donde dice "Lanzar excepciones para indicar condiciones excepcionales ... No fuerce al cliente a usar excepciones para el flujo de control ". (y "Por el contrario, no fallar en silencio.")

Por otro lado, noté que la HttpWebRequest que estoy usando parece arrojar una excepción cuando la solicitud falla, en lugar de devolver una respuesta que contiene un " Mensaje de 500 Internal Server Error ".

¿Cuál es el mejor patrón para informar errores en este caso? Si lanzo una excepción en cada solicitud fallida, ¿estoy sufriendo un dolor masivo en algún momento en el futuro?

Editar: Muchas gracias por las respuestas hasta ahora. Algunas elaboraciones:

  • es una DLL que se les dará a los clientes para que hagan referencia en su aplicación.
  • un ejemplo análogo del uso sería ChargeCreditCard(CreditCardInfo i) - obviamente, cuando falla el método ChargeCreditCard(), es un gran problema; Simplemente no estoy 100% seguro de si debería detener las prensas o pasarle esa responsabilidad al cliente.

Editar la Segunda: Básicamente no estoy totalmente convencido de cuál de estos dos métodos de empleo:

try { 
ChargeCreditCard(cardNumber, expDate, hugeAmountOMoney); 
} catch(ChargeFailException e) { 
    // client handles error depending on type of failure as specified by specific type of exception 
} 

o

var status = TryChargeCreditCard(cardNumber, expDate, hugeAmountOMoney); 

if(!status.wasSuccessful) { 
    // client handles error depending on type of failure as specified in status 
} 

por ejemplo, cuando un usuario intenta cargar una tarjeta de crédito, ¿se rechaza realmente la tarjeta como una circunstancia excepcional ? ¿Estoy yendo demasiado lejos en el agujero del conejo al hacer esta pregunta en primer lugar?

+1

"excepciones, excepciones, excepciones!" (* Ballmer dance *) –

+1

¿Esto será RESTO, o JABÓN, o ambos? –

+0

La 'WebException' lanzada por' WebRequest's 'GetResponse()' contiene 'WebResponse' si es _valid_ con respecto al formato de los protocolos. Entonces, para usted, es mantener el diseño de las clases 'BCL' y construir algo similar o ... no. ¿Cuál es el objetivo de tu aplicación? ¿Es esencial que la solicitud del servidor tenga éxito? Entonces tira. ¿O es simplemente bueno tener datos adicionales? – ordag

Respuesta

6

Aquí hay una breve lista de cosas que debe considerar. Aunque no es exhaustivo, creo que estas cosas pueden ayudarte a escribir un mejor código. En pocas palabras: no necesariamente perciben el manejo de excepciones como malvado. En cambio, al escribirlos, pregúntese: ¿Cuán bien entiendo el problema que estoy resolviendo el realmente? En la mayoría de los casos, esto lo ayudará a convertirse en un mejor desarrollador.

  1. ¿Podrán otros desarrolladores ser capaces de leer esto? ¿Puede ser razonablemente entendido por el desarrollador promedio? Ejemplo: ServiceConnectionException frente a un confuso ServiceDisconnectedConnectionStatusException
  2. En el caso de arrojar una excepción, ¿cómo excepcional es la circunstancia? ¿Qué tiene que hacer la persona que llama para implementar el método?
  3. ¿Es esta excepción fatal? ¿Se puede hacer algo con esta excepción si se detecta? Subprocesos abortados, sin memoria ... no puede hacer nada útil. No lo atrapes.
  4. ¿Es la excepción confusa? Supongamos que tiene un método llamado Car GetCarFromBigString(string desc) que toma una cadena y devuelve un objeto Car. Si la mayoría de los casos de uso para ese método es generar un objeto Car a partir de ese string, no arroje una excepción cuando un Car no se pudo determinar a partir del string. En su lugar, escriba un método como bool TryGetCarFromBigString(string desc, out Car).
  5. ¿Se puede evitar esto fácilmente? ¿Puedo verificar algo, digamos que el tamaño de una matriz o una variable es nulo?

Por razones de legibilidad del código, echemos un vistazo a su contexto.

bool IsServiceAlive() 
{ 
    bool connected = false; //bool is always initialized to false, but for readability in this context 

    try 
    { 
     //Some check 
     Service.Connect(); 
     connected = true; 
    } 
    catch (CouldNotConnectToSomeServiceException) 
    { 
     //Do what you need to do 
    } 

    return connected; 
} 

//or 
void IsServiceAlive() 
{ 
    try 
    { 
     //Some check 
     Service.Connect(); 
    } 
    catch (CouldNotConnectToSomeServiceException) 
    { 
     //Do what you need to do 
     throw; 
    } 
} 



static void Main(string[] args) 
{ 
    //sample 1 
    if (IsServiceAlive()) 
    { 
     //do something 
    } 

    //sample 2 
    try 
    { 
     if (IsServiceAlive()) 
     { 
      //do something 
     } 
    } 
    catch (CouldNotConnectToSomeServiceException) 
    { 
     //handle here 
    } 

    //sample 3 
    try 
    { 
     IsServiceAlive(); 
     //work 
    } 
    catch (CouldNotConnectToSomeServiceException) 
    { 
     //handle here 
    } 
} 

se puede ver arriba, que la captura de los CouldNotConnectToSomeServiceException en la muestra 3 no producirá necesariamente mejor legibilidad si el contexto es simplemente una prueba binaria. Sin embargo, ambos funcionan. Pero ¿es realmente necesario? ¿Su manguera está humedecida si no puede conectarse? ¿Qué tan crítico es realmente? Todos estos son factores que deberá tener en cuenta. Es difícil de decir ya que no tenemos acceso a todo tu código.

Echemos un vistazo a algunas otras opciones que muy probablemente causen problemas.

//how will the code look when you have to do 50 string comparisons? Not pretty or scalable. 
public class ServiceConnectionStatus 
{ 
    public string Description { get; set; } 
} 

y

//how will your code look after adding 50 more of these? 
public enum ServiceConnectionStatus 
{ 
    Success, 
    Failure, 
    LightningStormAtDataCenter, 
    UniverseExploded 
} 
3

creo que es necesario tener en cuenta algunas cosas en su diseño:

1) ¿Cómo se accede a la API? Si lo expone a través de servicios web, entonces lanzar excepciones probablemente no sea una buena idea. Si la API está en una DLL que está proporcionando para que las personas hagan referencia en sus aplicaciones, entonces las excepciones pueden estar bien.

2) ¿Cuántos datos adicionales deben viajar con el valor de retorno para que la respuesta de falla sea útil para el consumidor API? Si necesita proporcionar información utilizable en su mensaje de error (es decir, identificación de usuario e inicio de sesión) en oposición a una cadena con esa información incrustada, entonces podría utilizar excepciones personalizadas o una clase "ErrorEncountered" que contiene el código de error y otra información utilizable . Si solo necesita pasar un código de vuelta, entonces un ENum que indica ya sea éxito (0) o falla (cualquier valor distinto de cero) puede ser apropiado.

3) Se olvidó de esto en la respuesta original: las excepciones son caras en .Net Framework. Si su API será llamada de vez en cuando, esto no necesita tener en cuenta.Sin embargo, si se llama a la API para cada página web que se sirve en un sitio de alto tráfico, por ejemplo, definitivamente no desea lanzar excepciones para indicar una falla en la solicitud.

Por lo tanto, la respuesta corta es que realmente depende de las circunstancias exactas.

2

Me gusta mucho la idea de "Lanzar excepciones para indicar condiciones excepcionales". Deben tener ese nombre por una razón.

En una aplicación normal, debe usar File.Exists() antes de File.Open() para evitar que se produzca una excepción. Los errores esperados como excepciones son difíciles de manejar.

Sin embargo, en un entorno cliente-servidor, es posible que desee evitar tener que enviar dos solicitudes y crear una clase FileOpenResponse para enviar tanto el estado como los datos (como un identificador de archivo, en este caso).

+2

La otra cara de "las excepciones son excepcionales" (a partir de la documentación de MSDN y las directrices de diseño de .NET framework): "Las excepciones son el mecanismo estándar para informar errores". También: http://blogs.msdn.com/b/kcwalina/archive/2008/07/17/exceptionalerror.aspx –

+0

Aunque una aplicación puede manejar diferentes tipos de excepciones de manera diferente, prefiero ver una API con un 'TryAlloc() 'método que uno con' Alloc() 'lanzando una' OutOfMemoryException' (mirando el ejemplo en el enlace). Sin embargo, sería una tontería no reconocer la experiencia de las personas que escriben estas pautas. Al menos me hizo pensar de nuevo :) –

Cuestiones relacionadas