Czy używanie wielu metod statycznych jest złą rzeczą?


97

Zwykle deklaruję jako statyczne wszystkie metody w klasie, gdy ta klasa nie wymaga śledzenia stanów wewnętrznych. Na przykład, jeśli muszę przekształcić A w B i nie polegać na jakimś wewnętrznym stanie C, który może się różnić, tworzę transformację statyczną. Jeśli istnieje stan wewnętrzny C, który chcę mieć możliwość dostosowania, dodaję konstruktora do ustawienia C i nie używam transformacji statycznej.

Czytałem różne zalecenia (w tym dotyczące StackOverflow), aby NIE nadużywać metod statycznych, ale nadal nie rozumiem, co jest nie tak z powyższą zasadą.

Czy to rozsądne podejście, czy nie?

Odpowiedzi:


153

Istnieją dwa rodzaje typowych metod statycznych:

  • „Bezpieczna” metoda statyczna zawsze daje te same wyniki dla tych samych wejść. Nie modyfikuje żadnych zmiennych globalnych i nie wywołuje żadnych „niebezpiecznych” metod statycznych żadnej klasy. Zasadniczo używasz ograniczonego rodzaju programowania funkcjonalnego - nie bój się tego, są w porządku.
  • „Niebezpieczna” metoda statyczna powoduje mutację stanu globalnego lub serwerów proxy do obiektu globalnego lub innego niemożliwego do przetestowania zachowania. Są to zwroty do programowania proceduralnego i powinny zostać zmienione, jeśli to w ogóle możliwe.

Istnieje kilka typowych zastosowań „niebezpiecznych” statystyk - na przykład we wzorcu Singleton - ale pamiętaj, że pomimo wszelkich ładnych nazw, które je nazywasz, po prostu mutujesz zmienne globalne. Zastanów się dokładnie, zanim użyjesz niebezpiecznych statystyk.


To był właśnie problem, który musiałem rozwiązać - użycie, a raczej niewłaściwe użycie obiektów Singleton.
overslacked

Dziękuję za tę doskonałą odpowiedź. Moje pytanie brzmi, czy jeśli singletony są przekazywane jako parametry do metod statycznych, czy powoduje to, że metoda statyczna jest niebezpieczna?
Tony D

1
Terminy „czysta funkcja” i „nieczysta funkcja” to nazwy nadawane w programowaniu funkcjonalnym temu, co nazywamy „bezpieczną” i „niebezpieczną” statyką.
Omnimike

19

Obiekt bez żadnego stanu wewnętrznego jest rzeczą podejrzaną.

Zwykle obiekty hermetyzują stan i zachowanie. Obiekt, który tylko hermetyzuje zachowanie, jest dziwny. Czasami jest to przykład wagi lekkiej lub muszej .

Innym razem jest to projekt proceduralny wykonany w języku obiektowym.


6
Słyszę, co mówisz, ale jak coś takiego jak obiekt Math może obejmować cokolwiek innego niż zachowanie?
JonoW

10
Po prostu powiedział, że podejrzany, nie zły, i ma absolutną rację.
Bill K

2
@JonoW: Matematyka to bardzo szczególny przypadek, w którym istnieje wiele funkcji bezstanowych. Oczywiście, jeśli robisz programowanie funkcjonalne w Javie, miałbyś wiele funkcji bezstanowych.
S.Lott

14

To naprawdę tylko kontynuacja wspaniałej odpowiedzi Johna Millikina.


Chociaż może być bezpieczne uczynienie metod bezstanowych (które są w zasadzie funkcjami) statycznymi, czasami może to prowadzić do sprzężenia, które jest trudne do zmodyfikowania. Rozważ, że masz metodę statyczną jako taką:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Które nazywasz jako:

StaticClassVersionOne.doSomeFunkyThing(42);

