Dlaczego skutki uboczne są uważane za złe w programowaniu funkcjonalnym?

69

Uważam, że skutki uboczne są zjawiskiem naturalnym. Ale jest to coś w rodzaju tabu w językach funkcjonalnych. Jakie są powody?

Moje pytanie jest specyficzne dla funkcjonalnego stylu programowania. Nie wszystkie języki programowania / paradygmaty.

Gulszan
źródło
6
Program bez efektów ubocznych jest bezużyteczny, więc skutki uboczne nie są ani złem, ani tabu. Ale FP powoduje ograniczenie kodu z efektami ubocznymi, więc tak duża część kodu, jak to możliwe, to funkcje wolne od skutków ubocznych. Zachęca się to, ponieważ funkcje i podsystemy pozbawione skutków ubocznych są łatwiejsze do zrozumienia, łatwiejsze do analizy, łatwiejsze do testowania i łatwiejsze do optymalizacji.
JacquesB
@JacquesB Byłoby dobrą odpowiedzią na wyjaśnienie, dlaczego są one łatwiejsze do zrozumienia, łatwiejsze do analizy, łatwiejsze do testowania i łatwiejsze do optymalizacji.
ceving,

Odpowiedzi:

72

Pisanie swoich funkcji / metod bez skutków ubocznych - więc są one czystymi funkcjami - ułatwia uzasadnienie poprawności twojego programu.

Ułatwia także komponowanie tych funkcji w celu tworzenia nowych zachowań.

Umożliwia to również pewne optymalizacje, w których kompilator może na przykład powtórzyć wyniki funkcji lub użyć funkcji Common Subexpression Elimination.

Edycja: na żądanie Benjola: Ponieważ duża część twojego stanu jest przechowywana na stosie (przepływ danych, a nie przepływ kontrolny, jak to tutaj nazwał Jonas ), możesz zrównoleglić lub inaczej zmienić kolejność wykonywania tych części obliczeń, które są niezależne od wzajemnie. Możesz łatwo znaleźć te niezależne części, ponieważ jedna część nie zapewnia danych wejściowych dla drugiej.

W środowiskach z debuggerami, które pozwalają wycofać stos i wznowić przetwarzanie (takie jak Smalltalk), posiadanie czystych funkcji oznacza, że ​​bardzo łatwo można zobaczyć, jak zmienia się wartość, ponieważ poprzednie stany są dostępne do wglądu. W obliczeniach obciążonych mutacjami, chyba że wyraźnie dodasz akcje do / cofnij do swojej struktury lub algorytmu, nie zobaczysz historii obliczeń. (To wiąże się z pierwszym akapitem: pisanie czystych funkcji ułatwia sprawdzenie poprawności programu.)

Frank Shearar
źródło
4
Może warto dodać coś do współbieżności w swojej odpowiedzi?
Benjol
5
Funkcje bez skutków ubocznych są łatwiejsze do przetestowania i ponownego użycia.
LennyProgrammers,
@ Lenny222: ponowne użycie było tym, o czym wspomniałem, mówiąc o składzie funkcji.
Frank Shearar,
@Frank: Ach, ok, zbyt płytkie przeglądanie. :)
LennyProgrammers
@ Lenny222: W porządku; prawdopodobnie dobrze jest to przeliterować.
Frank Shearar,
23

Z artykułu o programowaniu funkcjonalnym :

