Le funzioni secondarie delle funzioni principali dovrebbero terminare la funzione principale

2

Quindi, se ho una grande funzione, la suddivido in quelle più piccole per aumentare la leggibilità.

Se ho parti nella mia grande funzione in cui voglio che ritorni. Voglio che le mie sottofunzioni abbiano la possibilità di terminare anche la funzione principale, se una condizione è vera nella sottofunzione. Qual è l'approccio migliore per questo?

Rendere le sottofunzioni restituire una variabile bool sembra disordinata con i troppi ifs nella funzione principale.

OriginalFunction

void MainFunction()
{

    //DoStuff1
    ....
    ....
    if(condition1)
        return;

    //DoStuff2
    ....
    ....
    if(condition1)
        return;

    //DoStuff3
    ....
    ....
    if(condition1)
        return;

    //DoStuff4
    ....
    ....
    if(condition1)
        return;

}

Funzione modificata

void MainFunctionModified()
{
    if(DoStuff1())
        return;

    if(DoStuff2())
        return;

    if(DoStuff3())
        return;

    if(DoStuff4())
        return;

}

bool DoStuff1()
{
    ....
    ....
    if(condition1)
        return true;

    return false;
}
    
posta Ferenc Dajka 10.07.2018 - 14:19
fonte

6 risposte

4

Se la lingua che stai usando utilizza una valutazione lazy delle espressioni, puoi semplicemente collegare queste sub funzioni con && 's. Termina quindi la valutazione dell'espressione, e quindi la funzione che chiama, sul primo false restituito:

void MainFunctionModified()
{
    DoStuff1() &&
    DoStuff2() &&
    DoStuff3() &&
    DoStuff4();
}

bool DoStuff1()
{
    ....
    ....
    return condition1;
}

...
    
risposta data 10.07.2018 - 15:38
fonte
2

Un'altra opzione sarebbe quella di lasciare che i submethod generino eccezioni che si propagano al chiamante. Questo metodo non è sempre adatto, ma se i metodi secondari funzionano solo per essere chiamati dal metodo principale (e non fanno parte di una più ampia rete di classi), potrebbero funzionare ed è più facile da implementare (e leggere, pensare) rispetto al suggerimento ICommand:

public void main() {

    try {
        sub1();
        sub2();     
        // finalize as if all were successful
    } catch (Exception e) {
        // do whatever
    }   
}

private void sub1() {
    //...
    throw new InvalidOperationException();
}

private void sub2() {
    //...
    throw new InvalidOperationException();
}

Sia nel suggerimento ICommand che nel concatenato e suggerimento, questi si basano sui submethod che restituiscono uno status (bool) a seconda che il metodo abbia avuto successo. Io personalmente tendo ad evitare di tornare in questo modo e preferisco che il metodo abbia successo a meno che non ci sia un'eccezione lanciata.

    
risposta data 10.07.2018 - 16:59
fonte
1

La tua funzione potrebbe trarre vantaggio dall'implementazione del modello di comando o almeno di una versione semplificata di esso.

Dovrai creare un'interfaccia "ICommand" (userò la notazione C # poiché sono più fluente con esso)

public interface ICommand
{
    bool Execute();
}

Quindi implementare ogni classe di comando con una delle parti che si desidera eseguire. Quindi, nella tua funzione principale, avrai:

void MainFunction()
{
    var commands = new List<ICommand>
    {
        //initialize command classes here, or have another method return a command list
    }
    foreach(var command in commands) 
    {
        if (!command.Execute()) return; //you can also break out of the foreach, roll back, or do other actions in case of failure
    }
}

Puoi anche adottare un approccio più snello e funzionale al metodo sopra, anche se meno flessibile dato che non hai un'interfaccia espandibile:

void MainFunction()
{
    var functions = new List<Func<bool>>
    {
        //initialize with a list of function delegates here
    }
    foreach(var function in functions) 
    {
        if (!function()) return;
    }
}
    
risposta data 10.07.2018 - 15:57
fonte
0

Con la maggior parte delle lingue, una funzione deve sempre tornare al chiamante. In genere, se è necessario terminare il programma senza che il chiamante continui dopo la chiamata della funzione, la funzione restituirà un valore e il chiamante verificherà la situazione, come nel tuo esempio.

Alcune lingue offrono anche un modo diretto per terminare il processo (ad esempio, i programmi .NET possono utilizzare Environment.Exit() ), ma questo di solito non è il modo migliore per farlo.

    
risposta data 10.07.2018 - 19:55
fonte
-1

Dalla tua OriginalFunction , se le tue DoStuff operazioni rappresentano i comportamenti che MainFunction() dovrebbe eseguire nella propria conditions . Con questo numero di rami, potresti prendere in considerazione l'utilizzo del Pattern di stato .

Il Pattern di stato riduce in modo efficace la complessità delle strategie di ramificazione e rende il codice più leggibile e mantenibile.

    
risposta data 10.07.2018 - 14:49
fonte
-1

Questo è uno dei motivi per cui C include le funzioni setjmp/longjmp : srotola uno stack di chiamate di notevole profondità. Questo approccio, tuttavia, presenta alcuni pericoli - se qualcosa nella catena di chiamate ha fatto una considerevole allocazione delle risorse, quella risorsa potrebbe essere trapelata, quindi deve essere gestita con cura.

Altre lingue hanno componenti linguistici simili a setjmp/longjmp .

Un altro approccio potrebbe essere una tipica struttura a macchina di stato:

while (state  != STATE_EXIT) {
   switch (state):
      case FUNC1:
         state = doFunc1();
         break;
      case FUNC2:
         state = doFunc2();
      ...
}
    
risposta data 10.07.2018 - 17:22
fonte

Leggi altre domande sui tag