Co jest dobre, dobre i bardzo wygodne, dopóki nie natrafisz na przypadek, w którym musisz zmodyfikować zachowanie metody statycznej i stwierdzisz, że jesteś ściśle związany StaticClassVersionOne . Możliwe, że możesz zmodyfikować kod i byłoby dobrze, ale gdyby istniały inne wywołania zależne od starego zachowania, będą musiały zostać uwzględnione w treści metody. W niektórych przypadkach ciało metody może stać się dość brzydkie lub nie do utrzymania, jeśli spróbuje zrównoważyć wszystkie te zachowania. Jeśli podzielisz metody, być może będziesz musiał zmodyfikować kod w kilku miejscach, aby to uwzględnić, lub wykonać wywołania nowych klas.

Ale zastanów się, czy stworzyłeś interfejs, aby udostępnić metodę i przekazałeś ją wywołującym, teraz, gdy zachowanie musi się zmienić, można utworzyć nową klasę do implementacji interfejsu, który jest czystszy, łatwiejszy do przetestowania i łatwiejszy w utrzymaniu, i to zamiast tego jest przekazywane dzwoniącym. W tym scenariuszu klasy wywołujące nie muszą być zmieniane ani nawet ponownie kompilowane, a zmiany są lokalizowane.

Może to być sytuacja prawdopodobna lub nie, ale myślę, że warto się nad tym zastanowić.


5
Twierdzę, że to nie tylko prawdopodobny scenariusz, to sprawia, że ​​statyka jest ostatecznością. Statyka sprawia, że ​​TDD też jest koszmarem. Gdziekolwiek używasz statycznego, nie możesz zrobić makiety, musisz wiedzieć, jakie dane wejściowe i wyjściowe są, aby przetestować niepowiązaną klasę. Teraz, jeśli zmienisz zachowanie statycznego, twoje testy na niepowiązanych klasach, które używają tego statycznego, zostaną przerwane. Ponadto staje się ukrytą zależnością, której nie można przekazać konstruktorowi w celu powiadomienia deweloperów o potencjalnie ważnej zależności.
DanCaveman,

6

Inną opcją jest dodanie ich jako metod niestatycznych do obiektu źródłowego:

tj. zmiana:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

w

public class Bar {
    ...
    public Foo transform() { ...}
}

jednak w wielu sytuacjach nie jest to możliwe (np. zwykłe generowanie kodu klasy z XSD / WSDL / itp.) lub spowoduje to, że klasa będzie bardzo długa, a metody transformacji mogą często być prawdziwym problemem dla złożonych obiektów i po prostu ich potrzebujesz we własnej, oddzielnej klasie. Więc tak, mam statyczne metody w klasach narzędzi.


5

Klasy statyczne są w porządku, o ile są używane we właściwych miejscach.

Mianowicie: Metody, które są metodami typu „liść” (nie modyfikują stanu, a jedynie w jakiś sposób przekształcają dane wejściowe). Dobrym tego przykładem są takie rzeczy jak Path.Combine. Tego typu rzeczy są przydatne i tworzą bardziej prostą składnię.

Te problemy mam z statyki są liczne:

Po pierwsze, jeśli masz klasy statyczne, zależności są ukryte. Rozważ następujące:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

Patrząc na TextureManager, nie możesz powiedzieć, jakie kroki inicjalizacji należy wykonać, patrząc na konstruktora. Musisz zagłębić się w klasę, aby znaleźć jej zależności i zainicjować rzeczy we właściwej kolejności. W takim przypadku wymaga zainicjowania ResourceLoader przed uruchomieniem. Teraz zwiększ skalę tego koszmaru zależności i prawdopodobnie możesz zgadnąć, co się stanie. Wyobraź sobie, że próbujesz zachować kod, w którym nie ma wyraźnej kolejności inicjalizacji. Porównaj to z iniekcją zależności z instancjami - w takim przypadku kod nie zostanie nawet skompilowany, jeśli zależności nie zostaną spełnione!

Co więcej, jeśli używasz statystyk, które modyfikują stan, jest to jak domek z kart. Nigdy nie wiadomo, kto ma do czego dostęp, a projekt przypomina potwora spaghetti.

