2009-10-29 8 views
9

** EDITAR: Hay varias opciones a continuación que funcionarían. Por favor vote/comente de acuerdo a sus puntos de vista sobre el asunto.Método Refactor con múltiples puntos de retorno

estoy trabajando en la limpieza y la adición de la funcionalidad de aC# método con la siguiente estructura básica:

public void processStuff() 
    { 
     Status returnStatus = Status.Success; 
     try 
     { 
      bool step1succeeded = performStep1(); 
      if (!step1succeeded) 
       return Status.Error; 

      bool step2suceeded = performStep2(); 
      if (!step2suceeded) 
       return Status.Warning; 

      //.. More steps, some of which could change returnStatus..// 

      bool step3succeeded = performStep3(); 
      if (!step3succeeded) 
       return Status.Error; 
     } 
     catch (Exception ex) 
     { 
      log(ex); 
      returnStatus = Status.Error; 
     } 
     finally 
     { 
      //some necessary cleanup 
     } 

     return returnStatus; 
    } 

Hay muchos pasos, y en la mayoría de los casos el paso x deben tener éxito con el fin de continuar con el paso x + 1. Ahora, necesito agregar alguna funcionalidad que siempre se ejecutará al final del método, pero que depende del valor de retorno. Estoy buscando recomendaciones sobre cómo refactorizar limpiamente esto para el efecto deseado. La elección obvia sería poner la funcionalidad que depende del valor de retorno en el código de llamada, pero no puedo modificar las llamadas.

Una opción:

public void processStuff() 
    { 
     Status returnStatus = Status.Success; 
     try 
     { 
      bool step1succeeded = performStep1(); 
      if (!step1succeeded) 
      { 
       returnStatus = Status.Error; 
       throw new Exception("Error"); 
      } 

      bool step2succeeded = performStep2(); 
      if (!step2succeeded) 
      { 
       returnStatus = Status.Warning; 
       throw new Exception("Warning"); 
      } 

      //.. the rest of the steps ..// 
     } 
     catch (Exception ex) 
     { 
      log(ex); 
     } 
     finally 
     { 
      //some necessary cleanup 
     } 
     FinalProcessing(returnStatus); 
     return returnStatus; 
    } 

Esto parece un poco feo para mí. En cambio, podría lanzar directamente desde los métodos performStepX(). Sin embargo, esto deja el problema de establecer la variable returnStatus apropiadamente en el bloque catch de processStuff(). Como habrá notado, el valor devuelto al fallar un paso de procesamiento depende del paso que falló.

public void processStuff() 
    { 
     Status returnStatus = Status.Success; 
     try 
     { 
      bool step1succeeded = performStep1(); //throws on failure 
      bool step2succeeded = performStep2(); //throws on failure 

      //.. the rest of the steps ..// 
     } 
     catch (Exception ex) 
     { 
      log(ex); 
      returnStatus = Status.Error; //This is wrong if step 2 fails! 
     } 
     finally 
     { 
      //some necessary cleanup 
     } 
     FinalProcessing(returnStatus); 
     return returnStatus; 
    } 

Agradecería cualquier sugerencia que pueda tener.

Respuesta

9

No utilice excepciones para controlar el flujo de su programa. Personalmente, dejaría el código tal como está. Para agregar la nueva funcionalidad al final, puede hacer:

public void processStuff() 
    { 
     Status returnStatus = Status.Success; 
     try 
     { 
      if (!performStep1()) 
       returnStatus = Status.Error; 
      else if (!performStep2()) 
       returnStatus = Status.Warning; 

      //.. More steps, some of which could change returnStatus..// 

      else if (!performStep3()) 
       returnStatus = Status.Error; 
     } 
     catch (Exception ex) 
     { 
      log(ex); 
      returnStatus = Status.Error; 
     } 
     finally 
     { 
      //some necessary cleanup 
     } 

     // Do your FinalProcessing(returnStatus); 

     return returnStatus; 
    } 
+0

Definitivamente estoy de acuerdo con la primera mitad de su respuesta. Aquí hay algunas buenas respuestas que mejorarían la legibilidad sin violar ese principio. – jheddings

+0

Tal como está, no se comporta como se requiere. Necesito hacer algo al final del método que depende del valor de retorno. Y, como digo, no puedo cambiar el código de llamada. – Odrade

+0

Lo siento @Odrade Olvidé que necesitabas cambiar la funcionalidad. Vea si mi respuesta actualizada lo ayuda. –

0

¿Puedes hacer performStep1 y performStep2 lanzando diferentes excepciones?

