Refactoring a metodo lungo: lasciare com'è vs separarsi in metodi vs usare funzioni locali


9

Supponiamo di avere un metodo lungo come questo:

public void SomeLongMethod()
{
    // Some task #1
    ...

    // Some task #2
    ...
}

Questo metodo non ha parti ripetitive che dovrebbero essere spostate in un metodo separato o in una funzione locale.

Ci sono molte persone (incluso me) che pensano che i metodi lunghi siano odori di codice. Inoltre, non mi piace l'idea di utilizzare #regionqui il / i e c'è una risposta molto popolare che spiega perché questo è negativo .


Ma se separo questo codice in metodi

public void SomeLongMethod()
{
    Task1();

    Task2();
}

private void Task1()
{
    // Some task #1
    ...
}

private void Task2()
{
    // Some task #1
    ...
}

Vedo i seguenti problemi:

  • Ambito di definizione della classe inquinante con definizioni utilizzate internamente da un singolo metodo e che significa che dovrei documentarlo da qualche parte Task1e che Task2sono destinate solo agli interni di SomeLongMethod(o ogni persona che legge il mio codice dovrà dedurre questa idea).

  • Completamento automatico IDE inquinante (ad esempio Intellisense) di metodi che verrebbero utilizzati solo una volta all'interno del singolo SomeLongMethodmetodo.


Quindi, se separo questo codice di metodo in funzioni locali

public void SomeLongMethod()
{
    Task1();

    Task2();

    void Task1()
    {
        // Some task #1
        ...
    }

    void Task2()
    {
        // Some task #1
        ...
    }
}

quindi questo non ha gli svantaggi di metodi separati ma questo non sembra migliore (almeno per me) rispetto al metodo originale.


Quale versione di SomeLongMethodè più gestibile e leggibile per te e perché?



@gnat la mia domanda è specifica per c # , non per java . La mia preoccupazione principale riguarda il metodo classico rispetto alla funzione locale, non solo separato in metodi o no.
Vadim Ovchinnikov,

@gnat Anche AFAIK Java (a differenza di C #) non supporta le funzioni nidificate.
Vadim Ovchinnikov,

1
la parola chiave privata protegge sicuramente il tuo "ambito di definizione di classe"?
Ewan,

1
aspetta ...... quanto è grande la tua classe?
Ewan,

Risposte:


13

Le attività autonome dovrebbero essere spostate in metodi autonomi. Non esiste uno svantaggio abbastanza grande da superare questo obiettivo.

Dici che inquinano lo spazio dei nomi della classe, ma non è questo il compromesso che devi considerare quando prendi in considerazione il tuo codice. Un futuro lettore della classe (ad es. Tu) è molto più propenso a chiedere "Che cosa fa questo metodo specifico e come devo cambiarlo in modo che faccia quello che dicono i requisiti modificati?" di "Qual è lo scopo e il senso di essere di tutti i metodi nella classe? " Pertanto, rendere ciascuno dei metodi più facile da capire (rendendolo avere meno elementi) è molto più importante che rendere l'intera classe più facile da capire (facendolo avere meno metodi).

Naturalmente, se le attività non sono veramente autonome ma richiedono che alcune variabili di stato si spostino, un oggetto metodo, un oggetto helper o un costrutto simile possono invece essere la scelta giusta. E se le attività sono totalmente autonome e non sono affatto legate al dominio dell'applicazione (ad es. Solo la formattazione di stringhe, allora è plausibile che vadano in una classe (utility) completamente diversa. Ma tutto ciò non cambia il fatto che " mantenere i metodi per classe "è nella migliore delle ipotesi un obiettivo molto sussidiario.


Quindi sei per metodi, non per funzioni locali, sì?
Vadim Ovchinnikov,

2
@VadimOvchinnikov Con un moderno IDE di code-folding c'è poca differenza. Il più grande vantaggio di una funzione locale è che può accedere a variabili che altrimenti dovresti passare come parametri, ma se ce ne sono molte, le attività non erano realmente indipendenti all'inizio.
Kilian Foth,

6

Non mi piace nessuno dei due approcci. Non hai fornito alcun contesto più ampio, ma la maggior parte delle volte sembra così:

Hai una classe che svolge un certo lavoro combinando la potenza di più servizi in un unico risultato.

class SomeClass
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;

    public void SomeLongMethod()
    {
        _service1.Task1();
        _service2.Task2();       
    }
}

