Wzorzec C # do czystego obchodzenia się z „darmowymi funkcjami”, unikając klas statycznych typu „torba narzędziowa” w stylu pomocnika

9

Niedawno przeglądałem kilka klas statycznych typu „torba narzędziowa” pomocnika unoszących się wokół dużych baz kodu C #, z którymi pracuję, w zasadzie takie jak ten bardzo skondensowany fragment kodu:

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

Konkretne metody, które sprawdziłem, to

  • w większości niezwiązane ze sobą,
  • bez wyraźnego stanu utrzymywanego między wywołaniami,
  • mały i
  • każdy z nich jest konsumowany przez różne niepowiązane typy.

Edycja: Powyższe nie ma stanowić listy domniemanych problemów. To lista wspólnych cech konkretnych metod, które recenzuję. Jest kontekstem, aby pomóc w udzieleniu odpowiedzi na bardziej trafne rozwiązania.

Tylko na to pytanie będę się odnosił do tego rodzaju metody jako GLUM (ogólna metoda lekkiej użyteczności). Negatywne znaczenie słowa „ponury” jest częściowo zamierzone. Przepraszam, jeśli okaże się to głupim żartem.

Nawet odkładając na bok mój własny domyślny sceptycyzm wobec GLUM, nie podoba mi się następujące rzeczy:

  • Klasa statyczna jest używana wyłącznie jako przestrzeń nazw.
  • Identyfikator klasy statycznej jest w zasadzie bez znaczenia.
  • Kiedy dodaje się nowy GLUM, albo (a) ta klasa „torby” zostaje dotknięta bez powodu lub (b) tworzona jest nowa klasa „torby” (co samo w sobie nie jest zwykle problemem; szkoda, że ​​nowa klasy statyczne często powtarzają problem niezwiązania, ale przy użyciu mniejszej liczby metod).
  • Meta-nazewnictwa jest nieunikniony okropne, niestandardowe, a zwykle wewnętrznie niespójne, czy to Helpers, Utilitiesczy cokolwiek.

Jaki jest dość dobry i prosty wzorzec do refaktoryzacji tego, najlepiej w odpowiedzi na powyższe obawy, a najlepiej z jak najmniejszym naciskiem?

Powinienem chyba podkreślić: wszystkie metody, z którymi mam do czynienia, nie są ze sobą powiązane. Wydaje się, że nie ma rozsądnego sposobu na rozbicie ich na drobnoziarniste, ale wciąż wieloczłonowe torby metodyczne klasy statycznej.

William
źródło
1
O GLUMs ™: Myślę, że można usunąć skrót „lekka”, ponieważ metody powinny być lekkie, postępując zgodnie z najlepszymi praktykami;)
Fabio,
@Fabio Zgadzam się. Zawarłem ten termin jako (prawdopodobnie niezbyt zabawny i być może mylący) żart, który służy jako skrót do napisania tego pytania. Myślę, że „lekki” wydawał się tutaj odpowiedni, ponieważ bazy danych, o których mowa, są długowieczne, starsze i trochę niechlujne, tj. Zdarza się, że istnieje wiele innych metod (zwykle te GUMY ;-), które są „ciężkie”. Gdybym miał w tej chwili większy (ehem, jakikolwiek) budżet, zdecydowanie podjąłbym bardziej agresywne podejście i zajął się tym drugim.
William

Odpowiedzi:

17

Myślę, że masz dwa problemy ściśle ze sobą powiązane.

  • Klasy statyczne zawierają logicznie niepowiązane funkcje
  • Nazwy klas statycznych nie dostarczają pomocnych informacji o znaczeniu / kontekście zawartych funkcji.

Problemy te można rozwiązać, łącząc tylko logicznie powiązane metody w jedną klasę statyczną i przenosząc pozostałe do ich własnej klasy statycznej. W oparciu o domenę problemową Ty i Twój zespół powinniście zdecydować, jakie kryteria zastosujecie do podziału metod na powiązane klasy statyczne.

Obawy i możliwe rozwiązania

Metody są w większości niezwiązane ze sobą - problem. Połącz powiązane metody w jedną klasę statyczną i przenieś metody niepowiązane do własnych klas statycznych.

Metody są bez jawnego stanu utrzymywanego między wywołaniami - nie stanowi to problemu. Metody bezstanowe są cechą, a nie błędem. Stan statyczny i tak jest trudny do przetestowania i należy go używać tylko wtedy, gdy trzeba zachować stan w wywołaniach metod, ale istnieją lepsze sposoby, aby to zrobić, takie jak maszyny stanu i yieldsłowo kluczowe.

