exception - tipos - Manejo de excepciones: contrato vs enfoque excepcional
try catch c# ejemplo (5)
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.
Conozco dos enfoques para el manejo de excepciones, echemos un vistazo a ellos.
Enfoque de contrato.
Cuando un método no hace lo que dice que hará en el encabezado del método, emitirá 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.
Enfoque excepcional.
Solo lanza 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.
Usemos ambos enfoques 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 que un cliente no tenga dinero asumiendo que no ganó la lotería.
Pero aquí hay una situación en la que me equivoco sobre 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, maldita sea, espero una instancia de Car en lugar de un puntero nulo, que puede hacer estragos en mi código de ejecución una docena de líneas más tarde. 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?
Estoy a favor de lo que ustedes llaman el enfoque de "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 cláusulas "if (result == NULL)" o "if (result == -1)" mezcladas con lo que podría ser una lógica simple y directa.
Mi enfoque habitual es usar 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).
Cada error en los argumentos no se maneja. 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, de modo que si ocurre una excepción, probablemente sea FUBAR de todos modos.
Si en realidad le interesan las excepciones y desea pensar en cómo usarlas para construir sistemas sólidos, considere leer Hacer sistemas distribuidos confiables en presencia de errores de software .
Ambos enfoques son correctos. 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 ser excepcionales en función de lo que espera la persona que llama del código. Si la persona que llama está esperando que un diccionario contenga un determinado elemento, y la ausencia de ese elemento indicaría un problema grave, la falla en encontrar el artículo es una condición excepcional y debe provocar una excepción. 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 manera de manejar tales variaciones en la expectativa de la persona que llama es tener un contrato que especifique dos métodos: un método DoSomething y un método TryDoSomething, por ejemplo
TValue GetValue(TKey Key); bool TryGetValue(TKey Key, ref TValue value);
Tenga en cuenta que, si bien el patrón "probar" estándar es como se ilustra arriba, algunas alternativas también pueden ser útiles si se está diseñando una interfaz que produce elementos:
// 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 el uso de algo así como el patrón normal de 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 se use en una declaración ''var'':
var myThingResult = myThing.TryGetSomeValue(ref ok, whatever); if (ok) { do_whatever }
No es exactamente el enfoque estándar, pero en algunos casos las ventajas pueden justificarlo.