Autor Wątek: Co zamiast zmiennych globalnych?  (Przeczytany 3848 razy)

Offline Liosan

  • Moderator

# Styczeń 05, 2012, 00:17:17
Innym argumentem przeciwko zmiennym globalnym jest trudnośc w testowaniu. W momencie gdy masz 50 zmiennych globalnych wpływających na daną klase bardzo trudno jest pisac testy jednostkowe. Nigdy nie jesteś do końca pewien, czy odpowiednio ustawiłeś te wszystkie globale przed odpaleniem testu. Gdy wszystko jest ustawiane w konstruktorze i listach argumentów metod dokładnie wiesz z czego dana klasa korzysta.
To też można schrzanić... Jak tych parametrów się uzbiera 6, i te 6 jest takich samych w 38 wywołaniach, to przyjdzie pomysł żeby to zgrupować w jakiś GlobalState, Context czy inny Support... i zaraz się okazuje, że mamy ten sam problem co wcześniej, tylko w parametrze :)

Liosan

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline vashpan

  • Użytkownik
    • Strona

# Styczeń 05, 2012, 12:05:28
@vashpan A przypadkiem aplikację na androida nie pisze się w Javie, w której nie ma wskaźników?

O tym ze wskaznikow jest mnostwo napisal juz owyn, ale na Androida mozna pisac tez w C++ i mysle ze chyba o taka sytuacje chodzilo Korialtrashowi ? Bo w Javie nie ma zmiennych globalnych w sensie stricte ( ale mozna sobie wrzucic n-zmiennych 'public static' do jakiejs klasy i 'voila' ) Inna opcja jest taka ze Korialtrash pyta sie rzeczywiscie o Jave ;) Wtedy 90% postow w tym watku nie ma sensu, ale nie zaznaczyl tego... ( Android nie implikuje Javy )

Offline siso

  • Użytkownik

# Styczeń 06, 2012, 00:16:03
Podstawową wadą globali jest to, co już padło w tym wątku: poważne utrudnienia w testowaniu. W javie można jechać oczywiscie na globalach, widziałem takie wynalazki kilka razy, ale takiego kodu nie da się rozwijać w TDD. Pierwsze, co tam należy zrobić, to właśnie usunięcie global state'u :) Jest to jedna z technik zwanych ogólnie "rozcinaniem zależności". A wywołanie globalne tworzy bardzo silne zależności pomiędzy partiami kodu, które tego globala definiują i które go wywołują. Przekazywanie natomiast zależności do konstruktora obiektu to bardzo dobra technika.

Jeśli chce się zaprojektować dobry system zarządzania zasobami, warto się wzorować na Javie EE. Mimo tego, nawet, że niewiele ma to wspólnego z gamedevem.
Występuje tam pojęcie tzw. kontenera, w którym umieszczane są beany. Taki bean będzie naszym zasobem. Kontener kontroluje cykl życia zasobu, czyli wie kiedy go ma stworzyć, kiedy zniszczyć, kiedy pasywować i kiedy aktywować. My możemy w dowolnym momencie poprosić kontener o zasób i otrzymać go, nie wnikając w to, skąd on pochodzi i jak ma zostać skonstruowany. Nie na tym etapie. Jedyne, co nam potrzebne, to nazwa zasobu. Można to też nazwać adresem, jak kto woli.

