¿Reduciendo el código de manejo de errores duplicados en C#?


Nunca he estado completamente satisfecho con la forma en que funciona el manejo de excepciones, hay muchas excepciones y try/catch trae a la mesa (desenrollar la pila, etc.), pero parece romper mucho del modelo 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 UNC en particular en algún lugar). Por varias razones, no desea que esas operaciones de E / s falla, por lo que si detecta que fallan, vuelve a intentarlo y sigue intentándolos hasta que tengan éxito o llegue a un tiempo de espera. 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 período de tiempo de espera, etc.

El problema es que tiene un montón de operaciones IO en varios métodos de esta clase, y necesita envolver cada uno de ellos en la lógica try-catch / retry.

Aquí hay un código de ejemplo fragmento de código:

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 evitar duplicar la mayor parte de este código para cada operación de E / S de archivo en toda la clase? Mi solución fue usar bloques delegados anónimos y un solo método en la clase que ejecutó el bloque delegado que se le pasó. Esto me permitió hacer cosas como esta en otros métodos:

this.RetryFileIO( delegate()
    {
        // some code block
    } );

Me gusta un poco, pero deja mucho que desear. Me gustaría escuchar cómo otras personas resolverían este tipo de problemas.

Author: Wedge, 2008-08-04

4 answers

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 que extraiga la preocupación multifuncional (es decir, Reintente durante 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 verse (con un buen método de extensión en Int32)

[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
  //.. code to just delete the archive
}
 13
Author: Mike Minutillo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-05 09:43:55

Solo me pregunto, ¿qué sientes que tu método deja que desear? Podría reemplazar el delegado anónimo con a.. ¿nombre? 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" );
    }
 4
Author: Chris Marasti-Georg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-04 20:07:41

También podría usar un enfoque más OO:

  • Cree una clase base que haga el manejo de errores y llame 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 cada tipo de operación que realice y le da un patrón de comando: las operaciones se han representado como objetos.

 2
Author: Andrew Peters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-07 11:30:38

Esto es lo que hice recientemente. Probablemente se ha hecho mejor en otros lugares, pero parece bastante limpio y reutilizable.

Tengo un método de utilidad que se parece a esto:

    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 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 replantea dentro de una excepción de reintento.

 2
Author: Andrej Kyselica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-13 02:25:02