2008-08-25 5 views
11

Conozco dos enfoques para el manejo de excepciones, echemos un vistazo a ellos.Manejo de excepciones: contrato vs enfoque excepcional

  1. Enfoque de contrato.

    Cuando un método no hace lo que dice que hará en el encabezado del método, generará una excepción. Por lo tanto, el método "promete" que hará la operación, y si falla por alguna razón, arrojará una excepción.

  2. Enfoque excepcional.

    Solo arroje excepciones cuando ocurra algo verdaderamente extraño. No debe usar excepciones cuando puede resolver la situación con flujo de control normal (sentencias If). No utiliza Excepciones para el flujo de control, como podría hacer en el enfoque por contrato.

Permite utilizar ambos métodos en diferentes casos:

Tenemos una clase de cliente que tiene un método llamado OrderProduct.

enfoque de contrato:

class Customer 
{ 
    public void OrderProduct(Product product) 
    { 
      if((m_credit - product.Price) < 0) 
        throw new NoCreditException("Not enough credit!"); 
      // do stuff 
    } 
} 

enfoque excepcional:

class Customer 
{ 
    public bool OrderProduct(Product product) 
    { 
      if((m_credit - product.Price) < 0) 
        return false; 
      // do stuff 
      return true; 
    } 
} 

if !(customer.OrderProduct(product)) 
      Console.WriteLine("Not enough credit!"); 
else 
    // go on with your life 

Aquí prefiero el enfoque excepcional, ya que no es realmente excepcional de que un cliente no tiene dinero suponiendo que no ganó la lotería .

Pero aquí hay una situación en la que me equivoco en el estilo del contrato.

excepcional:

class CarController 
{ 
    // returns null if car creation failed. 
    public Car CreateCar(string model) 
    { 
     // something went wrong, wrong model 
     return null; 
    } 
} 

Cuando llamo a un método llamado CreateCar, me da la Wel esperan una instancia de coches en lugar de algún puntero nulo pésimo, que puede devastar mi código en ejecución de una docena de líneas más adelante. Por lo tanto, prefiero contratar a este:

class CarController 
{ 

    public Car CreateCar(string model) 
    { 
     // something went wrong, wrong model 
     throw new CarModelNotKnownException("Model unkown"); 

     return new Car(); 
    } 
} 

¿Qué estilo usas? ¿Cuál crees que es el mejor enfoque general para las Excepciones?

Respuesta

7

estoy a favor de lo que se llama el enfoque "contrato". No es necesario devolver valores nulos u otros valores especiales para indicar errores en un idioma que admita excepciones. Me resulta mucho más fácil de entender el código cuando no tiene un montón de "si (resultado == NULL)" o "si (resultado == -1)" cláusulas mezclados con lo que podría ser la lógica muy simple y directa.

+0

1 para su ejemplo de devolver un nulo o un resultado -1. En lugar de hacer que el código sea más claro, el retorno nulo o -1 obliga al escritor del método de invocación a saber cómo el método invocado elige informar un resultado no estándar. La excepción es un enfoque de contrato más limpio en el que el invocador puede tratar el invocado como una caja negra (posiblemente) cambiante. Todo el invocador tiene que saber que tendrá que manejar una excepción, no es que sea un nulo hoy y un Integer.MIN_VALUE mañana. – rajah9

0

Creo que si está creando una clase que será utilizada por un programa externo (o será reutilizada por otros programas), entonces debe usar el enfoque de contrato. Un buen ejemplo de esto es una API de cualquier tipo.

1

Mi enfoque habitual es utilizar el contrato para manejar cualquier tipo de error debido a la invocación del "cliente", es decir, debido a un error externo (es decir, ArgumentNullException).

No se manejan todos los errores en los argumentos. Se genera una excepción y el "cliente" se encarga de manejarlo. Por otro lado, para los errores internos, siempre intente corregirlos (como si no pudiera obtener una conexión a la base de datos por algún motivo) y solo si no puede manejarlos resucite la excepción.

Es importante tener en cuenta que la mayoría de las excepciones no manejadas en dicho nivel no podrán ser manejadas por el cliente de todos modos, por lo que probablemente se dirijan al manejador de excepción más general, por lo que si ocurre una excepción, usted probablemente FUBAR de todos modos.

0

Ambos enfoques tienen razón. Lo que eso significa es que un contrato debe escribirse de tal manera que se especifique para todos los casos que no son verdaderamente excepcionales, un comportamiento que no requiera lanzar una excepción.

Tenga en cuenta que algunas situaciones pueden o no pueden ser excepcionales en base a lo que la persona que llama el código de espera. Si la persona que llama está a la espera de que un diccionario contiene un determinado tema, y ​​la ausencia de ese elemento que indicaría un problema grave, entonces el fracaso para encontrar el artículo es una condición excepcional y debe provocar una excepción a ser lanzado. Sin embargo, si la persona que llama no sabe realmente si existe un artículo, y está igualmente preparado para manejar su presencia o su ausencia, entonces la ausencia del artículo sería una condición esperada y no debería causar una excepción. La mejor forma de manejar dichas variaciones en la expectativa de la persona que llama es que el contrato especifique dos métodos: un método DoSomething y un método TryDoSomething, p.

 
TValue GetValue(TKey Key); 
bool TryGetValue(TKey Key, ref TValue value); 

Tenga en cuenta que, mientras que el patrón estándar 'try' es como se indica más arriba, algunas alternativas también pueden ser útiles si uno está diseñando una interfaz que produce artículos:

 
// In case of failure, set ok false and return default<TValue>. 
TValue TryGetResult(ref bool ok, TParam param); 
// In case of failure, indicate particular problem in GetKeyErrorInfo 
// and return default<TValue>. 
TValue TryGetResult(ref GetKeyErrorInfo errorInfo, ref TParam param); 

Tenga en cuenta que usar algo como el patrón normal TryGetResult dentro de una interfaz hará que la interfaz sea invariable con respecto al tipo de resultado; el uso de uno de los patrones anteriores permitirá que la interfaz sea covariante con respecto al tipo de resultado. Además, permitirá que el resultado que se utilizará en una declaración 'var':

 
    var myThingResult = myThing.TryGetSomeValue(ref ok, whatever); 
    if (ok) { do_whatever } 

No es el enfoque estándar, pero en algunos casos las ventajas puede justificarlo.