Jak prosić o zasób? Na przykład za pomocą jakiegoś dostawcy zasobów. Skąd pobrać dostawcę? Z produkującej go fabryki.
Dlaczego tak?
1) Jeśli potrzebny jest dostęp globalny do zasobu, musi istnieć mechanizm, który potrafi ten zasób wstrzyknąć w dowolnym miejscu w aplikacji. Ale ponieważ walczymy z globalami na korzyść prostoty testowania, tenże dostawca nie powinien sam być globalny. Powinniśmy go przekazywać jako parametr tam, gdzie jest potrzebny. Dopiero kiedy to przekazywanie miałoby się stać uciążliwe, możemy uciec się do wywołań statycznej fabryki, która nam takiego dostawcę zwróci. I to w zasadzie byłoby jedyne statyczne wywołanie w kodzie. Bo nie należy utrudniać sobie życia tylko dlatego, żeby za wszelką cenę nie było statycznych calli w kodzie.
2) Dostawca zasobów zna kontekst nazewnictwa aplikacji i potrafi zidentyfikować zasób po nazwie. Mając dostawcę i nazwę, potrafimy zawsze dotrzeć do zasobu w taki sam sposób. Dostawca zasobów współpracuje z kontenerem.
3) Jak już napisałem, kontener panuje w całości nad cyklem życia zasobów. Cykl życia kontenera pokrywa się z cyklem życia aplikacji. Pozyskiwanie zasobów nie jest możliwe zanim kontener się zainicjalizuje i zanim nie zostaną zainicjalizowane zasoby, które mają być dostępne bez opóźnień. Reszta może stosować strategię lazy loading.
4) Zasoby mogą być umieszczane w kontekstach o różnej długości cyklu życia. Klienci dostawcy zasobów mogą kontrolować te konteksty właśnie za jego pomocą.
5) Nie wszystkie zasoby muszą być singletonami. Czasami dobrze jest mieć pulę zasobów danego typu widzianą jako singleton przez kod kliencki (tak działają np. różnej maści connections).
6) Aktywacja i pasywacja to w rzeczywistości odpowiednio deserializacja i serializacja zasobu w jakimś skojarzonym z kontenerem składzie. Nazwijmy go store, bo lepiej brzmi ;) Mechaniz ten przydaje się, gdy serwujemy zasoby z puli. Ponieważ uogólnienie singletona na pulę pozwala wprowadzić stan do takiego zasobu, czasami może się przydać przechowanie jakiegoś aktualnie nieużywanego zasobu w storze, kiedy brakuje wolnych w puli, i przywrócenie go do życia, kiedy znów jest potrzebny.

Powyższy opis to tylko kilka ficzerów wybranych z JEE pod kątem zarządzania zasobami. W prostych aplikacjach na pewno nie będzie się opłacało stosować tego wszystkiego, na pewno da się znaleźć prostsze sposoby. Nawet te nieszczęsne globale, jeśli miałoby ich być kilka. Ale w sytuacji gdy zaczynamy mówić o dziesiątkach globali, warto rozważyć jakiś sprytniejszy mechanizm, bo w pewnym momencie okaże się, że mamy tak ciasno powiązany kod, że już nic z nim nie idzie zrobić.

Offline Shelim

  • Użytkownik
    • Homepage

# Styczeń 06, 2012, 09:26:47
Wyjaśniając, chodziło mi oczywiście o pisanie w C++ na Androida :)

@siso -> dziękuję za obszernego posta, jedna rzecz jest niejasna: jak zaimplementować w takim wypadku ekran loading jeżeli zasoby są ładowane on-the-need basis?

Offline siso

  • Użytkownik

# Styczeń 06, 2012, 15:07:37
Nie ma sprawy, cała przyjemność po mojej stronie ;)

Loading screen mógłby być kontrolowany przez dostawcę zasobu jako niezależny efekt podłączany z zewnątrz. Wówczas można by dla różnych typów zasobów podłączać różne loading screeny. Na andku można to zrobić przez activity albo dialog z jakimś półprzezroczystym tłem. Zrobiliśmy tak kiedyś dla oczekiwania na synchroniczną odpowiedź serwera (też jest to pewnie rodzaj zasobu) i sprawdzało się bardzo dobrze. (Chociaż w tym wypadku najpewniej lepszym pomysłem jest asynchroniczny callback).

Musi być tylko spełniony podstawowy warunek - dostawca zasobu musi w pełni kontrolować te efekty, żeby się nie okazało, że nie można efektu zakończyć, bo polecenie zamykające poszło zanim efekt się wykonał. Groziłoby to wiszącymi efektami, swego rodzaju zombie screenami :)