W praktyce aplikacje muszą mieć pewne skutki uboczne. Simon Peyton-Jones, główny współpracownik funkcjonalnego języka programowania Haskell, powiedział: „Ostatecznie każdy program musi manipulować stanem. Program, który nie ma żadnych skutków ubocznych, jest rodzajem czarnej skrzynki. Wszystko, co możesz powiedzieć, to że pudełko robi się cieplejsze ”. ( http://oscon.blip.tv/file/324976 ) Kluczem jest ograniczenie skutków ubocznych, wyraźna ich identyfikacja i uniknięcie rozproszenia ich w całym kodzie.

Peter Stuifzand
źródło
23

Pomyliłeś się, programowanie funkcjonalne promuje ograniczanie skutków ubocznych, aby programy były łatwe do zrozumienia i optymalizacji. Nawet Haskell pozwala pisać do plików.

Zasadniczo to, co mówię, to to, że programiści funkcjonalni nie uważają, że skutki uboczne są złe, po prostu uważają, że ograniczenie stosowania efektów ubocznych jest dobre. Wiem, że to może wydawać się tak prostym rozróżnieniem, ale robi różnicę.

ChaosPandion
źródło
Dlatego są „czymś w rodzaju tabu” - FPL zachęcają do ograniczania skutków ubocznych.
Frank Shearar
+1 za podejście. skutki uboczne wciąż istnieją. w rzeczywistości są ograniczone
Belun,
Dla wyjaśnienia nie powiedziałem „dlaczego efekt uboczny jest niedozwolony w programowaniu funkcjonalnym” ani „dlaczego efekt uboczny nie jest potrzebny”. Wiem, że jest dozwolone w językach funkcjonalnych i czasami jest koniecznością. Ale jest to bardzo odradzane w programowaniu funkcjonalnym. Dlaczego? To było moje pytanie.
Gulshan
@Gulshan - Ponieważ efekty uboczne sprawiają, że programy są trudniejsze do zrozumienia i optymalizacji.
ChaosPandion
w przypadku haskell głównym celem nie jest „ograniczenie skutków ubocznych”. Efekty uboczne nie są możliwe do wyrażenia w JĘZYKU. funkcje, jakie readFilewykonują, to zdefiniowanie sekwencji działań. ta sekwencja jest funkcjonalnie czysta i przypomina trochę abstrakcyjne drzewo opisujące CO należy zrobić. środowisko wykonawcze przeprowadza następnie rzeczywiste brudne skutki uboczne.
sara
13

Kilka uwag:

  • Funkcje bez efektów ubocznych mogą być trywialnie wykonywane równolegle, podczas gdy funkcje z efektami ubocznymi zwykle wymagają pewnego rodzaju synchronizacji.

  • Funkcje bez efektów ubocznych pozwalają na bardziej agresywną optymalizację (np. Przez przezroczyste użycie pamięci podręcznej wyników), ponieważ dopóki otrzymamy właściwy wynik, nie ma nawet znaczenia, czy funkcja została naprawdę wykonana

użytkownik 281377
źródło
Bardzo interesujący punkt: nie ma nawet znaczenia, czy funkcja została naprawdę wykonana . Byłoby interesujące skończyć z kompilatorem, który może pozbyć się kolejnych wywołań funkcji wolnych od efektów ubocznych, o równoważnych parametrach.
Noel Widmer,
1
@NoelWidmer Coś takiego już istnieje. Oracle PL / SQL oferuje deterministicklauzulę dla funkcji bez skutków ubocznych, więc nie są one wykonywane częściej niż to konieczne.
user281377,
Łał! Myślę jednak, że języki powinny być semantycznie ekspresyjne, aby kompilator mógł sam to zrozumieć bez konieczności podawania wyraźnej flagi (nie jestem pewien, co to jest klauzula). Rozwiązaniem mogłoby być określenie parametrów, które mają być zmienne / niezmienne. Ogólnie rzecz biorąc, wymagałoby to silnego systemu typów, w którym kompilator mógłby poczynić założenia dotyczące skutków ubocznych. W razie potrzeby funkcja ta musiałaby zostać wyłączona. Rezygnacja zamiast rezygnacji. To tylko moja opinia, oparta na ograniczonej wiedzy, którą mam, odkąd przeczytałem twoją odpowiedź :)
Noel Widmer,
deterministicKlauzula jest tylko słowo kluczowe, które informuje kompilator, że jest to funkcja deterministyczny, porównywalny do tego, jak finalkluczowe w Javie informuje kompilator, że zmienna nie może się zmienić.
user281377,
11

Pracuję teraz przede wszystkim w kodzie funkcjonalnym i z tej perspektywy wydaje się to oślepiająco oczywiste. Skutki uboczne stanowią ogromne obciążenie psychiczne dla programistów próbujących czytać i rozumieć kod. Nie zauważysz tego obciążenia, dopóki nie uwolnisz się od niego przez jakiś czas, a potem nagle będziesz musiał ponownie przeczytać kod ze skutkami ubocznymi.

Rozważ ten prosty przykład:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

W funkcjonalnym języku wiem, że footo wciąż 42. Nie muszę nawet patrzeć na kod pomiędzy, a tym bardziej go rozumieć, ani na implementację wywoływanych funkcji.

