Jak zmienna wprowadza stan?

11

Czytałem „Standardy kodowania C ++” i ten wiersz tam był:

Zmienne wprowadzają stan i powinieneś poradzić sobie z jak najmniejszym stanem, przy tak krótkim okresie życia.

Czy nic, co mutuje, ostatecznie nie manipuluje stanem? Co powinieneś mieć do czynienia z jak najmniejszym stanem, jak to możliwe ?

Czy w nieczystym języku, takim jak C ++, zarządzanie stanem nie jest naprawdę tym, co robisz? A jakie są inne sposoby radzenia sobie z tak małym stanem, jak to możliwe, poza ograniczaniem zmiennej żywotności?

kunj2aaan
źródło

Odpowiedzi:

16

Czy żadna zmienna rzecz tak naprawdę nie manipuluje stanem?

Tak.

A co oznacza „powinieneś poradzić sobie z małym stanem”?

Oznacza to, że mniej stanu jest lepsze niż więcej stanu. Więcej stanu ma tendencję do wprowadzania większej złożoności.

Czy w nieczystym języku, takim jak C ++, zarządzanie stanem nie jest tak naprawdę tym, co robisz?

Tak.

Jakie są inne sposoby „radzenia sobie z małym stanem” poza ograniczaniem zmiennej żywotności?

Zminimalizuj liczbę zmiennych. Izoluj kod, który manipuluje jakimś stanem, w samodzielną jednostkę, aby inne sekcje kodu mogły go zignorować.

David Schwartz
źródło
9

Czy żadna zmienna rzecz tak naprawdę nie manipuluje stanem?

Tak. W C ++ jedyne zmienne rzeczy to (nie const) zmienne.

A co oznacza „powinieneś poradzić sobie z małym stanem”?

Im mniej stanu ma program, tym łatwiej zrozumieć jego działanie. Dlatego nie powinieneś wprowadzać stanu, który nie jest potrzebny, i nie powinieneś go utrzymywać, gdy już go nie potrzebujesz.

Czy w nieczystym języku, takim jak C ++, zarządzanie stanem nie jest tak naprawdę tym, co robisz?

W języku opartym na wielu paradygmatach, takim jak C ++, często istnieje wybór między „czystym” podejściem funkcjonalnym lub podejściem opartym na stanie lub rodzajem hybrydy. Historycznie obsługa języków dla programowania funkcjonalnego była dość słaba w porównaniu do niektórych języków, ale poprawia się.

Jakie są inne sposoby „radzenia sobie z małym stanem” poza ograniczaniem zmiennej żywotności?

Ogranicz zakres i czas życia, aby zmniejszyć sprzężenie między obiektami; faworyzuj lokalne, a nie globalne zmienne oraz prywatne, a nie publiczne elementy obiektu.

Mike Seymour
źródło
5

stan oznacza, że ​​coś jest gdzieś przechowywane , abyś mógł się do niego później odwoływać.

Utworzenie zmiennej tworzy miejsce do przechowywania niektórych danych. Te dane to stan Twojego programu.

Używasz go do robienia rzeczy, zmieniania go, obliczania itp.

To jest stan , podczas gdy rzeczy, które robisz , nie są stanem.

W języku funkcjonalnym przeważnie zajmujesz się tylko funkcjami i przekazywaniem funkcji tak, jakby były obiektami. Chociaż funkcje te nie mają stanu, a przekazywanie funkcji dookoła, nie wprowadza żadnego stanu (poza być może wewnątrz samej funkcji).

W C ++ możesz tworzyć obiekty funkcji , które są structlub classtypy, które zostały operator()()przeciążone. Te obiekty funkcji mogą mieć stan lokalny, chociaż niekoniecznie jest to współużytkowane przez inny kod w twoim programie. Funkcje (tj. Obiekty funkcyjne) są bardzo łatwe do przekazania. Jest to tak blisko, jak można naśladować funkcjonalny paradygmat w C ++. (AFAIK)

Niewielki lub żaden stan oznacza, że ​​możesz łatwo zoptymalizować program do równoległego wykonywania, ponieważ nic nie może być współużytkowane przez wątki lub procesory, więc nie można tworzyć sprzeczek i nic, co musisz chronić przed wyścigami danych itp.

Tony Lew
źródło
2

Inni udzielili dobrych odpowiedzi na pierwsze 3 pytania.

A jakie są inne sposoby na „radzenie sobie z możliwie najmniejszym stanem” poza ograniczaniem zmiennej długości życia?

Kluczowa odpowiedź na pytanie nr 1 brzmi: tak, wszystko, co mutuje, ostatecznie wpływa na stan. Kluczem jest zatem, aby nie mutować rzeczy. Niezmienne typy, wykorzystujące funkcjonalny styl programowania, w którym wynik jednej funkcji jest przekazywany bezpośrednio do drugiej i nie jest przechowywany, przekazując wiadomości lub zdarzenia bezpośrednio zamiast zapisywania stanu, obliczania wartości zamiast przechowywania i aktualizacji ...

W przeciwnym razie ograniczymy wpływ stanu; przez widoczność lub przez cały okres użytkowania.

Telastyn
źródło
1

A co oznacza „powinieneś poradzić sobie z małym stanem”?

Oznacza to, że twoje klasy powinny być jak najmniejsze, optymalnie reprezentując pojedynczą abstrakcję. Jeśli umieścisz 10 zmiennych w swojej klasie, najprawdopodobniej robisz coś złego i powinieneś zobaczyć, jak refaktoryzować swoją klasę.

BЈовић
źródło
1

Aby zrozumieć, jak działa program, musisz zrozumieć jego zmiany stanu. Im mniej masz stanu i im bardziej lokalny jest kod, który go używa, tym łatwiej.

Jeśli kiedykolwiek pracowałeś z programem, który miał dużą liczbę zmiennych globalnych, zrozumiałbyś pośrednio.

Mark Ransom
źródło
1

Stan to po prostu przechowywane dane. Każda zmienna jest naprawdę jakimś stanem, ale zwykle używamy „stanu” w odniesieniu do danych, które są trwałe między operacjami. Jako prosty, bezcelowy przykład, możesz mieć klasę, która wewnętrznie przechowuje inti ma increment()i decrement()funkcje członka. Tutaj wewnętrzną wartością jest stan, ponieważ utrzymuje się przez całe życie instancji tej klasy. Innymi słowy, wartością jest stan obiektu.

Idealnie stan zdefiniowany przez klasę powinien być jak najmniejszy przy minimalnej redundancji. Pomaga to twojej klasie w spełnieniu zasady pojedynczej odpowiedzialności , poprawia enkapsulację i zmniejsza złożoność. Stan obiektu powinien być całkowicie zamknięty w interfejsie tego obiektu. Oznacza to, że wynik dowolnej operacji na tym obiekcie będzie przewidywalny, biorąc pod uwagę semantykę obiektu. Możesz dodatkowo poprawić enkapsulację, minimalizując liczbę funkcji, które mają dostęp do stanu .

Jest to jeden z głównych powodów unikania globalnego stanu. Stan globalny może wprowadzić zależność dla obiektu bez wyrażania go przez interfejs, co powoduje, że stan ten jest ukryty przed użytkownikiem obiektu. Wywołanie operacji na obiekcie z globalną zależnością może mieć różne i nieprzewidywalne wyniki.

Joseph Mansfield
źródło
1

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ą).

wprowadź opis zdjęcia tutaj

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.

wprowadź opis zdjęcia tutaj

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ć.


źródło