Metody są małe - nie stanowi problemu. - Metody powinny być małe.

Każda z metod jest wykorzystywana przez różne niepowiązane typy - nie stanowi to problemu. Stworzyłeś ogólną metodę, którą można ponownie wykorzystać w wielu niepowiązanych miejscach.

Klasa statyczna jest używana wyłącznie jako przestrzeń nazw - nie stanowi to problemu. W ten sposób stosowane są metody statyczne. Jeśli przeszkadza ci ten sposób wywoływania metod, spróbuj użyć metod rozszerzenia lub using staticdeklaracji.

Identyfikator klasy statycznej jest w zasadzie bez znaczenia - problem . Jest to bez znaczenia, ponieważ programiści stosują w nim niepowiązane metody. Umieść niepowiązane metody we własnych klasach statycznych i nadaj im konkretne i zrozumiałe nazwy.

Kiedy dodaje się nowy GLUM, albo (a) ta klasa „torby” zostaje dotknięta (smutek SRP) - nie stanowi problemu, jeśli podążasz za ideą, że tylko logicznie powiązane metody powinny znajdować się w jednej klasie statycznej. SRPnie jest tu naruszane, ponieważ zasadę należy stosować do klas lub metod. Pojedyncza odpowiedzialność klas statycznych oznacza zawarcie różnych metod dla jednego ogólnego „pomysłu”. Ten pomysł może być funkcją lub konwersją, iteracją (LINQ), normalizacją danych lub sprawdzaniem poprawności ...

lub rzadziej (b) tworzona jest nowa klasa „worków” (więcej worków) - to nie problem. Klasy / klasy statyczne / funkcje - to nasze narzędzia (dla programistów), których używamy do projektowania oprogramowania. Czy Twój zespół ma jakieś ograniczenia dotyczące korzystania z zajęć? Jakie są maksymalne limity dla „większej liczby toreb”? W programowaniu chodzi o kontekst, jeśli dla rozwiązania w twoim konkretnym kontekście otrzymasz 200 klas statycznych, które, co zrozumiałe, nazwane, umieszczone logicznie w przestrzeniach nazw / folderach z logiczną hierarchią - nie stanowi problemu.

Meta-nazewnictwo jest nieuchronnie okropne, niestandardowe i zwykle wewnętrznie niespójne, niezależnie od tego, czy jest to pomocnik, program narzędziowy, czy cokolwiek innego - problem. Podaj lepsze nazwy opisujące oczekiwane zachowanie. Zachowaj tylko powiązane metody w jednej klasie, które zapewnią pomoc w lepszym nazewnictwie.

Fabio
źródło
To nie jest naruszenie SRP, ale całkiem wyraźnie naruszenie zasady Open / Closed Principle. To powiedziawszy, ogólnie uważam, że Ocp jest większym kłopotem niż jest wart
Paul
2
@Paul, zasada Open / Close nie może być stosowana do klas statycznych. To był mój punkt widzenia, że ​​zasady SRP lub OCP nie mogą być stosowane do klas statycznych. Możesz zastosować go do metod statycznych.
Fabio
Okej, było tu trochę zamieszania. Pierwsza lista elementów pytania nie jest listą rzekomych problemów. To lista wspólnych cech konkretnych metod, które recenzuję. Jest kontekstem, aby pomóc w udzieleniu odpowiedzi na bardziej odpowiednie rozwiązania. Przeredaguję, aby wyjaśnić.
William
1
Jeśli chodzi o „klasę statyczną jako przestrzeń nazw”, fakt, że jest to wspólny wzorzec, nie oznacza, że ​​nie jest to anty-wzorek. Metoda (która jest w zasadzie „funkcją swobodną” do zapożyczenia terminu z C / C ++) w ramach tej konkretnej klasy statycznej o niskiej kohezji jest w tym przypadku znaczącą jednostką. W tym szczególnym kontekście wydaje się, że strukturalnie lepiej jest mieć podprzestrzeń nazw Aze 100 klasami statycznymi pojedynczej metody (w 100 plikach) niż klasą statyczną Azawierającą 100 metod w jednym pliku.
William
1
Zrobiłem dość poważne oczyszczenie twojej odpowiedzi, głównie kosmetyczne. Nie jestem pewien, o czym jest twój ostatni akapit; nikt nie martwi się tym, jak testują kod wywołujący Mathklasę statyczną.
Robert Harvey,
5

Wystarczy przyjąć konwencję, że klasy statyczne są używane jak przestrzenie nazw w takich sytuacjach w C #. Przestrzenie nazw mają dobre nazwy, podobnie jak te klasy. using staticCechą C # 6 sprawia, że życie trochę łatwiejsze, zbyt.