Wszystkie te rzeczy na temat współbieżności, paralelizacji i optymalizacji są fajne, ale właśnie to informatycy umieścili w broszurze. Nie muszę się zastanawiać, kto mutuje Twoją zmienną i kiedy naprawdę lubię codzienną praktykę.

Karl Bielefeldt
źródło
6

Niewiele języków uniemożliwia wywoływanie skutków ubocznych. Języki, które byłyby całkowicie wolne od skutków ubocznych, byłyby zbyt trudne (prawie niemożliwe) w użyciu, z wyjątkiem bardzo ograniczonej pojemności.

Dlaczego skutki uboczne są uważane za złe?

Ponieważ znacznie utrudniają uzasadnienie dokładnie tego, co robi program i udowodnienie, że robi to, czego się spodziewasz.

Na bardzo wysokim poziomie wyobraź sobie testowanie całej 3-poziomowej witryny z testami tylko w czarnej skrzynce. Jasne, jest to wykonalne, w zależności od skali. Ale z pewnością dzieje się dużo powielania. A jeśli nie jest to błąd (co jest związane z efektem ubocznym), a następnie można potencjalnie złamać cały system do dalszych badań, aż problem został zdiagnozowany i stałe, a poprawka jest wdrożony w środowisku testowym.

Korzyści

Teraz zmniejsz to. Gdybyś był dość dobry w pisaniu kodu wolnego od efektów ubocznych, o ile szybciej byłbyś w rozumowaniu tego, co zrobił jakiś istniejący kod? O ile szybciej mógłbyś pisać testy jednostkowe? Jak byś się czuł, przekonani, że kod bez żadnych skutków ubocznych była gwarantowana wolna od błędów, a użytkownicy mogą ograniczyć ekspozycję na wszelkie błędy go nie mają?

Jeśli kod nie ma skutków ubocznych, kompilator może również mieć dodatkowe optymalizacje, które mógłby wykonać. Implementacja tych optymalizacji może być znacznie łatwiejsza. Znacznie łatwiej jest nawet opracować koncepcję optymalizacji kodu wolnego od skutków ubocznych, co oznacza, że ​​producent kompilatora może wdrożyć optymalizacje trudne w kodzie z efektami ubocznymi.

Współbieżność jest również znacznie prostsza we wdrażaniu, automatycznym generowaniu i optymalizacji, gdy kod nie ma skutków ubocznych. Jest tak, ponieważ wszystkie elementy można bezpiecznie ocenić w dowolnej kolejności. Umożliwienie programistom pisania wysoce współbieżnego kodu jest powszechnie uważane za kolejne duże wyzwanie, z którym musi zmierzyć się informatyka, i jedno z niewielu pozostałych zabezpieczeń przed prawem Moore'a .

Merlyn Morgan-Graham
źródło
1
Ada bardzo utrudnia wywoływanie skutków ubocznych. Nie jest to jednak niemożliwe, ale wyraźnie wiesz, co wtedy robisz.
mouviciel
@mouviciel: Myślę, że istnieje co najmniej kilka użytecznych języków, które bardzo utrudniają efekty uboczne i staram się przenieść je na Monady.
Merlyn Morgan-Graham
4

Skutki uboczne są jak „wycieki” w kodzie, które będą musiały zostać usunięte później przez ciebie lub przez niczego niepodejrzewającego współpracownika.

Języki funkcjonalne omijają zmienne stanu i zmienne dane jako sposób na uczynienie kodu mniej zależnym od kontekstu i bardziej modułowym. Modułowość zapewnia, że ​​praca jednego programisty nie wpłynie na pracę innego.

Skalowanie tempa rozwoju wraz z rozmiarem zespołu to dziś „święty graal” rozwoju oprogramowania. Podczas pracy z innymi programistami niewiele rzeczy jest tak samo ważnych jak modułowość. Nawet najprostsze logiczne skutki uboczne sprawiają, że współpraca jest niezwykle trudna.

