Czy nic, co mutuje, ostatecznie nie manipuluje stanem?
Tak, ale jeśli stoi za funkcją członka małej klasy, która jest jedynym bytem w całym systemie, który może manipulować jego stanem prywatnym, to stan ten ma bardzo wąski zakres.
Co powinieneś mieć do czynienia z jak najmniejszym stanem, jak to możliwe?
Z punktu widzenia zmiennej: jak najmniej wierszy kodu powinno mieć do niej dostęp. Zawęź zakres zmiennej do minimum.
Z punktu widzenia kodu: z tego wiersza kodu powinno być jak najmniej dostępnych zmiennych. Zawęź liczbę zmiennych, do których wiersz kodu może mieć dostęp (nawet nie ma znaczenia, czy ma do niego dostęp, liczy się tylko to, czy może ).
Zmienne globalne są tak złe, ponieważ mają maksymalny zasięg. Nawet jeśli są dostępne z 2 linii kodu w bazie kodu, z linii POV kodu, zmienna globalna jest zawsze dostępna. Z POV zmiennej zmienna globalna z zewnętrznym łączem jest dostępna dla każdego wiersza kodu w całej bazie kodu (lub każdego wiersza kodu, który i tak zawiera nagłówek). Pomimo, że faktycznie są dostępne tylko 2 linie kodu, jeśli zmienna globalna jest widoczna dla 400 000 linii kodu, twoja bezpośrednia lista podejrzanych, gdy okaże się, że była ustawiona na nieprawidłowy stan, będzie zawierała 400 000 pozycji (być może szybko zredukowana do 2 wpisy z narzędziami, ale jednak bezpośrednia lista będzie zawierać 400 000 podejrzanych i nie jest to zachęcający punkt wyjścia).
Istnieją również szanse, że nawet jeśli zmienna globalna zacznie być modyfikowana tylko przez 2 wiersze kodu w całej bazie kodu, niefortunna tendencja baz kodu do ewolucji do tyłu będzie miała tendencję do drastycznego wzrostu tej liczby, po prostu dlatego, że może wzrosnąć o tyle programiści, pragnący dotrzymać terminów, widzą tę globalną zmienną i zdają sobie sprawę, że mogą przez nią przejść na skróty.
Czy w nieczystym języku, takim jak C ++, zarządzanie stanem nie jest naprawdę tym, co robisz?
W dużej mierze tak, chyba że używasz C ++ w bardzo egzotyczny sposób, który ma do czynienia z niestandardowymi niezmiennymi strukturami danych i czystym programowaniem funkcjonalnym w całym tekście - często jest również źródłem większości błędów, gdy zarządzanie stanem staje się złożone, a złożoność jest często funkcja widoczności / ekspozycji tego stanu.
A jakie są inne sposoby radzenia sobie z tak małym stanem, jak to możliwe, poza ograniczaniem zmiennej żywotności?
Wszystko to ogranicza zakres zmiennej, ale można to zrobić na wiele sposobów:
- Unikaj surowych zmiennych globalnych, takich jak plaga. Nawet jakaś głupia globalna funkcja ustawiająca / pobierająca drastycznie zawęża widoczność tej zmiennej i przynajmniej pozwala na pewien sposób utrzymywania niezmienników (np. Jeśli zmienna globalna nigdy nie powinna mieć wartości ujemnej, ustawiający może zachować tę niezmiennik). Oczywiście, nawet konstrukcja setera / gettera na czymś, co w innym przypadku byłaby zmienną globalną, jest dość kiepskim projektem, chodzi mi o to, że wciąż jest o wiele lepszy.
- Jeśli to możliwe, zmniejszaj swoje klasy. Klasa z setkami funkcji składowych, 20 zmiennymi składowymi i 30 000 wierszy kodu implementującymi ją miałaby raczej „globalne” zmienne prywatne, ponieważ wszystkie te zmienne byłyby dostępne dla jej funkcji składających się z 30 000 wierszy kodu. Można powiedzieć, że „złożoność stanu” w tym przypadku, pomijając zmienne lokalne w każdej funkcji składowej, wynosi
30,000*20=600,000
. Gdyby oprócz tego dostępnych było 10 zmiennych globalnych, złożoność stanu mogłaby być podobna 30,000*(20+10)=900,000
. Zdrowa „złożoność stanu” (moja osobista wymyślona miara) powinna mieścić się w tysiącach lub poniżej klas, a nie dziesiątek tysięcy, a na pewno nie setek tysięcy. W przypadku bezpłatnych funkcji, powiedz setki lub mniej, zanim zaczniemy mieć poważne problemy z utrzymaniem.
- W tym samym duchu, co powyżej, nie implementuj czegoś jako funkcji członka lub funkcji znajomego, która w innym przypadku mogłaby być nieczłonkowska, nieprzyjazna, używając tylko publicznego interfejsu klasy. Takie funkcje nie mają dostępu do prywatnych zmiennych klasy, a tym samym zmniejszają ryzyko wystąpienia błędu poprzez ograniczenie zakresu tych zmiennych prywatnych.
- Unikaj deklarowania zmiennych na długo zanim faktycznie będą potrzebne w funkcji (tj. Unikaj starszego stylu C, który deklaruje wszystkie zmienne na górze funkcji, nawet jeśli potrzebne są tylko wiele wierszy poniżej). Jeśli mimo wszystko korzystasz z tego stylu, staraj się przynajmniej stosować krótsze funkcje.
Poza zmiennymi: efekty uboczne
Wiele z wyżej wymienionych wskazówek dotyczy bezpośredniego dostępu do surowego, zmiennego stanu (zmiennych). Jednak w wystarczająco złożonej bazie kodu samo zawężenie zakresu surowych zmiennych nie wystarczy, aby łatwo uzasadnić poprawność.
Mógłbyś, powiedzmy, centralną strukturę danych, za całkowicie SOLIDNYM, abstrakcyjnym interfejsem, w pełni zdolnym do perfekcyjnego utrzymywania niezmienników, i wciąż skończyłoby się dużym żalem z powodu szerokiego narażenia na ten centralny stan. Przykładem stanu centralnego, który niekoniecznie jest globalnie dostępny, ale jedynie powszechnie dostępny, jest wykres centralnej sceny silnika gry lub struktura danych warstwy centralnej programu Photoshop.
W takich przypadkach idea „stanu” wykracza poza surowe zmienne, a jedynie do struktur danych i tego rodzaju rzeczy. Pomaga również zmniejszyć ich zasięg (zmniejszyć liczbę linii, które mogą wywoływać funkcje, które pośrednio je mutują).
Zwróć uwagę, jak celowo oznaczyłem tutaj nawet interfejs jako czerwony, ponieważ z szerokiego, pomniejszonego poziomu architektonicznego dostęp do tego interfejsu wciąż mutuje, choć pośrednio. Klasa może utrzymywać niezmienniki w wyniku interfejsu, ale to idzie tak daleko, jak na naszą zdolność rozumowania o poprawności.
W tym przypadku centralna struktura danych kryje się za abstrakcyjnym interfejsem, który może nawet nie być globalnie dostępny. Może być po prostu wstrzyknięty, a następnie pośrednio zmutowany (poprzez funkcje składowe) z zestawu funkcji w złożonej bazie kodu.
W takim przypadku, nawet jeśli struktura danych doskonale utrzymuje własne niezmienniki, dziwne rzeczy mogą się zdarzyć na szerszym poziomie (np. Odtwarzacz audio może utrzymywać wszelkiego rodzaju niezmienniki, tak że poziom głośności nigdy nie wykracza poza zakres 0% do 100%, ale to nie chroni go przed naciśnięciem przycisku odtwarzania i posiadaniem losowego klipu audio innego niż ten, który ostatnio załadował, rozpoczyna odtwarzanie jako zdarzenie, które powoduje przetasowanie listy odtwarzania w prawidłowy sposób, ale wciąż niepożądane, błędne zachowanie z szerokiej perspektywy użytkownika).
Sposobem na ochronę się w tych złożonych scenariuszach jest „wąskie gardło” miejsc w bazie kodu, które mogą wywoływać funkcje, które ostatecznie powodują zewnętrzne skutki uboczne, nawet z tego rodzaju szerszego widoku systemu, który wykracza poza stan surowy i interfejsy.
Choć wygląda to dziwnie, widać, że w wielu miejscach nie ma dostępu do „stanu” (pokazanego na czerwono, a to nie oznacza „surowej zmiennej”, oznacza tylko „obiekt” i być może nawet za abstrakcyjnym interfejsem) . Każda z funkcji ma dostęp do stanu lokalnego, który jest również dostępny dla centralnego aktualizatora, a stan centralny jest dostępny tylko dla centralnego aktualizatora (co sprawia, że nie jest już centralny, ale raczej lokalny).
Dotyczy to tylko naprawdę złożonych baz kodu, takich jak gra, która obejmuje 10 milionów linii kodu, ale może ogromnie pomóc w uzasadnieniu poprawności oprogramowania i stwierdzeniu, że zmiany dają przewidywalne wyniki, gdy znacznie ograniczysz / wąskie gardło liczby miejsc, które mogą mutować stany krytyczne, wokół których obraca się cała architektura, aby działać poprawnie.
Poza surowymi zmiennymi są zewnętrzne skutki uboczne, a zewnętrzne skutki uboczne są źródłem błędów, nawet jeśli ograniczają się do kilku funkcji składowych. Jeśli mnóstwo funkcji może bezpośrednio wywołać te garstkę funkcji składowych, oznacza to, że w systemie jest mnóstwo funkcji, które mogą pośrednio powodować zewnętrzne skutki uboczne, co zwiększa złożoność. Jeśli jest tylko jedno miejsce w bazie kodu, które ma dostęp do tych funkcji składowych i ta jedna ścieżka wykonania nie jest uruchamiana przez sporadyczne zdarzenia w całym miejscu, ale jest wykonywana w bardzo kontrolowany, przewidywalny sposób, to zmniejsza złożoność.
Złożoność państwa
Nawet złożoność stanu jest raczej ważnym czynnikiem, który należy wziąć pod uwagę. Prosta struktura, szeroko dostępna za abstrakcyjnym interfejsem, nie jest trudna do zepsucia.
Złożona struktura danych wykresu, która reprezentuje logiczną reprezentację złożonej architektury, jest dość łatwa do zepsucia i w sposób, który nawet nie narusza niezmienników wykresu. Wykres jest wielokrotnie bardziej złożony niż prosta struktura, dlatego w takim przypadku staje się jeszcze bardziej istotne w celu zmniejszenia postrzeganej złożoności bazy kodu w celu zmniejszenia liczby miejsc, które mają dostęp do takiej struktury wykresu do absolutnego minimum, i gdzie taka strategia „centralnego aktualizatora”, która odwraca się do paradygmatu ściągania w celu uniknięcia sporadycznych, bezpośrednie wypychanie do struktury danych wykresu z całego miejsca może naprawdę się opłacić.