try - ¿Reducir el código de manejo de error duplicado en C#?
try catch c (4)
Nunca he estado completamente satisfecho con la forma en que funciona el manejo de excepciones, hay muchas excepciones y trae try / catch a la mesa (desenrollado de la pila, etc.), pero parece que rompe gran parte del modelo de OO en el proceso.
De todos modos, aquí está el problema:
Digamos que tiene alguna clase que envuelve o incluye operaciones de IO de archivos en red (por ejemplo, leer y escribir en algún archivo en alguna ruta particular de UNC en alguna parte). Por diversas razones, no desea que esas operaciones de IO fallen, por lo que si detecta que fallan, vuelva a intentarlas y continúe intentándolo hasta que tengan éxito o llegue a un tiempo de espera excedido. Ya tengo una clase RetryTimer conveniente que puedo instanciar y usar para dormir el hilo actual entre reintentos y determinar cuándo ha transcurrido el tiempo de espera, etc.
El problema es que tiene varias operaciones de IO en varios métodos de esta clase, y necesita ajustar cada uno de ellos en la lógica de try-catch / retry.
Aquí hay un fragmento de código de ejemplo:
RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
try
{
// do some file IO which may succeed or fail
success = true;
}
catch (IOException e)
{
if (fileIORetryTimer.HasExceededRetryTimeout)
{
throw e;
}
fileIORetryTimer.SleepUntilNextRetry();
}
}
Entonces, ¿cómo evita duplicar la mayor parte de este código para cada operación de IO de archivos a lo largo de la clase? Mi solución fue usar bloques de delegados anónimos y un único método en la clase que ejecutó el bloque de delegados que se le pasó. Esto me permitió hacer cosas como esta en otros métodos:
this.RetryFileIO( delegate()
{
// some code block
} );
Me gusta algo así, pero deja mucho que desear. Me gustaría escuchar cómo otras personas resolverían este tipo de problema.
Esto es lo que hice recientemente. Probablemente se haya hecho mejor en otros lugares, pero parece bastante limpio y reutilizable.
Tengo un método de utilidad que se ve así:
public delegate void WorkMethod();
static public void DoAndRetry(WorkMethod wm, int maxRetries)
{
int curRetries = 0;
do
{
try
{
wm.Invoke();
return;
}
catch (Exception e)
{
curRetries++;
if (curRetries > maxRetries)
{
throw new Exception("Maximum retries reached", e);
}
}
} while (true);
}
Luego, en mi aplicación, uso la sintaxis de la expresión Lamda de c # para mantener las cosas ordenadas:
Utility.DoAndRetry( () => ie.GoTo(url), 5);
Esto llama a mi método y vuelve a intentarlo hasta 5 veces. En el quinto intento, la excepción original se vuelve a lanzar dentro de una excepción de reintento.
Esto parece una excelente oportunidad para echar un vistazo a la Programación Orientada a Aspectos. Aquí hay un buen artículo sobre AOP en .NET . La idea general es extraer la preocupación interfuncional (es decir, volver a intentar por x horas) en una clase separada y luego anotar cualquier método que necesite modificar su comportamiento de esa manera. Así es como se verá (con un buen método de extensión en Int32)
[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
//.. code to just delete the archive
}
Solo me pregunto, ¿qué sientes que tu método deja que desear? Podría reemplazar al delegado anónimo con un .. named? delegar, algo así como
public delegate void IoOperation(params string[] parameters);
public void FileDeleteOperation(params string[] fileName)
{
File.Delete(fileName[0]);
}
public void FileCopyOperation(params string[] fileNames)
{
File.Copy(fileNames[0], fileNames[1]);
}
public void RetryFileIO(IoOperation operation, params string[] parameters)
{
RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
try
{
operation(parameters);
success = true;
}
catch (IOException e)
{
if (fileIORetryTimer.HasExceededRetryTimeout)
{
throw;
}
fileIORetryTimer.SleepUntilNextRetry();
}
}
}
public void Foo()
{
this.RetryFileIO(FileDeleteOperation, "L:/file.to.delete" );
this.RetryFileIO(FileCopyOperation, "L:/file.to.copy.source", "L:/file.to.copy.destination" );
}
También podría usar un enfoque más OO:
- Cree una clase base que maneje el error y llame a un método abstracto para realizar el trabajo concreto. (Patrón de método de plantilla)
- Crea clases concretas para cada operación.
Esto tiene la ventaja de nombrar cada tipo de operación que realiza y le proporciona un patrón de comando: las operaciones se han representado como objetos.