Jeśli używasz słowa kluczowego „statycznego” bez słowa kluczowego „końcowego”, powinien to być sygnał do dokładnego przemyślenia projektu. Nawet obecność „finału” nie jest wolnym przejściem, ponieważ zmienny statyczny obiekt końcowy może być równie niebezpieczny.
Oceniłbym, że w około 85% przypadków widzę „statyczny” bez „końcowego”, to jest NIEPRAWIDŁOWE. Często znajduję dziwne obejścia, aby ukryć lub ukryć te problemy.
Nie twórz statycznych zmiennych. Zwłaszcza kolekcje. Ogólnie rzecz biorąc, kolekcje powinny być inicjowane, gdy ich obiekt zawierający jest inicjowany i powinny być zaprojektowane w taki sposób, aby były resetowane lub zapominane, gdy ich obiekt zawierający zostanie zapomniany.
Używanie statyki może tworzyć bardzo subtelne błędy, które powodują ból inżynierów przez wiele dni. Wiem, ponieważ zarówno stworzyłem, jak i polowałem na te błędy.
Jeśli chcesz uzyskać więcej informacji, przeczytaj…
Dlaczego nie użyć statyki?
Istnieje wiele problemów ze statyką, w tym pisanie i wykonywanie testów, a także subtelne błędy, które nie są od razu oczywiste.
Kod oparty na obiektach statycznych nie może być łatwo przetestowany jednostkowo, a statyki nie można łatwo wyśmiewać (zwykle).
Jeśli używasz statyki, nie można zamienić implementacji klasy w celu przetestowania komponentów wyższego poziomu. Na przykład wyobraź sobie statycznego CustomerDAO, który zwraca obiekty klienta, które ładuje z bazy danych. Teraz mam klasę CustomerFilter, która musi uzyskać dostęp do niektórych obiektów klienta. Jeśli CustomerDAO jest statyczny, nie mogę napisać testu dla CustomerFilter bez uprzedniej inicjalizacji mojej bazy danych i wypełnienia użytecznych informacji.
A zapełnianie i inicjowanie baz danych zajmuje dużo czasu. Z mojego doświadczenia wynika, że twoja struktura inicjowania bazy danych zmienia się z czasem, co oznacza, że dane zmieniają się, a testy mogą ulec uszkodzeniu. IE, wyobraź sobie, że klient 1 był VIP-em, ale zmieniła się struktura inicjalizacji bazy danych, a teraz klient 1 nie jest już VIP-em, ale twój test został na stałe zakodowany, aby załadować klienta 1…
Lepszym rozwiązaniem jest utworzenie wystąpienia CustomerDAO i przekazanie go do CustomerFilter po jego zbudowaniu. (Jeszcze lepszym rozwiązaniem byłoby użycie Spring lub innej struktury Inversion of Control.
Gdy to zrobisz, możesz szybko wyśmiewać lub wyeliminować alternatywnego DAO w teście CustomerFilterTest, dzięki czemu masz większą kontrolę nad testem,
Bez statycznego DAO test będzie szybszy (bez inicjalizacji db) i bardziej niezawodny (ponieważ nie zawiedzie, gdy zmieni się kod inicjujący db). Na przykład w tym przypadku upewnienie się, że Klient 1 jest i zawsze będzie VIP-em, jeśli chodzi o test.
Wykonywanie testów
Statyka powoduje prawdziwy problem podczas jednoczesnego uruchamiania pakietów testów jednostkowych (na przykład z serwerem Continuous Integration). Wyobraź sobie statyczną mapę obiektów gniazd sieciowych, które pozostają otwarte między testami. Pierwszy test może otworzyć gniazdo na porcie 8080, ale zapomniałeś wyczyścić mapę, gdy test zostanie zerwany. Teraz, gdy uruchamia się drugi test, prawdopodobnie zawiesza się, gdy próbuje utworzyć nowe Socket dla portu 8080, ponieważ port jest nadal zajęty. Wyobraź sobie również, że odwołania do gniazd w Twojej kolekcji statycznej nie są usuwane i (z wyjątkiem WeakHashMap) nigdy nie kwalifikują się do odśmiecania, co powoduje wyciek pamięci.
Jest to przykład uogólniony, ale w dużych systemach problem ten zdarza się CAŁOŚĆ. Ludzie nie myślą o testach jednostkowych kilkakrotnie uruchamiających i zatrzymujących swoje oprogramowanie w tej samej maszynie JVM, ale jest to dobry test projektu oprogramowania, a jeśli masz aspiracje do wysokiej dostępności, musisz o tym wiedzieć.
Problemy te często pojawiają się w przypadku obiektów struktury, na przykład dostępu do bazy danych, buforowania, przesyłania wiadomości i rejestrowania. Jeśli korzystasz z Java EE lub najlepszych frameworków, prawdopodobnie wiele z nich zarządza, ale jeśli podobnie jak ja masz do czynienia ze starszym systemem, możesz mieć wiele niestandardowych ram dostępu do tych warstw.
Jeśli konfiguracja systemu mająca zastosowanie do tych komponentów struktury zmienia się między testami jednostkowymi, a struktura testów jednostkowych nie rozrywa i nie odbudowuje komponentów, zmiany te nie mogą zostać zastosowane, a gdy test opiera się na tych zmianach, nie powiodą się .
Problem ten dotyczą nawet komponentów innych niż frameworki. Wyobraź sobie statyczną mapę o nazwie OpenOrders. Piszesz jeden test, który tworzy kilka otwartych zamówień, i sprawdza, czy wszystkie są we właściwym stanie, a następnie test się kończy. Inny programista pisze drugi test, który umieszcza potrzebne zamówienia w mapie OpenOrders, a następnie potwierdza, że liczba zamówień jest dokładna. Uruchamiane indywidualnie, oba testy przeszłyby pomyślnie, ale jeśli zostaną uruchomione razem w pakiecie, nie powiedzie się.
Co gorsza, niepowodzenie może zależeć od kolejności, w której testy zostały uruchomione.
W takim przypadku, unikając statyki, unikasz ryzyka utrwalenia danych w różnych instancjach testowych, zapewniając lepszą niezawodność testu.
Subtelne błędy
Jeśli pracujesz w środowisku wysokiej dostępności lub w dowolnym miejscu, w którym wątki mogą być uruchamiane i zatrzymywane, ten sam problem, o którym mowa powyżej w przypadku zestawów testów jednostkowych, może mieć zastosowanie, gdy kod działa również w środowisku produkcyjnym.
W przypadku wątków zamiast używać obiektu statycznego do przechowywania danych, lepiej jest użyć obiektu zainicjowanego podczas fazy uruchamiania wątku. W ten sposób za każdym razem, gdy wątek jest uruchamiany, tworzona jest nowa instancja obiektu (z potencjalnie nową konfiguracją), dzięki czemu unikasz danych z jednej instancji wątku krwawiącej do następnej.
Kiedy wątek umiera, obiekt statyczny nie jest resetowany lub śmieci są gromadzone. Wyobraź sobie, że masz wątek o nazwie „EmailCustomers”, a gdy się uruchomi, zapełni statyczną kolekcję Ciągów listą adresów e-mail, a następnie rozpocznie wysyłanie wiadomości e-mail do każdego z tych adresów. Powiedzmy, że wątek został w jakiś sposób przerwany lub anulowany, więc struktura wysokiej dostępności ponownie uruchamia wątek. Następnie, gdy wątek się uruchamia, przeładowuje listę klientów. Ponieważ jednak kolekcja jest statyczna, może zachować listę adresów e-mail z poprzedniej kolekcji. Teraz niektórzy klienci mogą otrzymywać zduplikowane wiadomości e-mail.
Na bok: finał statyczny
Użycie „static static” jest faktycznie odpowiednikiem języka Java dla C #define, chociaż istnieją techniczne różnice w implementacji. AC / C ++ #define jest zamieniany z kodu przez preprocesor przed kompilacją. „Statyczny finał” Java zakończy się rezydencją pamięci na stosie. W ten sposób jest bardziej podobny do zmiennej „static const” w C ++ niż do #define.
Podsumowanie
Mam nadzieję, że pomoże to wyjaśnić kilka podstawowych powodów, dla których statyka jest problematyczna. Jeśli korzystasz z nowoczesnego frameworka Java, takiego jak Java EE lub Spring itp., Możesz nie spotkać wielu z tych sytuacji, ale jeśli pracujesz z dużą ilością starszego kodu, mogą one stać się znacznie częstsze.