2009-10-29 11 views
9

** MODIFICA: Ci sono diverse opzioni che potrebbero funzionare. Si prega di votare/commentare in base alle proprie opinioni in merito.Metodo di rifasamento con più punti di ritorno

sto lavorando sulla pulizia e l'aggiunta di funzionalità per il metodo aC# con la seguente struttura di base:

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; 
    } 

Ci sono molti passi, e nella maggior parte dei casi passo x devono avere successo, al fine di procedere al passaggio x + 1. Ora, ho bisogno di aggiungere alcune funzionalità che verranno sempre eseguite alla fine del metodo, ma che dipende dal valore restituito. Sto cercando dei consigli su come rifarlo in modo pulito per ottenere l'effetto desiderato. La scelta più ovvia sarebbe quella di mettere la funzionalità che dipende dal valore restituito nel codice chiamante, ma non sono in grado di modificare i chiamanti.

Una possibilità:

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; 
    } 

Questo sembra un po 'brutto per me. Invece, potrei buttare direttamente dai metodi performStepX(). Tuttavia, questo lascia il problema di impostare la variabile returnStatus in modo appropriato nel blocco catch di processStuff(). Come avrai notato, il valore restituito in caso di fallimento di una fase di elaborazione dipende da quale fase è fallita.

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; 
    } 

Apprezzerei qualsiasi suggerimento che potreste avere.

risposta

9

Non utilizzare eccezioni per controllare il flusso del programma. Personalmente, lascerei il codice così com'è. Per aggiungere la nuova funzionalità alla fine è possibile:

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

Sono decisamente d'accordo con la prima metà della risposta. Ci sono alcune buone risposte qui che migliorerebbero la leggibilità senza violare quel principio. – jheddings

+0

Così com'è, non si comporta come richiesto. Ho bisogno di fare qualcosa alla fine del metodo che dipende dal valore di ritorno. E, come ho detto, non posso cambiare il codice chiamante. – Odrade

+0

Mi dispiace @Odrade Ho dimenticato che era necessario modificare la funzionalità. Vedi se la mia risposta aggiornata ti aiuta. –

0

È possibile effettuare performStep1 e performStep2 diverse eccezioni?

+0

Questo mi è successo. Cosa pensano gli altri di questo approccio? – Odrade

+0

Vorrei fare eccezioni che hanno senso. –

+0

Intendi gettare eccezioni con tipi personalizzati, denominati per riflettere chiaramente i loro scopi? – Odrade

3

È possibile eseguire il refactoring dei passaggi in un'interfaccia, in modo che ogni passaggio, anziché essere un metodo, esponga un metodo Run() e una proprietà Status ed è possibile eseguirli in un ciclo, fino a quando non si attiva un'eccezione . In questo modo, puoi mantenere le informazioni complete su quale fase è stata eseguita e su quale stato ogni passo ha avuto.

+0

Come una leggera modifica, il metodo Run() dell'interfaccia potrebbe restituire true o false, consentendo di interrogare il campo dello stato solo per i passaggi non riusciti. L'eccezione potrebbe essere generata nel ciclo se lo stato è Errore. Ciò consente al loop di continuare per gli Avvisi. – jheddings

+0

Dipende veramente dal fatto che i passaggi appartengano effettivamente o meno alle loro classi. Creare classi per ogni passaggio, creare un'interfaccia e tenere traccia di tutti gli oggetti potrebbe diventare eccessivamente oneroso, se così non fosse. – jasonh

+0

@jasonh Vero, ma la gestione dei passaggi e l'ordine di esecuzione potrebbero essere gestiti utilizzando le fabbriche. Se questa soluzione è implementata correttamente, anche l'aggiunta di passaggi o la modifica dell'ordine di esecuzione può essere eseguita senza modificare il codice e ricompilare. –

1

Ottieni una classe di tuple. Poi fare:

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ì, è possibile utilizzare i tipi anon per le tuple:

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

Ciò non consentirebbe l'esecuzione del codice per continuare in presenza di un avviso su performStep1. – jheddings

+0

Inoltre, il metodo non segue lo schema che sto visualizzando% 100. Alcuni passi recuperano i dati che devono essere passati ai passaggi successivi. – Odrade

+0

@jheddings come l'ho visto, qualsiasi eccezione attiva la fine dell'elaborazione; solo lo stato cambia. @odrade, beh l'originale aveva appena eseguitoStep1/2/3() ...:) – MichaelGG

2

È possibile eseguire l'elaborazione nella vostra sezione finally. finally è divertente anche se è stato restituito nel blocco try il blocco finally ancora verrà eseguito prima che la funzione ritorni effettivamente. Ricorderà quale valore è stato restituito, tuttavia, così puoi anche abbandonare lo return alla fine della tua funzione.

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

È possibile invertire le impostazioni dello stato. Impostare lo stato di errore prima di chiamare il metodo di lancio delle eccezioni. Alla fine, impostalo correttamente se non ci sono eccezioni.

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

E questo rende il codice più chiaro come? –

+0

"Errore di default"? Ciò gli consente di lanciare eccezioni da ogni passaggio come voleva, assicurandosi che sia impostato l'errore per ogni passaggio. Nessun condizionale extra o più codice. Il numero C# è limitato quando si tratta di refactoring a un livello così piccolo. – MichaelGG

-1

E se nidifica se?

poteva lavorare e non poteva lavorare

eliminerebbe ogni ritorno di lasciare un solo

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

Questo potrebbe funzionare, ma porterebbe una mostruosità profondamente annidata. – Odrade

0

Non sapendo a molto di quello che stai requisiti logiche sono, mi piacerebbe iniziare con la creazione di un classe astratta per agire come oggetto base per eseguire un passo specifico e restituire lo stato dell'esecuzione. Dovrebbe avere metodi sovrascrivibili per implementare l'esecuzione delle attività, le azioni sul successo e le azioni sul fallimento. anche gestire la logica Mettere la logica in questa classe per gestire successo o il fallimento del compito:

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; 
    } 
} 

Dopo aver creato le classi Task per eseguire i vostri passi, aggiungerli a un SortedList in ordine di esecuzione, poi iterare attraverso di loro controllare lo staus quando l'attività è stata completata:

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; 
} 

ho lasciato nella gestione degli errori, come io non sono sicuro se i vostri errori di cattura in esecuzione dell'attività o semplicemente alla ricerca per l'eccezione si stava gettando su un particolare passo fallendo.

1

Ampliando l'approccio interfaccia di cui sopra:

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

Vedendo le modifiche per passare i dati tra le fasi di elaborazione, questo potrebbe non funzionare così com'è. Spero che ti possa iniziare. – jheddings

0

vorrei consigliare su alcuni refactoring dei passaggi in classi separate, dopo tutto, la classe dovrebbe avere una sola responsabilità in ogni caso. Penso che suona come se la responsabilità dovesse controllare il funzionamento dei passaggi.

Problemi correlati