Refaktoryzacja długich metod: pozostawianie bez zmian w porównaniu do metod vs używanie funkcji lokalnych

9

Załóżmy, że mam taką długą metodę:

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

    // Some task #2
    ...
}

Ta metoda nie ma powtarzalnych części, które należy przenieść do oddzielnej metody lub funkcji lokalnej.

Wiele osób (w tym ja) uważa, że ​​długie metody to zapachy kodowe. Nie podoba mi się również pomysł użycia tutaj #region(ów) i istnieje bardzo popularna odpowiedź wyjaśniająca, dlaczego jest to złe .


Ale jeśli podzielę ten kod na metody

public void SomeLongMethod()
{
    Task1();

    Task2();
}

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

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

Widzę następujące problemy:

  • Zakres definicji klasy zanieczyszczeń z definicjami, które są używane wewnętrznie przez jedną metodę, co oznacza, że ​​powinienem gdzieś to udokumentować Task1i Task2są one przeznaczone tylko dla elementów wewnętrznych SomeLongMethod(lub każda osoba, która czyta mój kod, będzie musiała wywnioskować ten pomysł).

  • Zanieczyszczające autouzupełnianie IDE (np. Intellisense) metod, które byłyby używane tylko raz w obrębie jednej SomeLongMethodmetody.


Więc jeśli podzielę ten kod metody na funkcje lokalne