Ami
źródło
+1 - „lub jakiś niczego niepodejrzewający współpracownik”
Merlyn Morgan-Graham
1
-1 dla skutków ubocznych będących „przeciekami, które należy usunąć”. Tworzenie „efektów ubocznych” (kodu niefunkcjonalnego) jest głównym celem pisania każdego nietrywialnego programu komputerowego.
Mason Wheeler,
Ten komentarz pojawia się sześć lat później, ale są skutki uboczne, a potem są skutki uboczne. Pożądane efekty uboczne, robienie operacji we / wy i tak dalej, są rzeczywiście konieczne dla każdego programu, ponieważ musisz jakoś przekazać wyniki użytkownikowi - ale inny rodzaj efektów ubocznych, w których kod zmienia się bez dobrego powody, takie jak robienie operacji we / wy, są rzeczywiście „wyciekiem”, który będzie musiał zostać rozwiązany później. Podstawową ideą jest rozdzielanie zapytań : funkcja, która zwraca wartość, nie powinna powodować skutków ubocznych.
rmunn
4

Cóż, IMHO, to jest dość obłudne. Nikt nie lubi skutków ubocznych, ale wszyscy ich potrzebują.

To, co jest tak niebezpieczne w skutkach ubocznych, polega na tym, że wywołanie funkcji może mieć wpływ nie tylko na sposób, w jaki zachowuje się funkcja przy następnym wywołaniu, ale może mieć również wpływ na inne funkcje. Zatem skutki uboczne wprowadzają nieprzewidywalne zachowanie i nietrywialne zależności.

Zarówno paradygmaty programowania jak OO i funkcjonalne rozwiązują ten problem. OO zmniejsza problem poprzez nałożenie separacji obaw. Oznacza to, że stan aplikacji, który składa się z wielu zmiennych danych, jest enkapsulowany w obiekty, z których każdy jest odpowiedzialny tylko za utrzymanie własnego stanu. W ten sposób zmniejsza się ryzyko zależności, a problemy są znacznie bardziej odizolowane i łatwiejsze do śledzenia.

Programowanie funkcjonalne przyjmuje znacznie bardziej radykalne podejście, w którym stan aplikacji jest po prostu niezmienny z perspektywy programisty. To fajny pomysł, ale sam czyni język bezużytecznym. Dlaczego? Ponieważ DOWOLNA operacja We / Wy ma skutki uboczne. Natychmiast po odczytaniu dowolnego strumienia wejściowego stan aplikacji prawdopodobnie się zmieni, ponieważ przy następnym wywołaniu tej samej funkcji wynik może być inny. Być może odczytujesz różne dane lub - również możliwe - operacja może się nie powieść. To samo dotyczy danych wyjściowych. Nawet wyjście jest operacją z efektami ubocznymi. W dzisiejszych czasach nie zdajesz sobie z tego często sprawy, ale wyobraź sobie, że masz tylko 20 KB na wyjście, a jeśli je wypiszesz, aplikacja zawiesza się, ponieważ brakuje miejsca na dysku lub cokolwiek innego.

Więc tak, skutki uboczne są paskudne i niebezpieczne z perspektywy programisty. Większość błędów wynika ze sposobu, w jaki niektóre części stanu aplikacji są blokowane w prawie niejasny sposób, poprzez nierozważne i często niepotrzebne skutki uboczne. Z punktu widzenia użytkownika skutki uboczne są potrzebne do korzystania z komputera. Nie dbają o to, co dzieje się w środku i jak jest zorganizowane. Robią coś i oczekują, że komputer odpowiednio ZMIENI.

back2dos
źródło
co ciekawe, programowanie logiczne nie tylko nie ma funkcjonalnych efektów ubocznych; ale nie można nawet zmienić wartości zmiennej po przypisaniu.
Ilan
@Ilan: Dotyczy to również niektórych języków funkcjonalnych i jest to styl łatwy do przyjęcia.
back2dos
„Programowanie funkcjonalne przyjmuje znacznie bardziej radykalne podejście, w którym stan aplikacji jest po prostu niezmienny z perspektywy programisty. To fajny pomysł, ale sam czyni język bezużytecznym. Dlaczego? Ponieważ KAŻDA operacja I / O ma swoją stronę efekty ”: FP nie zabrania efektów ubocznych, a raczej ogranicza je, gdy nie jest to konieczne. Np. (1) I / O -> działania niepożądane są konieczne; (2) obliczenie funkcji agregującej z sekwencji wartości -> efekt uboczny (np. Dla pętli ze zmienną akumulatorową) nie jest konieczne.
Giorgio
2

Każdy efekt uboczny wprowadza dodatkowe parametry wejściowe / wyjściowe, które należy wziąć pod uwagę podczas testowania.