Wreszcie, co równie ważne, używanie statyki wiąże program z konkretną implementacją. Kod statyczny jest przeciwieństwem projektowania pod kątem testowalności. Testowanie kodu, który jest pełen elementów statycznych, to koszmar. Wywołanie statyczne nigdy nie może zostać zamienione na podwójne testowe (chyba że używasz struktur testowych specjalnie zaprojektowanych do wyszydzania statycznych typów), więc system statyczny powoduje, że wszystko, co go używa, jest natychmiastowym testem integracji.

Krótko mówiąc, statystyka jest dobra do niektórych rzeczy, a do małych narzędzi lub kodu jednorazowego użytku nie zniechęcałbym ich do używania. Jednak poza tym są krwawym koszmarem ze względu na łatwość konserwacji, dobry projekt i łatwość testowania.

Oto dobry artykuł na temat problemów: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/


4

Powodem ostrzeżenia przed metodami statycznymi jest to, że ich używanie powoduje utratę jednej z zalet obiektów. Obiekty są przeznaczone do enkapsulacji danych. Zapobiega to występowaniu nieoczekiwanych skutków ubocznych, co pozwala uniknąć błędów. Metody statyczne nie mają hermetyzowanych danych *, więc nie przynoszą tej korzyści.

To powiedziawszy, jeśli nie korzystasz z danych wewnętrznych, są one w porządku i nieco szybsze do wykonania. Upewnij się jednak, że nie dotykasz w nich danych globalnych.

  • Niektóre języki mają również zmienne na poziomie klasy, które umożliwiają hermetyzację danych i metody statyczne.

4

Wydaje się, że to rozsądne podejście. Powodem, dla którego nie chcesz używać zbyt wielu statycznych klas / metod, jest to, że w końcu odchodzisz od programowania obiektowego i zaczynasz zajmować się programowaniem strukturalnym.

W przypadku, gdy po prostu przekształcasz A w B, powiedz, że wszystko, co robimy, to przekształcanie tekstu, z którego pochodzi

"hello" =>(transform)=> "<b>Hello!</b>"

Wtedy metoda statyczna miałaby sens.

Jeśli jednak często wywołujesz te statyczne metody na obiekcie i zwykle jest to unikalne dla wielu wywołań (np. Sposób ich użycia zależy od danych wejściowych) lub jest to część nieodłącznego zachowania obiektu, bądź mądry, aby uczynić go częścią przedmiotu i utrzymywać go w stanie. Jednym ze sposobów byłoby zaimplementowanie go jako interfejsu.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Edycja: dobrym przykładem doskonałego wykorzystania metod statycznych są metody pomocnicze HTML w Asp.Net MVC lub Ruby. Tworzą elementy html, które nie są powiązane z zachowaniem obiektu i dlatego są statyczne.

Edycja 2: Zmieniłem programowanie funkcjonalne na programowanie strukturalne (z jakiegoś powodu byłem zdezorientowany), rekwizyty Torstenowi za wskazanie tego.


2
Nie sądzę, aby używanie metod statycznych kwalifikowało się jako programowanie funkcjonalne, więc domyślam się, że masz na myśli programowanie strukturalne.
Torsten Marek

3

Niedawno zrefaktorowałem aplikację, aby usunąć / zmodyfikować niektóre klasy, które zostały pierwotnie zaimplementowane jako klasy statyczne. Z biegiem czasu te klasy zdobyły tak wiele, a ludzie po prostu oznaczali nowe funkcje jako statyczne, ponieważ nigdy nie było żadnej instancji.

Tak więc moja odpowiedź brzmi, że klasy statyczne nie są z natury złe, ale może być łatwiej zacząć tworzyć instancje teraz, a potem trzeba będzie je później refaktoryzować.


3

