2008-08-04 24 views
32

Nunca he estado del todo contento con la forma en que funciona el manejo de excepciones, hay muchas excepciones y try/catch trae a la mesa (desenrollado de pila, etc.), pero parece romper mucho el modelo OO en el proceso .¿Reducir el código de manejo de error duplicado en C#?

De todos modos, aquí está el problema:

Digamos que usted tiene un poco de clase que envuelve o incluye las operaciones de IO de archivos en red (por ejemplo, la lectura y la escritura en algún archivo en algún ruta UNC en particular en algún lugar). Por diversas razones, no desea que esas operaciones de IO fallen, por lo que si detecta que fallan, las vuelve a intentar y las vuelve a intentar 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 usted tiene un montón de operaciones IO en varios métodos de esta clase, y necesita envolver a cada uno de ellos en la lógica try-catch/retry.

He aquí 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(); 
    } 
} 

Así que, ¿cómo evitar la duplicación de la mayor parte de este código para cada operación IO archivo durante 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 esto de alguna manera, pero deja mucho que desear. Me gustaría escuchar cómo otras personas resolverían este tipo de problema.

+1

Solo un FYI general: es [casi * siempre * mejor] (http://philosopherdeveloper.wordpress.com/2010/05/05/re-throwing-caught-exceptions/) simplemente 'throw;' en su lugar de 'throw e;' –

Respuesta

13

Esto se ve como una excelente oportunidad para echar un vistazo a programación orientada a aspectos. Aquí hay un buen artículo en AOP in .NET. La idea general es que extraiga la preocupación multifuncional (es decir, vuelva a intentar por x horas) en una clase separada y luego anote cualquier método que necesite modificar su comportamiento de esa manera. Así es como podría ser (con un método de extensión agradable en Int32)

[RetryFor(10.Hours())] 
public void DeleteArchive() 
{ 
    //.. code to just delete the archive 
} 
4

Solo me pregunto, ¿qué sientes que tu método deja que desear? Podría reemplazar al delegado anónimo con un .. named? delegado, 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"); 
    } 
2

También es posible usar un enfoque más orientado a objetos:

  • crear una clase base que hace el manejo de errores y llama a un método abstracto para realizar el trabajo concreto. (Patrón de método de plantilla)
  • Cree clases concretas para cada operación.

Esto tiene la ventaja de nombrar a cada tipo de operación a realizar y le da un patrón de comando - operaciones han sido representados como objetos.

2

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 tiene este aspecto:

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 solicitud, yo uso la sintaxis de expresiones Lamda C# 's para mantener las cosas ordenadas:

Utility.DoAndRetry(() => ie.GoTo(url), 5); 

Esta llama mi método y reintentos hasta 5 veces En el quinto intento, la excepción original se vuelve a lanzar dentro de una excepción de reintento.

+0

¿Pero por qué el delegado personalizado 'WorkMethod' en lugar de' Action'? –

Cuestiones relacionadas