To sprawia, że ​​sprawdzanie poprawności kodu jest znacznie bardziej złożone, ponieważ środowisko nie może być ograniczone tylko do sprawdzania poprawności kodu, ale musi obejmować część lub całość otaczającego środowiska (globalny, który jest aktualizowany, mieszka tam w tym kodzie, co z kolei zależy od tego kod, który z kolei zależy od życia w pełnym serwerze Java EE ....)

Starając się unikać skutków ubocznych, ograniczasz ilość zewnętrznych potrzebnych do uruchomienia kodu.


źródło
1

Z mojego doświadczenia wynika, że ​​dobry projekt w programowaniu obiektowym nakazuje korzystanie z funkcji, które mają skutki uboczne.

Na przykład weź podstawową aplikację komputerową interfejsu użytkownika. Mogę mieć działający program, który ma na swoim stosie graf obiektowy reprezentujący bieżący stan modelu domeny mojego programu. Wiadomości docierają do obiektów na tym wykresie (na przykład za pośrednictwem wywołań metod wywoływanych z kontrolera warstwy interfejsu użytkownika). Wykres obiektowy (model domeny) na stercie jest modyfikowany w odpowiedzi na komunikaty. Obserwatorzy modelu są informowani o wszelkich zmianach, interfejs użytkownika i być może inne zasoby są modyfikowane.

Dalekie od bycia złym, prawidłowe rozmieszczenie tych efektów ubocznych modyfikujących stos i modyfikujących ekran jest podstawą projektowania OO (w tym przypadku wzorca MVC).

Oczywiście nie oznacza to, że twoje metody powinny mieć arbitralne skutki uboczne. A funkcje wolne od skutków ubocznych mają miejsce w poprawie czytelności, a czasem i wydajności kodu.

Flamingpenguin
źródło
1
Obserwatorzy (w tym interfejs użytkownika) powinni dowiedzieć się o modyfikacjach, subskrybując wiadomości / zdarzenia wysyłane przez obiekt. Nie jest to efekt uboczny, chyba że obiekt bezpośrednio zmodyfikuje obserwatora - co byłoby złym projektem.
ChrisF
1
@ChrisF Z pewnością jest to efekt uboczny. Komunikat przekazany do obserwatora (w języku OO najprawdopodobniej wywołanie metody interfejsu) doprowadzi do zmiany stanu komponentu interfejsu użytkownika na stercie (a te obiekty steru są widoczne dla innych części programu). Składnik interfejsu użytkownika nie jest ani parametrem metody, ani zwracaną wartością. W sensie formalnym, aby funkcja była wolna od skutków ubocznych, musi być idempotentna. Powiadomienie we wzorcu MVC nie jest, na przykład interfejs użytkownika może wyświetlać listę komunikatów, które otrzymał - konsola - dwukrotne wywołanie powoduje inny stan programu.
flamingpenguin
0

Zło jest trochę przesadzone .. wszystko zależy od kontekstu użycia języka.

Inną kwestią do wspomnianych już jest to, że sprawia, że ​​dowody poprawności programu są znacznie prostsze, jeśli nie ma funkcjonalnych efektów ubocznych.

Ilan
źródło
0

Jak wskazano powyżej, języki funkcjonalne nie tyle zapobiegają skutkom ubocznym kodu, ale dostarczają narzędzi do zarządzania, jakie skutki uboczne mogą wystąpić w danym fragmencie kodu i kiedy.

To okazuje się mieć bardzo interesujące konsekwencje. Po pierwsze i najbardziej oczywiste, istnieje wiele rzeczy, które można zrobić z kodem wolnym od skutków ubocznych, które zostały już opisane. Ale są też inne rzeczy, które możemy zrobić, nawet podczas pracy z kodem, który ma skutki uboczne:

  • W kodzie ze stanem zmiennym możemy zarządzać zakresem stanu w taki sposób, aby statycznie upewnić się, że nie może on przeciekać poza daną funkcję, co pozwala nam zbierać śmieci bez liczenia referencji lub schematów oznaczania i zamiatania , ale wciąż upewnij się, że żadne odniesienia nie przetrwają. Te same gwarancje są również przydatne do zachowania poufnych informacji itp. (Można to osiągnąć za pomocą monady ST w haskell)
  • Modyfikując stan współdzielony w wielu wątkach, możemy uniknąć konieczności blokowania, śledząc zmiany i wykonując aktualizację atomową na końcu transakcji lub wycofując transakcję i powtarzając ją, jeśli inny wątek wprowadził sprzeczną modyfikację. Jest to możliwe do osiągnięcia tylko dlatego, że możemy zapewnić, że kod nie będzie miał innych skutków niż modyfikacje stanu (które możemy z radością porzucić). Jest to wykonywane przez monadę STM (Software Transactional Memory) w Haskell.
  • możemy śledzić efekty kodu i trywialnie wyodrębnić go, filtrując wszelkie efekty, które może on potrzebować, aby upewnić się, że jest bezpieczny, umożliwiając w ten sposób (na przykład) bezpieczne wykonanie kodu wprowadzonego przez użytkownika na stronie internetowej