Uznałbym to za zapach designu. Jeśli używasz głównie metod statycznych, prawdopodobnie nie masz zbyt dobrego projektu obiektowego. Niekoniecznie jest to złe, ale tak jak w przypadku wszystkich zapachów, zatrzymałbym się i ponownie ocenił. Sugeruje, że możesz być w stanie stworzyć lepszy projekt OO lub że może powinieneś pójść w innym kierunku i całkowicie uniknąć tego problemu.


2

Kiedyś przechodziłem między klasami za pomocą kilku statycznych metod i singletona. Oba rozwiązują problem, ale singleton można znacznie łatwiej zastąpić więcej niż jednym. (Programiści zawsze wydają się być tak pewni, że będzie tylko 1 coś, a myliłem się wystarczająco dużo razy, aby całkowicie porzucić metody statyczne, z wyjątkiem niektórych bardzo ograniczonych przypadków).

W każdym razie singleton daje możliwość późniejszego przekazania czegoś do fabryki, aby uzyskać inną instancję, a to zmienia zachowanie całego programu bez refaktoryzacji. Zmiana globalnej klasy metod statycznych na coś z innymi danymi „bazowymi” lub nieco innym zachowaniem (klasa potomna) jest głównym problemem.

Metody statyczne nie mają podobnej przewagi.

Więc tak, są złe.


1

Dopóki nie wchodzi w grę stan wewnętrzny, to jest w porządku. Należy zauważyć, że zwykle metody statyczne powinny być bezpieczne dla wątków, więc jeśli używasz pomocniczych struktur danych, używaj ich w sposób bezpieczny dla wątków.


1

Jeśli wiesz, że nigdy nie będziesz musiał używać wewnętrznego stanu C, to jest w porządku. Gdyby to jednak kiedykolwiek się zmieniło w przyszłości, należałoby uczynić metodę niestatyczną. Jeśli na początku nie jest statyczny, możesz po prostu zignorować stan wewnętrzny, jeśli go nie potrzebujesz.


1

Jeśli jest to metoda narzędziowa, dobrze jest uczynić ją statyczną. Guawa i Apache Commons są zbudowane na tej zasadzie.

Moja opinia na ten temat jest czysto pragmatyczna. Jeśli jest to kod Twojej aplikacji, metody statyczne generalnie nie są najlepszym rozwiązaniem. Metody statyczne mają poważne ograniczenia w testowaniu jednostkowym - nie można ich łatwo wyszydzić: nie można wstrzyknąć fałszywej funkcjonalności statycznej do innego testu. Zwykle nie można również wstrzyknąć funkcjonalności do metody statycznej.

Tak więc w mojej logice aplikacji zwykle mam małe statyczne wywołania metod podobnych do narzędzi. To znaczy

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

jedną z korzyści jest to, że nie testuję takich metod :-)


1

Cóż, oczywiście nie ma srebrnej kuli. Klasy statyczne są odpowiednie dla małych narzędzi / pomocników. Ale używanie statycznych metod do programowania logiki biznesowej jest z pewnością złe. Rozważmy następujący kod

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

Widzisz statyczne wywołanie metody ItemsProcessor.SplitItem(newItem);It pachnie przyczyną

  • Nie masz zadeklarowanej jawnej zależności i jeśli nie zagłębisz się w kod, możesz przeoczyć sprzężenie między klasą a kontenerem metody statycznej
  • Nie można testować BusinessServiceizolując go od ItemsProcessor(większość narzędzi testowych nie mockuje klas statycznych) i uniemożliwia testowanie jednostkowe. Brak testów jednostkowych == niska jakość

0

Metody statyczne są ogólnie złym wyborem nawet w przypadku kodu bezstanowego. Zamiast tego utwórz klasę pojedynczą z tymi metodami, która jest tworzona raz i wstrzykiwana do tych klas, które chcą używać tych metod. Takie zajęcia są łatwiejsze do wyśmiewania i testowania. Są znacznie bardziej zorientowane obiektowo. W razie potrzeby możesz owinąć je proxy. Statyka znacznie utrudnia OO i nie widzę powodu, aby ich używać w prawie wszystkich przypadkach. Nie w 100%, ale prawie wszystkie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.