+0

Eso se me ocurrió. ¿Qué piensan los demás acerca de ese enfoque? – Odrade

+0

Lanzaría excepciones que tengan sentido. –

+0

¿Quiere decir lanzar excepciones con tipos personalizados, nombrados para reflejar claramente sus propósitos? – Odrade

3

Puede refactorizar los pasos en una interfaz, de modo que cada paso, en lugar de ser un método, expone un método Run() y una propiedad de Estado, y puede ejecutarlos en un bucle, hasta que llegue a una excepción . De esta forma, puede mantener la información completa sobre qué paso se ejecutó y qué estado tenía cada paso.

+0

Como una ligera modificación, el método Run() de la interfaz podría devolver verdadero o falso, permitiendo que el campo de estado sea consultado solo por pasos fallidos. La excepción podría arrojarse en el bucle si el estado es Error. Esto permite que el ciclo continúe en busca de Advertencias. – jheddings

+0

Esto realmente depende de si los pasos realmente pertenecen o no a sus propias clases. Crear clases para cada paso, crear una interfaz y luego hacer un seguimiento de todos los objetos podría volverse demasiado gravoso si este no es el caso. – jasonh

+0

@jasonh Es cierto, pero la gestión de los pasos y el orden de ejecución podrían manejarse usando fábricas. Si esta solución se implementa correctamente, incluso se pueden realizar pasos adicionales o cambiar el orden de ejecución sin cambiar el código y volver a compilar. –

1

Obtiene una clase de tupla. Después, realice:

var steps = new List<Tuple<Action, Status>>() { 
    Tuple.Create(performStep1, Status.Error), 
    Tuple.Create(performStep2, Status.Warning) 
}; 
var status = Status.Success; 
foreach (var item in steps) { 
    try { item.Item1(); } 
    catch (Exception ex) { 
    log(ex); 
    status = item.Item2; 
    break; 
    } 
} 
// "Finally" code here 

Oh sí, puede utilizar tipos anon de tuplas:

var steps = new [] {       
    { step = (Action)performStep1, status = Status.Error }, 
    { step = (Action)performStep2, status = Status.Error }, 
};           
var status = Status.Success   
foreach (var item in steps) {    
    try { item.step(); }      
    catch (Exception ex) {      
    log(ex);         
    status = item.status;      
    break;         
    }           
}           
// "Finally" code here      
+0

Esto no permitiría que la ejecución del código continúe en presencia de una advertencia en performStep1. – jheddings

+0

Además, el método no sigue el patrón que estoy mostrando% 100. Algunos pasos captan datos que deben pasarse a los pasos posteriores. – Odrade

+0

@jheddings como yo lo vi, cualquier excepción desencadena el final del proceso; solo el estado cambia. @odrade, bueno el original acaba de ejecutar performStep1/2/3() ...:) – MichaelGG

2

Puede realizar el procesamiento en su sección finally. finally es divertido, incluso si ha regresado en su bloque try, el bloque finally seguirá ejecutándose antes de que la función realmente regrese. Sin embargo, recordará qué valor se devolvió, por lo que también puede abandonar el return al final de su función.

public void processStuff() 
{ 
    Status returnStatus = Status.Success; 
    try 
    { 
     if (!performStep1()) 
      return returnStatus = Status.Error; 

     if (!performStep2()) 
      return returnStatus = Status.Warning; 

     //.. the rest of the steps ..// 
    } 
    catch (Exception ex) 
    { 
     log(ex); 
     return returnStatus = Status.Exception; 
    } 
    finally 
    { 
     //some necessary cleanup 

     FinalProcessing(returnStatus); 
    } 
} 
0

Puede invertir la configuración de su estado. Establezca el estado del error antes de llamar al método de lanzamiento de excepción. Al final, configúralo con éxito si no hay excepciones.

Status status = Status.Error; 
try { 
    var res1 = performStep1(); 
    status = Status.Warning; 
    performStep2(res1); 
    status = Status.Whatever 
    performStep3(); 
    status = Status.Success; // no exceptions thrown 
} catch (Exception ex) { 
    log(ex); 
} finally { 
// ... 
} 
+0

Y esto hace que el código sea más claro, ¿cómo? –

+0

"Fallo por defecto"? Esto le permite lanzar excepciones de cada paso como lo desee, mientras se asegura de que se establezca la falla para cada paso. Sin condicionales adicionales o más código. C# 's tan limitado cuando se trata de refactorizar a un nivel tan pequeño. – MichaelGG

-1

¿qué hay de anidado si?