Jules
źródło
0

W złożonych bazach kodu najtrudniejszą rzeczą, o której myślę, jest złożona interakcja efektów ubocznych. Mogę mówić tylko osobiście, biorąc pod uwagę sposób działania mojego mózgu. Efekty uboczne i uporczywe stany oraz mutowanie danych wejściowych i tak dalej zmuszają mnie do myślenia o tym, „kiedy” i „gdzie” zdarzają się przyczyny prawidłowości, a nie tylko „co” dzieje się w każdej funkcji.

Nie mogę się skupić na „czym”. Po dokładnym przetestowaniu funkcji, która powoduje skutki uboczne, nie mogę stwierdzić, że spowoduje to zwiększenie niezawodności w całym kodzie przy jej użyciu, ponieważ osoby dzwoniące mogą nadal nadużywać, wywołując ją w niewłaściwym czasie, z niewłaściwego wątku, w niewłaściwym zamówienie. Tymczasem funkcja, która nie powoduje żadnych skutków ubocznych i po prostu zwraca nowy wynik na podstawie danych wejściowych (bez dotykania danych wejściowych), jest prawie niemożliwa do niewłaściwego użycia w ten sposób.

Ale myślę, że jestem typem pragmatycznym, a przynajmniej staram się być, i nie sądzę, że musimy koniecznie wyeliminować wszystkie skutki uboczne do jak najmniejszego minimum, aby uzasadnić poprawność naszego kodu (przynajmniej Bardzo trudno byłoby mi to zrobić w takich językach jak C). Bardzo trudno jest mi uzasadnić poprawność, gdy mamy kombinację złożonych przepływów kontrolnych i skutków ubocznych.

Skomplikowane przepływy kontroli do mnie to te, które mają charakter podobny do grafu, często rekurencyjne lub rekurencyjne (kolejki zdarzeń, np. Które nie wywołują bezpośrednio zdarzeń rekurencyjnych, ale mają charakter „rekurencyjny”), być może robią rzeczy w trakcie przemierzania rzeczywistej połączonej struktury grafu lub przetwarzania niejednorodnej kolejki zdarzeń, która zawiera eklektyczną mieszaninę zdarzeń do przetworzenia, prowadząc nas do wszelkiego rodzaju różnych części bazy kodu i wywołując różne skutki uboczne. Jeśli spróbujesz narysować wszystkie miejsca, w których ostatecznie znajdziesz się w kodzie, będzie to przypominało złożony wykres i potencjalnie z węzłami na wykresie, których nigdy się nie spodziewałbyś, byłyby tam w danym momencie, biorąc pod uwagę, że wszystkie one są wszystkie powodując skutki uboczne,

Języki funkcjonalne mogą mieć niezwykle złożone i rekurencyjne przepływy kontroli, ale wynik jest tak łatwy do zrozumienia pod względem poprawności, ponieważ nie występują przy tym różnego rodzaju eklektyczne skutki uboczne. Dopiero gdy złożone przepływy kontrolne napotykają eklektyczne skutki uboczne, uznaję to za bóle głowy, aby spróbować zrozumieć całość tego, co się dzieje i czy zawsze zrobi to dobrze.

