Cosa devo considerare quando aggiungo una condizione a una funzione esistente? [chiuso]

0

Spesso trovo che devo aggiungere nuove funzionalità condizionali al codice che esiste già. Ciò mi impone di mantenere la funzionalità esistente mentre affronta qualcosa di nuovo. Ho provato ciascuno dei seguenti metodi, ma nessuno dei due sembra elegante come vorrei.

Opzione 1:

bar_1():
    baz()
    frobnicate()

bar_2():
    baz()
    defrobnicate()

//Call functions.
if(foo == True):
    bar_1()
else:
    bar_2()

Opzione 2:

bar(foo):
    baz()
    if(foo):
        frobnicate()
    else:
        defrobnicate()

//Call the one function.
bar(foo)

Nell'opzione 1, la chiamata baz () viene ripetuta. Non è un grosso problema in questo esempio, ma nel codice reale che può essere 30 righe di ripetizione.

Nell'opzione 2, il codice è più compatto, ma ora abbiamo complicato tutte le chiamate alla barra con questo parametro aggiuntivo e aggiunto condizionali alla nostra funzione. E nel codice reale questi condizionali possono apparire in più punti, rendendo la funzione più difficile da leggere.

Ci sono altre considerazioni che mi mancano nel decidere quale usare?

    
posta Bindelstif 20.10.2017 - 23:04
fonte

5 risposte

1

Il condizionale sta aggiungendo EFFETTAMENTE una parte della funzione?

Se devi mettere il condizionale su ogni chiamata alla funzione, scrivi un wrapper per esso che applica il condizionale.

    
risposta data 20.10.2017 - 23:12
fonte
1

Penso che dipenda da quali costrutti linguistici sono disponibili per te. Se questo fosse, per esempio, c #, potrei farlo in questo modo:

class FooBase
{
    virtual void Baz()
    {
        //Do Baz stuff
    }

    abstract DoWork();

    public void Bar()
    {
        Baz();
        DoWork();
    }
}

Quindi implementa una sottoclasse per ogni opzione

class Foo1 : FooBase
{
    override void DoWork()
    {
        //Do frobnicate stuff
    }
}

class Foo2 : FooBase
{
    override void DoWork()
    {
        //Do defrobnicate stuff
    }
}

Poi nel tuo programma principale:

FooBase e = foo ? new Foo1() : new Foo2();
e.Bar();

Questo garantisce che Baz venga sempre chiamato prima di eseguire il lavoro ( frobnicate o defrobnicate ) che risolve il problema di accoppiamento sequenziale .

Nel frattempo puoi fornire tante sottoclassi di FooBase quante sono le condizioni, quindi è estensibile.

E infine, se mai ne hai bisogno, puoi sostituire Baz per una qualsiasi delle condizioni specifiche, nel caso in cui trovi che hanno bisogno di una logica di inizializzazione leggermente diversa.