public void SomeLongMethod()
{
    Task1();

    Task2();

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

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

to nie ma wad oddzielnych metod, ale nie wygląda to lepiej (przynajmniej dla mnie) niż metoda oryginalna.


Która wersja SomeLongMethodjest dla Ciebie łatwiejsza w utrzymaniu i czytelna i dlaczego?

Vadim Ovchinnikov
źródło
@gnat moje pytanie dotyczy tylko języka c # , a nie Java . Moją główną troską jest metoda klasyczna a funkcja lokalna, a nie tylko podział na metody czy nie.
Vadim Ovchinnikov
@gnat Również Java AFAIK (w przeciwieństwie do C #) nie obsługuje funkcji zagnieżdżonych.
Vadim Ovchinnikov
1
prywatne słowo kluczowe z pewnością chroni „zakres definicji klasy”?
Ewan
1
czekaj ...... jak duża jest twoja klasa?
Ewan

Odpowiedzi:

13

Samodzielne zadania należy przenieść na samodzielne metody. Nie ma wystarczająco dużej wady, by zastąpić ten cel.

Mówisz, że zanieczyszczają przestrzeń nazw klasy, ale to nie jest kompromis, na który musisz zwrócić uwagę przy faktorowaniu kodu. Przyszły czytelnik klasy (np. Ty) z większym prawdopodobieństwem zapyta: „Co robi ta konkretna metoda i jak ją zmienić, aby działała zgodnie ze zmienionymi wymaganiami?” niż „Jaki jest cel i sens bycia wszystkimi metodami w klasie? ” Dlatego też ułatwienie zrozumienia każdej z metod (dzięki temu, że ma mniej elementów) jest o wiele ważniejsze niż ułatwienie zrozumienia całej klasy (dzięki temu, że ma mniej metod).

Oczywiście, jeśli zadania nie są tak naprawdę samowystarczalne, ale wymagają przesunięcia niektórych zmiennych stanu, wówczas właściwym wyborem może być obiekt metody, obiekt pomocnika lub podobna konstrukcja. A jeśli zadania są całkowicie samowystarczalne i nie są w ogóle powiązane z domeną aplikacji (np. Po prostu formatowanie ciągów, to prawdopodobnie powinny przejść do zupełnie innej klasy (użytecznej). Ale to nie zmienia faktu, że „ utrzymanie metod w klasie na niskim poziomie ”jest w najlepszym razie bardzo pomocniczym celem.

Kilian Foth
źródło
Więc jesteś za metodami, a nie funkcjami lokalnymi, tak?
Vadim Ovchinnikov
2
@VadimOvchinnikov Dzięki nowoczesnemu IDE do składania kodu różnica jest niewielka. Największą zaletą funkcji lokalnej jest to, że może ona uzyskiwać dostęp do zmiennych, które w przeciwnym razie musiałbyś przekazać jako parametry, ale jeśli jest ich dużo, zadania nie były tak naprawdę niezależne.
Kilian Foth
6

Nie podoba mi się żadne podejście. Nie podałeś szerszego kontekstu, ale przez większość czasu wygląda to tak:

Masz klasę, która wykonuje pewne zadanie, łącząc moc wielu usług w jeden wynik.

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

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

Każda z twoich usług implementuje tylko część logiki. Coś specjalnego, co może zrobić tylko ta usługa. Jeśli ma inne prywatne metody niż tylko te, które obsługują główny interfejs API, możesz je łatwo posortować i i tak nie powinno być ich zbyt wiele.

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

    private void SomeHelperMethod() {}
}

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

Najważniejsze jest to: jeśli zaczniesz tworzyć regiony w kodzie w celu metod gorup, nadszedł czas, aby pomyśleć o enkapsulacji ich logiki za pomocą nowej usługi.

t3chb0t
źródło
2

Problem z tworzeniem funkcji w metodzie polega na tym, że nie zmniejsza ona długości metody.

Jeśli chcesz, aby dodatkowy poziom ochrony był „prywatny dla metody”, możesz utworzyć nową klasę

public class BigClass
{
    public void SomeLongMethod();

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

Ale klasy w klasach są bardzo brzydkie: /

Ewan
źródło
2

Dobrą praktyką jest minimalizowanie zakresu konstrukcji języka, takich jak zmienne i metody. Np. Unikaj zmiennych globalnych. Ułatwia to zrozumienie kodu i pomaga uniknąć niewłaściwego użycia, ponieważ konstrukcja jest dostępna w mniejszej liczbie miejsc.

Przemawia to za korzystaniem z funkcji lokalnych.

fsl
źródło
1
hmm, z pewnością ponownie wykorzystuję kod == udostępniając metodę wielu dzwoniącym. tj. maksymalizacja jego zasięgu
Ewan
1

Zgadzam się, że długie metody są często zapachem kodu, ale nie sądzę, że to jest cały obraz, raczej dlatego metoda jest długa, co wskazuje na problem. Zasadą ogólną jest to, że jeśli kod wykonuje wiele różnych zadań, każde z nich powinno być własną metodą. Dla mnie nie ma znaczenia, czy te sekcje kodu są powtarzalne, czy nie; chyba że naprawdę jesteś absolutnie pewien, że nikt nie będzie chciał osobno wywoływać ich osobno w przyszłości (a to jest bardzo trudne, aby być pewnym!) zastosuj je w innej metodzie (i uczyń to prywatnym - co powinno być bardzo silnym wskaźnikiem nie spodziewasz się, że będzie używany poza klasą).

Muszę przyznać, że nie korzystałem jeszcze z funkcji lokalnych, więc moje zrozumienie jest dalekie od pełnego, ale podany przez ciebie link sugeruje, że często byłyby używane w podobny sposób jak wyrażenie lambda (chociaż tutaj jest wiele przypadków użycia gdzie mogą być bardziej odpowiednie niż lambdas lub gdzie nie można użyć lambda, zobacz Funkcje lokalne w porównaniu do wyrażeń lambda w celu uzyskania szczegółowych informacji). Tutaj myślę, że może to sprowadzać się do szczegółowości „innych” zadań; jeśli są dość trywialne, to może funkcja lokalna byłaby OK? Z drugiej strony widziałem przykłady wyrażeń lambda behemotów (prawdopodobnie tam, gdzie programista niedawno odkrył LINQ), które spowodowały, że kod był znacznie mniej zrozumiały i łatwiejszy do utrzymania niż gdyby został wywołany z innej metody.

Strona funkcji lokalnych stwierdza, że:

„Funkcje lokalne wyjaśniają intencję twojego kodu. Każdy, kto czyta kod, może zobaczyć, że nie można wywołać metody, chyba że zawiera ona metodę zawierającą. W przypadku projektów zespołowych uniemożliwiają też innemu programistowi błędne wywołanie metody bezpośrednio z innego miejsca w klasie lub grupie.

Nie jestem pewien, czy naprawdę jestem z SM na tym jednym jako powód do użycia. Zakres twojej metody (wraz z nazwą i komentarzami) powinien jasno określać, jakie były twoje zamiary w tym czasie . Dzięki funkcji lokalnej mogłem to zauważyć, inni deweloperzy mogą postrzegać ją jako bardziej świętą, ale potencjalnie do tego stopnia, że ​​jeśli w przyszłości nastąpi zmiana, która wymaga ponownego użycia tej funkcji, piszą własną procedurę i pozostawiają funkcję lokalną na miejscu ; tworząc w ten sposób problem z duplikacją kodu. To ostatnie zdanie z powyższego cytatu może mieć znaczenie, jeśli nie ufasz członkom zespołu, że rozumieją metodę prywatną przed jej wywołaniem, i dlatego nazywaj ją niewłaściwie (więc może to mieć wpływ na twoją decyzję, chociaż edukacja byłaby lepszym rozwiązaniem tutaj) .

Podsumowując, ogólnie wolałbym rozdzielić na metody, ale stwardnienie rozsiane napisało Lokalne Funkcje z jakiegoś powodu, więc na pewno nie powiedziałbym, że nie używaj ich,

d219
źródło