Refactoring del metodo lungo: lasciare come è vs separare in metodi vs utilizzando funzioni locali

8

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 su un metodo separato o su 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 usare #region (s) qui e c'è una risposta molto popolare che spiega perché questo è male .

Ma se separio 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 di inquinamento con definizioni utilizzate internamente da un singolo metodo e che significa che dovrei documentare da qualche parte che Task1 e Task2 sono intese solo per interni di SomeLongMethod (o ogni persona che legge il mio codice dovrà inferire questa idea).

  • Compilazione automatica IDE di polling (ad es. Intellisense) di metodi che verrebbero utilizzati una sola volta all'interno del singolo metodo SomeLongMethod .

Quindi, se separio questo codice 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 dei metodi separati ma questo non ha un aspetto migliore (almeno per me) rispetto al metodo originale.

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

    
posta Vadim Ovchinnikov 29.03.2018 - 14:14
fonte

5 risposte

12

Le attività autonome devono essere spostate su metodi autonomi. Non c'è alcun svantaggio abbastanza grande da sovrascrivere questo obiettivo.

Dici che inquinano lo spazio dei nomi della classe, ma non è il trade-off che devi guardare quando fai il factoring del tuo codice. Un futuro lettore della classe (ad es. Tu) ha molte più probabilità di chiedere "Che cosa fa questo specifico metodo e come devo cambiarlo in modo che faccia quello che dicono i requisiti modificati?" di "Qual è lo scopo e il senso dell'essere di tutti i metodi nella classe? " Pertanto, rendere ciascuno più facile da capire (facendo in modo che abbia meno elementi) è molto più importante di rendendo l'intera classe più facile da capire (facendo in modo che abbia un minor numero di metodi).

Naturalmente, se le attività non sono veramente autonome ma richiedono alcune variabili di stato per spostarsi, allora un oggetto metodo, un oggetto helper o un costrutto simile possono essere la scelta giusta. E se i task sono completamente e completamente non associati al dominio dell'applicazione (ad esempio, solo la formattazione delle stringhe, quindi in teoria dovrebbero andare in una classe completamente diversa (utility). Cambiamo il fatto che "mantenere i metodi per classe giù" è nel migliore dei casi un obiettivo molto secondario.

    
risposta data 29.03.2018 - 14:29
fonte
5

Non mi piace nessuno dei due approcci. Non hai fornito un contesto più ampio, ma il più delle volte è simile a questo:

Hai una classe che fa un 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();       
    }
}

Ciascun tuo servizio implementa solo una parte della logica. Qualcosa di speciale, che solo questo servitore può fare. Se ha altri metodi privati rispetto a quelli che supportano l'API principale, puoi ordinarli facilmente e non dovrebbero essercene troppi.

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

    private void SomeHelperMethod() {}
}

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

La linea di fondo è la seguente: se inizi a creare regioni nel tuo codice per elaborare metodi, è giunto il momento di iniziare a pensare di incapsulare la loro logica con un nuovo servizio.

    
risposta data 29.03.2018 - 18:45
fonte
2

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

Se desideri che il livello di protezione aggiuntivo "privato al metodo", puoi creare una nuova classe

public class BigClass
{
    public void SomeLongMethod();

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

Ma le classi di classi sono molto brutte: /

    
risposta data 29.03.2018 - 17:01
fonte
2

Ridurre al minimo l'ambito dei costrutti del linguaggio come variabili e metodi è una buona pratica. Per esempio. evitare le variabili globali. Rende il codice più facile da capire e aiuta a evitare l'abuso, perché il costrutto è accessibile in meno punti.

Questo parla dell'utilizzo delle funzioni locali.

    
risposta data 29.03.2018 - 20:05
fonte
1

Sono d'accordo che i metodi lunghi sono spesso un odore di codice, ma non penso che sia l'immagine intera, piuttosto è perché il metodo è lungo che indica un problema. La mia regola generale è che se il codice esegue più attività distinte, ciascuna di queste attività dovrebbe essere il suo metodo. Per me è importante che quelle sezioni di codice siano ripetitive o meno; a meno che non siate assolutamente sicuri che nessuno vorrebbe chiamarli separatamente separatamente in futuro (e questa è una cosa molto difficile da accertare!) metteteli in un altro metodo (e rendetelo privato) che dovrebbe essere un indicatore molto strong non ti aspetti che venga usato 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 suggerito suggerisce che sarebbero spesso usati in modo simile a un'espressione lambda (anche se qui ci sono un numero di casi d'uso dove possono essere più appropriati di lambda, o dove non è possibile utilizzare un lambda, vedi Funzioni locali rispetto alle espressioni lambda per specifiche). Ecco allora, penso che potrebbe scendere alla granularità degli "altri" compiti; se sono abbastanza banali allora forse una funzione locale sarebbe OK? D'altra parte ho visto esempi di espressioni lambda behemoth (probabilmente dove uno sviluppatore ha recentemente scoperto LINQ) che hanno reso il codice molto meno comprensibile e mantenibile rispetto a se fosse stato chiamato semplicemente da un altro metodo.

Gli stati della pagina Funzioni locali che:

'Local functions make the intent of your code clear. Anyone reading you code can see that the method is not callable except by the containing method. For team projects, they also make it impossible for another developer to mistakenly call the method directly from elsewhere in the class or stuct.'

Non sono sicuro di essere realmente con MS su questo come ragione d'uso. Lo scopo del tuo metodo (insieme al suo nome e ai suoi commenti) dovrebbe chiarire quale fosse il tuo intento in quel momento . Con una funzione locale ho potuto vedere che, altri sviluppatori potrebbero vederlo come più sacro, ma potenzialmente al punto in cui se un cambiamento arriva nel futuro che richiede che la funzionalità venga riutilizzata, scrivono le loro procedure e lasciano la funzione locale in posizione ; creando così un problema di duplicazione del codice. L'ultima frase della citazione sopra potrebbe essere rilevante se non si fida dei membri del team di comprendere un metodo privato prima di chiamarlo, e quindi chiamarlo in modo inappropriato (quindi la tua decisione potrebbe essere influenzata da questo, anche se l'educazione sarebbe un'opzione migliore qui) .

In sintesi, in generale, preferirei separare i metodi ma MS ha scritto le funzioni locali per un motivo, quindi di certo non direi di non usarli,

    
risposta data 03.04.2018 - 15:04
fonte

Leggi altre domande sui tag