Jestem w projekcie systemu rozproszonego napisanym w Javie, w którym mamy klasy odpowiadające bardzo złożonym obiektom biznesowym w świecie rzeczywistym. Obiekty te mają wiele metod odpowiadających działaniom, które użytkownik (lub inny agent) może zastosować do tych obiektów. W rezultacie klasy te stały się bardzo złożone.
Ogólne podejście do architektury systemu doprowadziło do wielu zachowań skoncentrowanych na kilku klasach i wielu możliwych scenariuszach interakcji.
Jako przykład i dla jasności, powiedzmy, że Robot i Samochód były zajęciami w moim projekcie.
Tak więc w klasie Robot miałbym wiele metod według następującego wzoru:
- sen(); isSleepAvaliable ();
- obudzić(); isAwakeAvaliable ();
- spacer (kierunek); isWalkAvaliable ();
- strzelanie (kierunek); isShootAvaliable ();
- turnOnAlert (); isTurnOnAlertAvailable ();
- turnOffAlert (); isTurnOffAlertAvailable ();
- ładowanie (); isRechargeAvailable ();
- powerOff (); isPowerOffAvailable ();
- stepInCar (samochód); isStepInCarAvailable ();
- stepOutCar (samochód); isStepOutCarAvailable ();
- samozniszczenia(); isSelfDestructAvailable ();
- umierać(); isDieAvailable ();
- żyje(); jest obudzony(); isAlertOn (); getBatteryLevel (); getCurrentRidingCar (); getAmmo ();
- ...
W klasie samochodów byłoby podobnie:
- włączyć(); isTurnOnAvaliable ();
- wyłączyć(); isTurnOffAvaliable ();
- spacer (kierunek); isWalkAvaliable ();
- tankować(); isRefuelAvailable ();
- samozniszczenia(); isSelfDestructAvailable ();
- wypadek(); isCrashAvailable ();
- isOperational (); isOn (); getFuelLevel (); getCurrentPassenger ();
- ...
Każdy z nich (Robot i samochód) jest zaimplementowany jako maszyna stanu, w której niektóre działania są możliwe w niektórych stanach, a niektóre nie. Akcje zmieniają stan obiektu. Metody akcji rzucają, IllegalStateException
gdy są wywoływane w niepoprawnym stanie, a isXXXAvailable()
metody informują, czy akcja jest możliwa w danym momencie. Chociaż niektórych można łatwo wydedukować ze stanu (np. W stanie uśpienia dostępna jest funkcja czuwania), niektóre nie są (aby strzelać, musi być przebudzona, żywa, mając amunicję i nie jeżdżąc samochodem).
Co więcej, interakcje między obiektami są również złożone. Na przykład samochód może pomieścić tylko jednego pasażera robota, więc jeśli inny spróbuje wsiąść, należy zgłosić wyjątek; W przypadku awarii samochodu pasażer powinien umrzeć; Jeśli robot nie żyje w pojeździe, nie może wyjść, nawet jeśli sam Samochód jest w porządku; Jeśli robot jest w samochodzie, nie może wejść do innego przed wysiadaniem; itp.
Wynikiem tego jest, jak już powiedziałem, zajęcia te stały się naprawdę złożone. Co gorsza, istnieją setki możliwych scenariuszy interakcji Robota i Samochodu. Ponadto znaczna część tej logiki wymaga dostępu do zdalnych danych w innych systemach. W rezultacie testy jednostkowe stały się bardzo trudne i mamy wiele problemów z testowaniem, z których jeden powoduje drugie w błędnym kole:
- Konfiguracje przypadków testowych są bardzo złożone, ponieważ muszą stworzyć znacznie bardziej złożony świat do ćwiczeń.
- Liczba testów jest ogromna.
- Uruchomienie zestawu testowego zajmuje kilka godzin.
- Nasz zasięg testów jest bardzo niski.
- Kod testowy ma tendencję do pisania tygodni lub miesięcy później niż kod, który testuje lub wcale.
- Wiele testów jest również zepsutych, głównie dlatego, że zmieniły się wymagania testowanego kodu.
- Niektóre scenariusze są tak złożone, że przekroczą limit czasu podczas instalacji (skonfigurowaliśmy limit czasu w każdym teście, w najgorszych przypadkach 2 minuty, a nawet tak długo, limity czasu zapewniły, że nie jest to nieskończona pętla).
- Błędy regularnie wpadają do środowiska produkcyjnego.
Ten scenariusz robota i samochodu to rażące nadmierne uproszczenie tego, co mamy w rzeczywistości. Oczywiście ta sytuacja nie jest możliwa do opanowania. Proszę więc o pomoc i sugestie dotyczące: 1, Zmniejszenia złożoności zajęć; 2. Uprość scenariusze interakcji między moimi obiektami; 3. Skróć czas testu i ilość kodu do przetestowania.
EDYCJA:
Myślę, że nie miałem jasności co do automatów państwowych. Robot sam jest maszyną stanową, a stany „śpią”, „budzą się”, „ładują”, „martwe” itp. Samochód to kolejna maszyna stanowa.
EDYCJA 2: W przypadku, gdy jesteś ciekawy, czym naprawdę jest mój system, klasy, które oddziałują, to takie jak Serwer, Adres IP, Dysk, Kopia zapasowa, Użytkownik, Licencja oprogramowania, itd. Scenariusz Robot i samochód to tylko przypadek, który znalazłem to byłoby wystarczająco proste, aby wyjaśnić mój problem.
źródło
Odpowiedzi:
State wzornictwo może być przydatne, jeśli nie jesteś już go używać.
Główną ideą jest to, że tworzą wewnętrzną klasę dla każdego odrębnego państwa - tak, aby kontynuować swoje przykład
SleepingRobot
,AwakeRobot
,RechargingRobot
iDeadRobot
że wszystko będzie klas, wdrożenie wspólnego interfejsu.Metody
Robot
klasy (jaksleep()
iisSleepAvaliable()
) mają proste implementacje, które delegują do bieżącej klasy wewnętrznej.Zmiany stanu są realizowane przez zamianę bieżącej klasy wewnętrznej na inną.
Zaletą tego podejścia jest to, że każda z klas stanów jest znacznie prostsza (ponieważ reprezentuje tylko jeden możliwy stan) i może być niezależnie testowana. W zależności od języka implementacji (nieokreślony), nadal możesz być zmuszony do posiadania wszystkiego w tym samym pliku lub możesz być w stanie podzielić rzeczy na mniejsze pliki źródłowe.
źródło
Nie znam twojego kodu, ale na przykładzie metody „uśpienia” założę, że jest to coś podobnego do następującego „uproszczonego” kodu:
Myślę, że trzeba zrobić różnicę między integracji badań i testów jednostkowych . Napisanie testu przebiegającego przez cały stan maszyny jest z pewnością dużym zadaniem. Pisanie mniejszych testów jednostkowych, które sprawdzają, czy metoda snu działa prawidłowo, jest łatwiejsze. W tym momencie nie musisz wiedzieć, czy stan maszyny został poprawnie zaktualizowany lub czy „samochód” poprawnie zareagował na fakt, że stan maszyny został zaktualizowany przez „robota” ... itd., Otrzymujesz go.
Biorąc pod uwagę powyższy kod, wyśmiewałbym obiekt „machineState”, a moim pierwszym testem byłoby:
Osobiście uważam, że pisanie tak małych testów jednostkowych powinno być pierwszą rzeczą do zrobienia. Napisałeś to:
Uruchamianie tych małych testów powinno być bardzo szybkie i nie powinieneś mieć nic do zainicjowania wcześniej, jak „złożony świat”. Na przykład, jeśli jest to aplikacja oparta na kontenerze IOC (powiedzmy Spring), nie trzeba inicjalizować kontekstu podczas testów jednostkowych.
Po pokryciu znacznego odsetka złożonego kodu testami jednostkowymi możesz zacząć budować bardziej czasochłonne i bardziej złożone testy integracyjne.
Na koniec można to zrobić niezależnie od tego, czy kod jest złożony (jak powiedziałeś, że jest teraz), czy po dokonaniu refaktoryzacji.
źródło
Czytałem sekcję „Pochodzenie” artykułu w Wikipedii na temat zasady segregacji interfejsów i przypomniałem sobie o tym pytaniu.
Zacytuję ten artykuł. Problem: „… jedna główna klasa Job… gruba klasa z mnóstwem metod specyficznych dla różnych klientów”. Rozwiązanie: „... warstwa interfejsów między klasą Job a wszystkimi jej klientami ...”
Twój problem wydaje się być permutacją tej, którą miał Xerox. Zamiast jednej grubej klasy masz dwie, a te dwie tłuste klasy rozmawiają ze sobą zamiast wielu klientów.
Czy możesz pogrupować metody według rodzaju interakcji, a następnie utworzyć klasę interfejsu dla każdego typu? Na przykład: RobotPowerInterface, RobotNavigationInterface, RobotAlarmInterface?
źródło