Ciascuno dei tuoi servizi implementa solo una parte della logica. Qualcosa di speciale, che solo questo servizio può fare. Se ha altri metodi privati ​​oltre a quelli che supportano l'API principale, puoi ordinarli facilmente e non dovrebbero essercene troppi comunque.

class Service1
{
    public void Task1()
    {
        // Some task #1
        ...
    }

    private void SomeHelperMethod() {}
}

class Service2
{
    void Task2()
    {
        // Some task #1
        ...
    }
}

La linea di fondo è: se inizi a creare regioni nel tuo codice per gorupare i metodi, è tempo di iniziare a pensare di incapsulare la loro logica con un nuovo servizio.


2

Il problema con la creazione di funzioni all'interno del metodo è che non riduce la lunghezza del metodo.

Se si desidera il livello aggiuntivo di protezione "privato per metodo", è possibile creare una nuova classe

public class BigClass
{
    public void SomeLongMethod();

    private class SomeLongMethodRunner
    {
        private void Task1();
        private void Task2();
    }
}

Ma le lezioni in classe sono un brutto: /


2

Ridurre al minimo l'ambito dei costrutti del linguaggio come variabili e metodi è una buona pratica. Ad esempio, evitare le variabili globali. Semplifica la comprensione del codice e aiuta a evitare l'uso improprio perché è possibile accedere al costrutto in meno punti.

Questo parla a favore dell'uso delle funzioni locali.


1
hmm sicuramente riutilizzando il codice == condividendo un metodo tra molti chiamanti. vale a dire massimizzare la sua portata
Ewan

1

Concordo sul fatto che i metodi lunghi sono spesso un odore di codice, ma non penso che sia l'intero quadro, piuttosto è il motivo per cui il metodo è lungo che indica un problema. La mia regola generale è che se il codice sta eseguendo più attività distinte, ognuna di queste attività dovrebbe essere il proprio metodo. Per me importa se quelle sezioni di codice sono ripetitive o no; a meno che tu non sia davvero assolutamente sicuro che nessuno vorrebbe chiamarli individualmente separatamente in futuro (ed è una cosa molto difficile esserne certi!) inseriscili in un altro metodo (e rendilo privato - che dovrebbe essere un indicatore molto forte non ti aspetti che venga utilizzato al di fuori della classe).

Devo confessare che non ho ancora usato le funzioni locali, quindi la mia comprensione è lungi dall'essere completa qui, ma il link che hai fornito suggerisce che sarebbero spesso usati in modo simile a un'espressione lambda (anche se qui ci sono una serie di casi d'uso dove possono essere più appropriati di lambda o dove non è possibile utilizzare una lambda, vedere Funzioni locali rispetto alle espressioni lambda per specifiche). Qui quindi, penso che ciò potrebbe dipendere dalla granularità delle "altre" attività; se sono abbastanza banali, allora forse una funzione locale sarebbe OK? D'altra parte ho visto esempi di espressioni lambda di behemoth (probabilmente dove uno sviluppatore ha recentemente scoperto LINQ) che hanno reso il codice molto meno comprensibile e gestibile rispetto a se fosse stato semplicemente chiamato da un altro metodo.

La pagina Funzioni locali indica che:

'Le funzioni locali rendono chiaro l'intento del tuo codice. Chiunque legga il tuo codice può vedere che il metodo non è richiamabile se non con il metodo contenitore. Per i progetti di gruppo, rendono anche impossibile per un altro sviluppatore chiamare erroneamente il metodo direttamente da altre parti della classe o della struttura. "

Non sono sicuro di essere davvero affetto da SM su questo come motivo di utilizzo. L'ambito del tuo metodo (insieme al suo nome e commenti) dovrebbe chiarire quale fosse il tuo intento in quel momento . Con una funzione locale ho potuto constatare che altri sviluppatori potrebbero vederlo come più sacrosanto, ma potenzialmente al punto in cui in caso di cambiamento in futuro che richieda il riutilizzo di tale funzionalità scrivono la propria procedura e lasciano attiva la funzione locale ; creando così un problema di duplicazione del codice. Quest'ultima frase della citazione sopra potrebbe essere pertinente se non ti fidi che i membri del team comprendano un metodo privato prima di chiamarlo, e quindi chiamalo in modo inappropriato (quindi la tua decisione potrebbe essere influenzata da ciò, sebbene l'educazione sarebbe un'opzione migliore qui) .

In sintesi, in generale, preferirei separare in metodi ma MS ha scritto Funzioni locali per un motivo, quindi sicuramente non direi di non usarle,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.