podrían trabajar y no podía trabajar

que eliminaría todas retorno a dejar sólo una

if(performStep1()) 
{ 
    if(performStep2()) 
    { 
     //.......... 
    } 
    else 
    returnStatus = Status.Warning; 
} 
else 
    returnStatus = Status.Error; 
+1

Eso podría funcionar, pero conduciría a una monstruosidad profundamente anidada. – Odrade

0

Sin saber que gran parte de lo que estás son requisitos lógicos, me gustaría empezar con la creación de una clase abstracta para actuar como un objeto base para ejecutar un paso específico y devolver el estado de la ejecución. Debe tener métodos invalidables para implementar la ejecución de tareas, acciones en caso de éxito y acciones en caso de error. también se encargan de la lógica de poner la lógica en esta clase para manejar el éxito o el fracaso de la tarea:

abstract class TaskBase 
{ 
    public Status ExecuteTask() 
    { 
     if(ExecuteTaskInternal()) 
      return HandleSuccess(); 
     else 
      return HandleFailure(); 
    } 

    // overide this to implement the task execution logic 
    private virtual bool ExecuteTaskInternal() 
    { 
     return true; 
    } 

    // overide this to implement logic for successful completion 
    // and return the appropriate success code 
    private virtual Status HandleSuccess() 
    { 
     return Status.Success; 
    } 

    // overide this to implement the task execution logic 
    // and return the appropriate failure code 
    private virtual Status HandleFailure() 
    { 
     return Status.Error; 
    } 
} 

Después de crear clases de tareas a ejecutar sus pasos, añadirlos a una SortedList con el fin de la ejecución, a continuación, iterar a través de ellos el control de la staus cuando la tarea se ha completado:

public void processStuff() 
{ 
    Status returnStatus 
    SortedList<int, TaskBase> list = new SortedList<int, TaskBase>(); 
    // code or method call to load task list, sorted in order of execution. 
    try 
    { 
     foreach(KeyValuePair<int, TaskBase> task in list) 
     { 
      Status returnStatus task.Value.ExecuteTask(); 
      if(returnStatus != Status.Success) 
      { 
       break; 
      } 
     } 
    } 
    catch (Exception ex) 
    { 
     log(ex); 
     returnStatus = Status.Error; 
    } 
    finally 
    { 
     //some necessary cleanup 
    } 

    return returnStatus; 
} 

que quedan en el manejo de errores ya que no estoy seguro si sus errores de captura en la ejecución de tareas o simplemente buscando la excepción estabas tirando en un paso en particular defecto.

1

Ampliando el enfoque interfaz anterior:

public enum Status { OK, Error, Warning, Fatal } 

public interface IProcessStage { 
    String Error { get; } 
    Status Status { get; } 
    bool Run(); 
} 

public class SuccessfulStage : IProcessStage { 
    public String Error { get { return null; } } 
    public Status Status { get { return Status.OK; } } 
    public bool Run() { return performStep1(); } 
} 

public class WarningStage : IProcessStage { 
    public String Error { get { return "Warning"; } } 
    public Status Status { get { return Status.Warning; } } 
    public bool Run() { return performStep2(); } 
} 

public class ErrorStage : IProcessStage { 
    public String Error { get { return "Error"; } } 
    public Status Status { get { return Status.Error; } } 
    public bool Run() { return performStep3(); } 
} 

class Program { 
    static Status RunAll(List<IProcessStage> stages) { 
     Status stat = Status.OK; 
     foreach (IProcessStage stage in stages) { 
      if (stage.Run() == false) { 
       stat = stage.Status; 
       if (stat.Equals(Status.Error)) { 
        break; 
       } 
      } 
     } 

     // perform final processing 
     return stat; 
    } 

    static void Main(string[] args) { 
     List<IProcessStage> stages = new List<IProcessStage> { 
      new SuccessfulStage(), 
      new WarningStage(), 
      new ErrorStage() 
     }; 

     Status stat = Status.OK; 
     try { 
      stat = RunAll(stages); 
     } catch (Exception e) { 
      // log exception 
      stat = Status.Fatal; 
     } finally { 
      // cleanup 
     } 
    } 
} 
+0

Al ver los cambios para pasar datos entre los pasos de procesamiento, es posible que esto no funcione como está. Espero que te haga comenzar. – jheddings

0

te aconsejo en algunos refactorización de los pasos en clases separadas, después de todo, su clase debe sólo tienen una única responsabilidad de todos modos. Creo que parece deberse a la responsabilidad controlar el funcionamiento de los pasos.

Cuestiones relacionadas