Kiedy więc mam takie przypadki, często bardzo trudno mi, jeśli nie niemożliwie, czuć się bardzo pewnie co do poprawności takiego kodu, nie mówiąc już o bardzo pewności, że mogę wprowadzić zmiany w takim kodzie bez potknięcia się o coś nieoczekiwanego. Dla mnie rozwiązaniem jest albo uproszczenie przepływu kontroli, albo zminimalizowanie / ujednolicenie efektów ubocznych (przez ujednolicenie, mam na myśli, jak powodowanie tylko jednego rodzaju efektu ubocznego do wielu rzeczy podczas określonej fazy w systemie, a nie dwóch lub trzech lub jednego tuzin). Potrzebuję jednej z tych dwóch rzeczy, aby mój prosty mózg mógł mieć pewność co do poprawności istniejącego kodu i poprawności wprowadzanych przeze mnie zmian. Łatwo jest mieć pewność co do poprawności kodu wprowadzającego efekty uboczne, jeśli efekty uboczne są jednolite i proste wraz z przepływem kontrolnym, tak jak:

for each pixel in an image:
    make it red

Łatwo jest uzasadnić poprawność takiego kodu, ale głównie dlatego, że skutki uboczne są tak jednolite, a kontrola jest tak prosta. Ale powiedzmy, że mieliśmy taki kod:

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

Jest to absurdalnie uproszczony pseudokod, który zazwyczaj wymaga znacznie większej liczby funkcji i zagnieżdżonych pętli oraz znacznie więcej rzeczy, które musiałyby trwać (aktualizacja wielu map tekstur, masy kości, stanów selekcji itp.), Ale nawet pseudokod tak utrudnia powód poprawności z powodu interakcji złożonego, podobnego do grafu przepływu kontroli i trwających efektów ubocznych. Tak więc jedną strategią upraszczającą jest odroczenie przetwarzania i skupienie się na jednym rodzaju efektów ubocznych na raz:

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

... coś w tym celu jako jedna iteracja uproszczenia. Oznacza to, że wielokrotnie przepuszczamy dane, co z pewnością wiąże się z kosztami obliczeniowymi, ale często okazuje się, że łatwiej jest wielowątkowić taki wynikowy kod, teraz, gdy efekty uboczne i przepływy kontrolne nabrały tej jednolitej i prostszej natury. Ponadto każdą pętlę można uczynić bardziej przyjazną dla pamięci podręcznej niż przechodzenie przez podłączony wykres i wywoływanie efektów ubocznych w miarę upływu czasu (np. Użyj równoległego zestawu bitów, aby zaznaczyć, co należy przejść, abyśmy mogli następnie wykonać odroczone przejścia w posortowanej kolejności sekwencyjnej używając masek bitowych i FFS). Ale co najważniejsze, uważam drugą wersję za o wiele łatwiejszą do uzasadnienia pod względem poprawności, a także zmiany bez powodowania błędów. Po to aby'

W końcu potrzebujemy efektów ubocznych, w przeciwnym razie mielibyśmy po prostu funkcje, które wysyłają dane bez celu. Często musimy nagrać coś do pliku, wyświetlić coś na ekranie, przesłać dane przez gniazdo, coś w tym rodzaju, a wszystkie te rzeczy są efektami ubocznymi. Ale możemy zdecydowanie zmniejszyć liczbę zbędnych efektów ubocznych, które się utrzymują, a także zmniejszyć liczbę efektów ubocznych, które występują, gdy przepływy kontrolne są bardzo skomplikowane, i myślę, że znacznie łatwiej byłoby uniknąć błędów, gdybyśmy tak zrobili.


źródło
-1

To nie jest złe. Moim zdaniem konieczne jest rozróżnienie dwóch typów funkcji - z efektami ubocznymi i bez. Funkcja bez skutków ubocznych: - zwraca zawsze to samo z tymi samymi argumentami, więc na przykład taka funkcja bez żadnych argumentów nie ma sensu. - Oznacza to również, że kolejność, w jakiej nazywane są niektóre takie funkcje, nie odgrywa żadnej roli - musi być w stanie działać i może być debugowana tylko sama (!), Bez żadnego innego kodu. A teraz lol, zobacz, co robi JUnit. Funkcja z efektami ubocznymi: - ma coś w rodzaju „przecieków”, które można automatycznie zaznaczyć - jest to bardzo ważne przez debugowanie i wyszukiwanie błędów, które generalnie są powodowane przez skutki uboczne. - Każda funkcja z efektami ubocznymi ma również swoją „część” bez skutków ubocznych, co można również rozdzielić automatycznie. Więc zło to te skutki uboczne,

użytkownik225623
źródło
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami przedstawionymi i wyjaśnionymi w poprzednich 12 odpowiedziach
komnata