Śmierdzące nazwy klas Helperssygnalizują, że metody powinny być podzielone na lepiej nazwane klasy statyczne, jeśli to w ogóle możliwe, ale jednym z podkreślonych założeń jest to, że metody, z którymi mamy do czynienia, są „niezwiązane parami”, co oznacza podział na jedna nowa klasa statyczna na istniejącą metodę. To chyba dobrze, jeśli

  • istnieją dobre nazwy dla nowych klas statycznych i
  • oceniasz, że faktycznie wpływa to na łatwość utrzymania projektu.

Jako wymyślony przykład Helpers.LoadImagemoże stać się czymś takim FileSystemInteractions.LoadImage.

Mimo to możesz skończyć z workami metod statycznymi. Niektóre sposoby to może się zdarzyć:

  • Może tylko niektóre (lub żadna) z oryginalnych metod mają dobre nazwy dla swoich nowych klas.
  • Być może nowe klasy ewoluują później i obejmują inne odpowiednie metody.
  • Może pierwotna nazwa klasy nie była śmierdząca i była w porządku.

Ważne jest, aby pamiętać, że te torby metod klasy statycznej nie są strasznie rzadkie w prawdziwych bazach kodu C #. Prawdopodobnie nie jest patologiczne, aby mieć kilka małych .


Jeśli naprawdę widzisz korzyść z posiadania każdej metody podobnej do „darmowej funkcji” we własnym pliku (co jest w porządku i warte zachodu, jeśli uczciwie ocenisz, że faktycznie wpływa to na łatwość utrzymania projektu), możesz rozważyć utworzenie takiej klasy statycznej zamiast statycznej klasa częściowa, wykorzystując nazwę klasy statycznej podobną do przestrzeni nazw, a następnie wykorzystując ją using static. Na przykład:

struktura projektu fikcyjnego przy użyciu tego wzorca

Program.cs

namespace ConsoleApp1
{
    using static Foo;
    using static Flob;

    class Program
    {
        static void Main(string[] args)
        {
            Bar();
            Baz();
            Wibble();
            Wobble();
        }
    }
}

Foo / Bar.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Bar() { }
    }
}

Foo / Baz.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Baz() { }
    }
}

Flob / Wibble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wibble() { }
    }
}

Flob / Wobble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wobble() { }
    }
}
William
źródło
-6

Zasadniczo nie powinieneś mieć ani wymagać żadnych „ogólnych metod użyteczności”. W twojej logice biznesowej. Jeszcze raz rzuć okiem i umieść je w odpowiednim miejscu.

Jeśli piszesz bibliotekę matematyczną lub coś takiego, sugerowałbym, choć normalnie ich nienawidzę, Metody rozszerzenia.

Za pomocą metod rozszerzeń można dodawać funkcje do standardowych typów danych.

Na przykład Powiedzmy, że mam ogólną funkcję MySort () zamiast Helpers.MySort lub dodając nową ListOfMyThings.MySort Mogę dodać ją do IEnumerable

Ewan
źródło
Chodzi o to, aby nie podjąć kolejnego „twardego spojrzenia”. Chodzi o to, aby mieć wzór wielokrotnego użytku, który może łatwo wyczyścić „torbę” narzędzi. Wiem, że narzędzia to zapachy. Pytanie to stwierdza. Celem jest, aby rozsądnie izolować tych niezależnych (ale też ściśle współpracować znajduje) podmioty domeny na teraz i iść tak proste, jak to możliwe w budżecie projektu.
William
1
Jeśli nie masz „ogólnych metod użyteczności”, prawdopodobnie nie izolujesz wystarczająco elementów logiki od reszty logiki. Na przykład, o ile mi wiadomo, w .NET nie ma funkcji łączenia ścieżek URL ogólnego przeznaczenia, więc często kończę na pisaniu. Tego rodzaju cruft lepiej by było w ramach standardowej biblioteki, ale w każdej standardowej bibliotece czegoś brakuje . Alternatywa, pozostawiając logikę powtarzaną bezpośrednio w innym kodzie, sprawia, że ​​jest on znacznie mniej czytelny.
jpmc26,
1
@Ewan, który nie jest funkcją, jest to podpis delegata ogólnego przeznaczenia ...
TheCatWhisperer
1
@Ewan To był cały punkt TheCatWhisperer: klasy narzędziowe są kłopotliwe z powodu konieczności umieszczania metod w klasie. Cieszę się, że się zgadzasz.
jpmc26