Se le tue funzioni condividono variabili di stato, possono essere implementate come variabili dei membri della classe privata, il che aiuta a controllare il tuo ambito (che è un po 'disordinato per quanto riguarda i suoni) e chiarisce dove si trovano le dipendenze.

Ma di nuovo, dipende dalla tua lingua. Se stai scrivendo questo in assembly potresti voler evitare l'uso di un approccio basato sulla classe.

    
risposta data 21.10.2017 - 01:11
fonte
1

Questo è un classico esempio di principio di responsabilità singola a livello di metodo. Spesso si inizia con un semplice metodo che non ha o pochi argomenti e man mano che l'applicazione invecchia, si finisce con un metodo mostro che ha più di 10 argomenti e una tonnellata di costrutti if-then. Se non sai cosa stai facendo è così. A quanto pare, senti che c'è qualcosa di sbagliato qui in una fase iniziale. Buona!

Quello che sta succedendo qui è che si sta insinuando una certa complessità aggiuntiva. Ciò significa che è tempo di riconciliare il tuo progetto (a quel livello di metodo). Considera cosa puoi separare, sia con il polimorfismo di un metodo addizionale Foo-varietà che potresti voler chiamare al posto di quello che hai. Il punto è tornare al punto in cui hai meno complessità ciclomatica nei tuoi metodi individuali. Lo fai singolanando le responsabilità in metodi o classi separati.

    
risposta data 21.10.2017 - 09:04
fonte
0

Nella maggior parte dei casi, consiglierei la prima opzione. Se baz() è in realtà 30 righe di codice che non vuoi scrivere e mantenere due volte, separarle in una sua funzione è ragionevole e hai a malapena qualche codice ripetuto in più. Quindi, si ha il vantaggio di non aver apportato una brusca modifica e tutto il codice esistente continuerà a funzionare; hai solo una seconda opzione se ne hai bisogno.

Se la differenza che foo produce è più contorta rispetto all'esempio, influenzando le cose inframezzate in baz() , allora sarebbe sicuramente più difficile e / o meno efficace estrapolare una funzione condivisa. D'altra parte, se le differenze sono radicate e vitali per l'intera operazione della funzione, allora questo è in realtà un motivo ancora più GRANDE per farle essere funzioni separate. Le funzioni che fanno cose diverse non dovrebbero avere la stessa funzione.

    
risposta data 20.10.2017 - 23:34
fonte
0

Se questo tipo di problema si verifica spesso nel codice ("Spesso trovo che ho bisogno di aggiungere nuove funzionalità condizionali al codice che esiste già."), allora il codice non è ben progettato o il suo design non è compreso sufficiente.

In generale, è molto difficile dare un consiglio.

In primo luogo, dovresti iniziare a mantenere il codice meno sorprendente: se non hai tempo / denaro / inclinazione al refactoring, quindi continua ad usare gli stessi metodi usati in precedenza, non introdurre modi oscuri. (è facile, ad esempio, fare qualcosa come:

{True: bar_1, False: bar_2}[foo]()

o fai lo stesso con una fredda gerarchia di classi di Frobnicator, che risolverà i problemi di spedizione per te, ma questa freddezza non significa che il codice diventerà più gestibile.

In secondo luogo, considera il dominio aziendale. foo appartiene davvero alle preoccupazioni di bar ? Appartengono allo stesso livello di astrazione? Perché il codice sia più pulito, la funzione dovrebbe fare solo una cosa, quindi ogni parametro fornito rende il codice meno pulito. Un buon indicatore è se la barra_1 e la barra_2 hanno la loro interpretazione distinguibile nel modello di dominio. Se, lo fanno, non nascondono se nella funzione. Se foo è un parametro minore, questo è meglio se è all'interno. (L'unica eccezione a ciò che vedo nel software di modellazione scientifica, dove le funzioni possono avere molti parametri e una diversa cultura di programmazione). Inoltre, considera quante volte viene chiamata la funzione, poiché ogni chiamata dovrà essere toccata ovunque per modificare la firma della funzione.

Se hai dubbi in merito a quanto sopra, puoi prendere in considerazione un'altra considerazione se hai più di un% di parametri di tipo% di%. Puoi farti una domanda: quei parametri sono veramente indipendenti o possibilmente appartengono ad alcune entità? Ad esempio, se hai foo , x parametri e aggiungi y , puoi considerare di utilizzare un oggetto z . Potrebbe quindi essere, che la scelta diventerà una preoccupazione per quella nuova entità.

Come ultima difesa, puoi stare meglio usando il motore delle regole o qualche altro approccio dichiarativo. Potrebbe mantenere la complessità bassa e facilmente mantenibile.

Anche se non l'hai chiesto, rendere la logica più complessa con ifs può ritorcersi contro. Tracciare la logica diventerà sempre più difficile, e verrà il momento in cui nessuno capirà cosa sta succedendo, indipendentemente dal fatto che tu abbia, dentro o fuori la tua funzione.

    
risposta data 21.10.2017 - 16:16
fonte

Leggi altre domande sui tag