„Łatwy do uzasadnienia” - co to znaczy? [Zamknięte]

49

Słyszałem wiele razy, gdy inni programiści używają tego wyrażenia do „reklamowania” niektórych wzorców lub opracowywania najlepszych praktyk. Przez większość czasu ta fraza jest używana, gdy mówimy o korzyściach z programowania funkcjonalnego.

Sformułowanie „łatwy do uzasadnienia” zostało użyte w obecnym brzmieniu, bez żadnego wyjaśnienia ani próbki kodu. Dla mnie staje się to więc kolejnym słowem „buzz”, którego bardziej doświadczeni programiści używają w swoich rozmowach.

Pytanie: Czy możesz podać przykłady „Niełatwe do uzasadnienia”, aby można je porównać z przykładami „Łatwo uzasadnić”?

Fabio
źródło
4
@MartinMaat Bardziej precyzyjnym, powszechnie stosowanym wyrażeniem jest rozumowanie równania, sugerowałbym, że może to właśnie szuka Fabio
jk.
3
Do tego typu rzeczy lubię używać wyrażenia „obciążenie poznawcze” .
Baldrickk,
16
Czy wiesz, co oznacza rozumowanie programów ?
Bergi,
5
W sensie nieformalnym używam tego, aby oznaczać, że rozwiązanie jest wystarczająco proste, aby zrozumieć (ogólnie), jakie będą wyniki dla każdego wkładu bez testowania go. Oznacza to, że dla dowolnego zestawu danych wejściowych wyniki nie będą zaskakujące. Trudno uzasadnić na przykład rozwiązania, które mają nieoczywiste przypadki narożne. Używam tego głównie w odniesieniu do solidności.
JimmyJames,
7
Jestem bardzo winny częstego używania „łatwiejszego do uzasadnienia”; Zauważam jednak, że staram się uważać, by porównywanie było łatwiejsze niż absolutnie łatwe . Był taki dzień w moim życiu, że nie mogłem w ogóle myśleć o żadnym oprogramowaniu, więc tego dnia nie było łatwo ; stało się to łatwe tylko przez poświęcenie dużej ilości czasu i wysiłku. Stwierdzenie, że każdy problem z programowaniem jest łatwy, oznacza pejoratywne podejście do każdego, kto może (jeszcze) nie będzie mu łatwo. Stwierdzenie, że jeden model jest łatwiejszy niż inny, oznacza powiedzenie, że w grę wchodzi mniej pojęć, mniej części ruchomych i tak dalej.
Eric Lippert,

Odpowiedzi:

58

Moim zdaniem wyrażenie „łatwe do uzasadnienia” odnosi się do kodu, który łatwo „wykonać w głowie”.

Patrząc na fragment kodu, jeśli jest krótki, jasno napisany, z dobrymi nazwami i minimalną mutacją wartości, to mentalne przepracowanie tego, co robi kod, jest (względnie) łatwym zadaniem.

Długi kawałek kodu ze złymi nazwami, zmiennymi, które stale zmieniają wartość i zawiłe rozgałęzienia zwykle wymagają np. Długopisu i kartki papieru, aby śledzić aktualny stan. Taki kod nie może więc być łatwo przetworzony tylko w twojej głowie, więc nie jest łatwo go zrozumieć.

David Arno
źródło
29
Z niewielkim zastrzeżeniem, bez względu na to, jak dobrze nazywasz zmienne, program, który próbuje obalić hipotezę Goldbacha, jest z natury trudny do „wykonania” w twojej głowie lub gdzie indziej. Ale nadal łatwo jest się zastanowić, w tym sensie, że łatwo przekonać się, że jeśli twierdzi, że znalazł przeciwny przykład, to mówi prawdę ;-)
Steve Jessop,
4
Nigdy nie chciałbym wykonywać kodu w mojej głowie. To byłby dla mnie ostateczny pokaz „niełatwego do uzasadnienia”. Chciałbym móc przewidywać stwierdzenia dotyczące tego, co zrobiłby komputer bez jego wykonania. Kod, który jest „łatwy do uzasadnienia” to kod, który nie musi być wykonywany w twojej głowie, można go zamiast tego uzasadnić.
Cort Ammon
1
Jak odpowiedzieć na pytanie dotyczące rozumowania kodu, nawet nie wspominając o formalnej weryfikacji ? Ta odpowiedź sugeruje, że rozumowanie na temat kodu jest nieformalne i ad hoc. nie jest, zwykle odbywa się to z wielką starannością i matematyką. Istnieją pewne właściwości matematyczne, które czynią kod „łatwym do uzasadnienia” w sensie obiektywnym (czyste funkcje, aby dać bardzo łatwy przykład). nazwy zmiennych nie mają nic wspólnego z tym, jak łatwo „rozumować” kod, przynajmniej nie w sensie formalnym.
Polygnome,
3
@ Polygnome Rozumowanie kodu zwykle nie odbywa się z wielką starannością i podejściem matematycznym. Kiedy piszę to, ludzie nieformalnie myślący o kodzie przewyższają liczebnie matematyków o miliony do jednego, a przynajmniej tak mi się wydaje.
Kaz
2
@Polygnome "Code easy to reason about" almost exclusively alludes to its mathematical properties and formal verification- to z grubsza brzmi jak odpowiedź na pytanie. Możesz opublikować to jako odpowiedź zamiast nie zgadzać się na temat (subiektywnej) odpowiedzi w komentarzach.
Dukeling
47

Łatwo jest uzasadnić mechanizm lub fragment kodu, kiedy trzeba wziąć pod uwagę kilka rzeczy, aby przewidzieć, co zrobi, a rzeczy, które należy wziąć pod uwagę, są łatwo dostępne.

Prawdziwe funkcje bez efektów ubocznych i bez stanu są łatwe do uzasadnienia, ponieważ wynik jest całkowicie determinowany przez dane wejściowe, które są dokładnie tam w parametrach.

I odwrotnie, obiekt ze stanem jest znacznie trudniejszy do uzasadnienia, ponieważ musisz wziąć pod uwagę stan, w jakim znajduje się obiekt, gdy wywoływana jest metoda, co oznacza, że ​​musisz pomyśleć o tym, w jakich innych sytuacjach obiekt mógłby znajdować się w szczególny stan.

Jeszcze gorzej są zmienne globalne: aby zrozumieć kod, który odczytuje zmienną globalną, musisz zrozumieć, gdzie w kodzie ta zmienna może być ustawiona i dlaczego - a znalezienie tych wszystkich miejsc może nie być łatwe.

Prawie najtrudniejszą rzeczą do uzasadnienia jest programowanie wielowątkowe ze stanem współdzielonym, ponieważ nie tylko masz stan, masz wiele wątków zmieniających go w tym samym czasie, aby zrozumieć, co robi kawałek kodu, gdy jest wykonywany przez jeden wątek trzeba dopuścić możliwość, że w każdym punkcie wykonania jakiś inny wątek (lub kilka z nich!) może wykonywać prawie każdą inną część kodu i zmieniać dane, nad którymi operujesz, pod twoimi oczami. Teoretycznie można temu zaradzić za pomocą muteksów / monitorów / sekcji krytycznych / jakkolwiek to nazwiesz, ale w praktyce żaden zwykły człowiek nie jest w stanie tego zrobić niezawodnie, chyba że drastycznie ograniczą państwo wspólne i / lub paralelizm do bardzo małych sekcje kodu.

Michael Borgwardt
źródło
9
Zgadzam się z tą odpowiedzią, ale nawet w przypadku czystych funkcji, podejście deklaratywne (takie jak CSS, XSLT, makea nawet specjalizacja szablonów C ++ i przeciążenie funkcji) może przywrócić cię do ponownego rozważenia całego programu. Nawet gdy myślisz, że znalazłeś definicję czegoś, język pozwala na bardziej szczegółowe określenie w dowolnym miejscu programu, aby to zastąpić. Twoje IDE może w tym pomóc.
Steve Jessop,
4
Dodałbym, że w scenariuszu wielowątkowym musisz także mieć dość głębokie zrozumienie do tego, do jakich instrukcji niższego poziomu zapada twój kod: operacja, która wygląda na atomową w źródle, może mieć nieoczekiwane punkty przerwania w rzeczywistym wykonaniu.
Jared Smith,
6
@ SteveJessop: Rzeczywiście, ten punkt jest często pomijany. Jest powód, dla którego C # każe ci mówić, kiedy chcesz, aby metoda była nadpisywalna, zamiast po cichu ustawić domyślną nadpisalność; chcemy pomachać flagą mówiącą „poprawność twojego programu może zależeć od kodu, którego nie możesz znaleźć w czasie kompilacji” w tym momencie. (To powiedziawszy, chciałbym również, aby „zapieczętowany” był domyślny dla klas w języku C #.)
Eric Lippert
@EricLippert Jakie były ostatnie powody, dla których sealednie było to ustawienie domyślne?
Zev Spitz,
@ZevSpitz: Ta decyzja została podjęta na długo przed moim czasem; Nie wiem
Eric Lippert
9

W przypadku programowania funkcjonalnego znaczenie „łatwego do przemyślenia” jest przede wszystkim deterministyczne. Rozumiałem przez to, że dane wejście zawsze prowadzi do tego samego wyjścia. Możesz robić, co tylko chcesz programowi, dopóki nie dotkniesz tego fragmentu kodu, nie złamie się.

Z drugiej strony, OO jest zazwyczaj trudniejsze do uzasadnienia, ponieważ wytworzony „wynik” zależy od stanu wewnętrznego każdego zaangażowanego obiektu. Typowy sposób, w jaki się manifestuje, to nieoczekiwane skutki uboczne : przy zmianie jednej części kodu część pozornie niezwiązana zrywa się.

... wadą programowania funkcjonalnego jest oczywiście to, że w praktyce wiele rzeczy, które chcesz zrobić, to IO i zarządzanie stanem.

Istnieje jednak wiele innych rzeczy, o których trudniej jest uzasadnić, i zgadzam się z @Kilian, że współbieżność jest doskonałym przykładem. Systemy rozproszone też.

Dagnele
źródło
5

Unikanie szerszej dyskusji i odpowiedź na konkretne pytanie:

Czy możesz podać przykłady „Niełatwe do uzasadnienia”, aby można je porównać z przykładami „Łatwo uzasadnić”?

Odsyłam cię do „Historii Mela, prawdziwego programisty” , kawałka folkloru programisty, który pochodzi z 1983 roku i dlatego dla naszego zawodu liczy się jako „legenda”.

Opowiada historię programisty piszącego kod, który w miarę możliwości preferował tajemne techniki, w tym kod samoreferencyjny i samodmodyfikujący oraz celowe wykorzystanie błędów maszynowych:

pozorna nieskończona pętla faktycznie została zakodowana w taki sposób, aby wykorzystać błąd przepełnienia przenoszenia. Dodanie 1 do instrukcji, która została zdekodowana jako „Wczytaj z adresu x” zwykle daje „Wczytaj z adresu x + 1”. Ale gdy x był już najwyższym możliwym adresem, adres nie tylko zawinął się do zera, ale 1 został przeniesiony do bitów, z których czytany byłby kod operacji, zmieniając kod operacji z „load from” na „jump to”, więc pełna instrukcja zmieniła się z „load from the last address” na „jump to address zero”.

To jest przykład kodu, którego „trudno uzasadnić”.

Oczywiście Mel nie zgodzi się ...

AakashM
źródło
1
+1 za odniesienie do historii Mela, jednego z moich wiecznych ulubionych.
John Bollinger
3
Przeczytaj tutaj Historię Mela , ponieważ artykuł w Wikipedii nie zawiera do niej linku.
TRiG
@TRiG przypis 3 na stronie, nie?
AakashM
@AakashM Jakoś udało się to przegapić.
TRiG
5

Mogę podać przykład i bardzo powszechny.

Rozważ następujący kod C #.

// items is List<Item>
var names = new List<string>();
for (var i = 0; i < items.Count; i++)
{
    var item = items[i];
    var mangled = MyMangleFunction(item.Name);
    if (mangled.StartsWith("foo"))
    {
        names.Add(mangled);
    }
}

Teraz rozważ tę alternatywę.

// items is List<Item>
var names = items
    .Select(item => MyMangleFunction(item.Name))
    .Where(s => s.StartsWith("foo"))
    .ToList();

W drugim przykładzie dokładnie wiem, co ten kod robi na pierwszy rzut oka. Kiedy widzę Select, wiem, że lista przedmiotów jest konwertowana na listę czegoś innego. Kiedy widzę Where, wiem, że niektóre elementy są filtrowane. Na pierwszy rzut oka mogę zrozumieć, co to namesjest i skutecznie z niego korzystać.

Kiedy widzę forpętlę, nie mam pojęcia, co się z nią dzieje, dopóki nie przeczytam kodu. I czasami muszę to prześledzić, aby mieć pewność, że uwzględniłem wszystkie działania niepożądane. Muszę trochę popracować, aby nawet zrozumieć, jakie są nazwy (poza definicją typu) i jak je skutecznie wykorzystać. Zatem pierwszy przykład jest trudniejszy do uzasadnienia niż drugi.

Ostatecznie łatwość rozumowania w tym przypadku zależy również od zrozumienia metod LINQ Selecti Where. Jeśli ich nie znasz, to na początku trudniej jest zrozumieć drugi kod. Ale płacisz tylko za ich zrozumienie. Płacisz za zrozumienie forpętli za każdym razem, gdy używasz pętli za każdym razem, gdy się zmienia. Czasami warto zapłacić, ale zwykle ważniejsze jest „łatwiejsze uzasadnienie”.

Kasey Speakman
źródło
2

Powiązaną frazą jest (I parafrazę),

Nie wystarczy, żeby kod zawierał „ brak oczywistych błędów ”: zamiast tego powinien „ oczywiście nie zawierać błędów ”.

Przykładem stosunkowo „łatwego do uzasadnienia” może być RAII .

Innym przykładem może być unikanie śmiertelnego uścisku : jeśli możesz przytrzymać zamek i zdobyć inny zamek, a jest wiele zamków, trudno jest upewnić się, że nie ma scenariusza, w którym mogłoby dojść do śmiertelnego uścisku. Dodanie reguły typu „istnieje tylko jedna (globalna) blokada” lub „nie można uzyskać drugiej blokady, gdy trzymasz pierwszą blokadę”, sprawia, że ​​system jest stosunkowo łatwy do uzasadnienia.

ChrisW
źródło
1
Hmm Nie jestem pewien, czy RAII jest tak łatwe do uzasadnienia. Jasne, łatwo to zrozumieć koncepcyjnie , ale trudniej jest właściwie zrozumieć (tj. Przewidzieć) zachowanie kodu, który szeroko wykorzystuje RAII. Mam na myśli, że w zasadzie niewidoczne wywołania funkcji na poziomie zakresu. Fakt, że wiele osób ma problemy z uzasadnieniem, jest bardzo oczywisty, jeśli kiedykolwiek programowałeś COM .
Cody Gray
Miałem na myśli stosunkowo łatwe (C ++ w porównaniu z C): na przykład istnienie konstruktora obsługiwanego przez język oznacza, że ​​programiści nie mogą tworzyć / mieć / używać obiektu, którego zapominają zainicjować itp.
ChrisW
Ten przykład oparty na modelu COM jest problematyczny, ponieważ łączy style, tj. Inteligentny wskaźnik w stylu C ++ ( CComPtr<>) z funkcją w stylu C ( CoUninitialize()). Uważam to również za dziwny przykład, o ile pamiętam, że wywołujesz CoInitialize / CoUninitialize w zakresie modułu i przez cały okres jego użytkowania, np. W mainlub w DllMain, a nie w jakimś krótkim, krótkotrwałym zakresie funkcji lokalnej, jak pokazano w przykładzie .
ChrisW,
Jest to zbyt uproszczony przykład do celów ilustracyjnych. Masz całkowitą rację, że COM jest inicjowany w zakresie modułu, ale wyobraź sobie przykład Raymonda (jak przykład Larry'ego) jako funkcję punktu wejścia ( main) dla aplikacji. Inicjujesz COM podczas uruchamiania, a następnie dezinicjujesz go tuż przed wyjściem. Tyle że masz obiekty globalne, takie jak inteligentne wskaźniki COM, używając paradygmatu RAII. Jeśli chodzi o mieszanie stylów: globalny obiekt, który zainicjował COM w swoim ctor i niezainicjowany w swoim dtor, jest wykonalny, a to, co sugeruje Raymond, jest subtelne i niełatwe do uzasadnienia.
Cody Gray
Twierdziłbym, że na wiele sposobów programowanie COM jest łatwiejsze do zrozumienia w C, ponieważ wszystko jest jawnym wywołaniem funkcji. Za twoimi plecami nie dzieje się nic ukrytego ani niewidzialnego. Jest to trochę więcej pracy (tj. Bardziej żmudne), ponieważ musisz ręcznie napisać wszystkie te wywołania funkcji i wrócić i sprawdzić swoją pracę, aby zobaczyć, czy wykonałeś to poprawnie, ale wszystko jest wyłożone, co jest kluczem aby ułatwić zrozumienie . Innymi słowy, „czasami inteligentne wskaźniki są po prostu zbyt inteligentne” .
Cody Gray
2

Istotą programowania jest analiza przypadków. Alan Perlis zauważył o tym w Epigramie nr 32: Programiści nie powinni być mierzeni ich pomysłowością i logiką, ale kompletnością analizy przypadków.

Łatwo jest uzasadnić sytuację, jeśli analiza przypadku jest łatwa. Oznacza to albo, że jest kilka przypadków do rozważenia, albo, w przypadku braku tego, kilka przypadków specjalnych - mogą istnieć duże przestrzenie przypadków, ale które się zawalają z powodu pewnych regularności lub ulegają technice rozumowania, takiej jak indukcja.

Na przykład rekurencyjna wersja algorytmu jest zwykle łatwiejsza do uzasadnienia niż wersja imperatywna, ponieważ nie przyczynia się do zbędnych przypadków, które powstają w wyniku mutacji zmiennych stanu pomocniczego, które nie pojawiają się w wersji rekurencyjnej. Ponadto struktura rekurencji jest taka, że ​​wpasowuje się ona w matematyczny wzór korektora przez indukcję. Nie musimy brać pod uwagę złożoności, takich jak warianty pętli i najsłabsze ścisłe warunki wstępne, i tak dalej.

Innym aspektem tego jest struktura przestrzeni obudowy. Łatwiej jest uzasadnić sytuację, która ma płaski lub przeważnie płaski podział na sprawy w porównaniu z sytuacją zhierarchizowaną: sprawy z pod-sprawami i pod-sprawami i tak dalej.

Właściwością systemów, która upraszcza rozumowanie, jest ortogonalność : jest to właściwość polegająca na tym, że przypadki rządzące podsystemami pozostają niezależne po połączeniu tych podsystemów. Żadne kombinacje nie powodują powstania „specjalnych przypadków”. Jeśli coś z czterema przypadkami jest połączone z trzema przypadkami coś prostopadle, istnieje dwanaście przypadków, ale idealniekażdy przypadek jest połączeniem dwóch przypadków, które pozostają niezależne. W pewnym sensie tak naprawdę nie ma dwunastu przypadków; kombinacje to po prostu „pojawiające się zjawiska podobne do przypadków”, o które nie musimy się martwić. Oznacza to, że nadal mamy cztery przypadki, o których możemy pomyśleć bez rozważenia pozostałych trzech w innym podsystemie i odwrotnie. Jeśli niektóre kombinacje muszą być specjalnie zidentyfikowane i wyposażone w dodatkową logikę, wówczas rozumowanie jest trudniejsze. W najgorszym przypadku każda kombinacja ma specjalne podejście, a następnie naprawdę jest dwanaście nowych przypadków, które są dodatkiem do oryginalnych czterech i trzech.

Kaz
źródło
0

Pewnie. Weź współbieżność:

Sekcje krytyczne egzekwowane przez muteksy: łatwe do zrozumienia, ponieważ istnieje tylko jedna zasada (dwa wątki wykonania nie mogą wejść jednocześnie do sekcji krytycznej), ale podatne zarówno na nieefektywność, jak i impas.

Alternatywne modele, np. Programowanie bez blokady lub aktorzy: potencjalnie znacznie bardziej eleganckie i potężne, ale piekielnie trudne do zrozumienia, ponieważ nie można już polegać na (pozornie) podstawowych pojęciach, takich jak „teraz napisz tę wartość do tego miejsca”.

Łatwość uzasadnienia jest jednym z aspektów metody. Ale wybór metody, którą należy zastosować, wymaga uwzględnienia wszystkich aspektów łącznie .

Kilian Foth
źródło
13
-1: naprawdę, bardzo zły przykład, który sprawia, że ​​myślę, że nie rozumiesz, co to wyrażenie oznacza sam. „Sekcje krytyczne egzekwowane przez muteksy” są w rzeczywistości jedną z najtrudniejszych rzeczy do uzasadnienia - prawie każdy, kto ich używa, wprowadza warunki wyścigowe lub impas. Dam ci programowanie bez blokady, ale cały ten model aktora polega na tym, że jest o wiele, wiele łatwiej o tym myśleć.
Michael Borgwardt,
1
Problem polega na tym, że współbieżność sama w sobie jest bardzo trudnym tematem do rozważenia przez programistów, więc nie stanowi dobrego przykładu. Masz całkowitą rację, że krytyczne sekcje wymuszone przez muteksy są stosunkowo prostym sposobem na implementację współbieżności, w porównaniu do programowania bez blokady, ale większość programistów jest jak Michael, a ich oczy są pełne gniewu, kiedy zaczynasz mówić o krytycznych sekcjach i muteksach, więc to z pewnością nie wydaje się łatwą rzeczą do zrozumienia. Nie wspominając o wszystkich błędach.
Cody Gray
0

Ograniczmy to zadanie do formalnego uzasadnienia. Ponieważ humorystyczne, wynalazcze lub poetyckie rozumowanie ma inne prawa.

Mimo to wyrażenie jest niejasno zdefiniowane i nie można go ustawić ściśle. Ale to nie znaczy, że powinno być dla nas tak słabe. Wyobraźmy sobie, że struktura przechodzi test i otrzymuje oceny dla różnych punktów. Dobre oceny za KAŻDY punkt oznaczają, że konstrukcja jest wygodna pod każdym względem, a zatem „łatwa do uzasadnienia”.

Struktura „Łatwa do uzasadnienia” powinna uzyskać dobre oceny za:

  • Terminy wewnętrzne mają rozsądne, łatwe do odróżnienia i zdefiniowania nazwy. Jeśli elementy mają pewną hierarchię, różnica między nazwami rodzica i dziecka powinna różnić się od różnicy między nazwami rodzeństwa.
  • Liczba rodzajów elementów konstrukcyjnych jest niska
  • Stosowane typy elementów konstrukcyjnych są łatwymi rzeczami, do których jesteśmy przyzwyczajeni.
  • Trudno zrozumiałe elementy (rekurencje, meta stopnie, geometria 4+ wymiarowa ...) są izolowane - nie łączone bezpośrednio ze sobą. (na przykład, jeśli spróbujesz pomyśleć o zmianie reguły rekurencyjnej dla kostek wymiarowych 1,2,3,4..n.., będzie to bardzo skomplikowane. Ale jeśli przeniesiesz każdą z tych reguł do jakiejś formuły w zależności od n, będziesz mieć osobno formułę dla każdego n-sześcianu i osobno regułę rekurencji dla takiej formuły. I że dwie struktury osobno można łatwo pomyśleć)
  • Rodzaje elementów strukturalnych są oczywiście różne (na przykład nieużywanie tablic mieszanych zaczynając od 0 i od 1)

Czy test jest subiektywny? Tak, oczywiście, że tak. Ale samo wyrażenie jest również subiektywne. To, co jest łatwe dla jednej osoby, nie jest łatwe dla innej. Tak więc testy powinny być różne dla różnych domen.

Gangnus
źródło
0

Pomysł, że możliwe jest rozumienie języków funkcjonalnych, wywodzi się z ich historii, zwłaszcza ML, który został opracowany jako język programowania analogiczny do konstrukcji, których logika funkcji obliczeniowych używała do rozumowania. Większość języków funkcjonalnych jest bliższa formalnym rachunkom programowania niż języki rozkazujące, więc tłumaczenie z kodu na wejście systemu rozumowania jest mniej uciążliwe.

Na przykład systemu rozumowania, w rachunku różniczkowym i całkowym, każda zmienna lokalizacja pamięci w języku imperatywnym musi być reprezentowana jako oddzielny równoległy proces, podczas gdy sekwencja operacji funkcjonalnych jest pojedynczym procesem. Czterdzieści lat później od prowokatora twierdzeń LFC pracujemy z GB pamięci RAM, więc posiadanie setek procesów jest mniejszym problemem - użyłem rachunku pi do usunięcia potencjalnych zakleszczeń z kilkuset wierszy C ++, mimo że reprezentacja ma setki procesy powodujące wyczerpanie przestrzeni stanu przez około 3 GB i wyleczenie sporadycznego błędu. Byłoby to niemożliwe w latach 70. lub wymagało superkomputera na początku lat 90., podczas gdy przestrzeń stanowa funkcjonalnego programu językowego o podobnej wielkości była wystarczająco mała, aby o tym wtedy myśleć.

Z pozostałych odpowiedzi zdanie to staje się gwarem, mimo że znaczna trudność, która utrudniała rozumienie języków imperatywnych, jest niszczona przez prawo Moore'a.

Pete Kirkham
źródło
-2

Łatwo uzasadnić to pojęcie specyficzne kulturowo, dlatego tak trudno jest wymyślić konkretne przykłady. Jest to termin zakotwiczony w ludziach, którzy mają rozumować.

„Łatwy do uzasadnienia” to tak naprawdę bardzo opisowe zdanie. Jeśli ktoś patrzy na kod i chce zrozumieć, co robi, to łatwo =)

Okej, rozkładam to. Jeśli patrzysz na kod, zwykle chcesz, żeby coś zrobił. Chcesz się upewnić, że robi to, co Twoim zdaniem powinno. Opracowujesz teorie na temat tego, co powinien robić kod, a następnie zastanawiasz się nad tym, aby spróbować argumentować, dlaczego kod rzeczywiście działa. Próbujesz myśleć o kodzie jak człowiek (a nie jak komputer) i próbujesz zracjonalizować argumenty dotyczące tego, co potrafi kod.

Najgorszym przypadkiem „łatwego do uzasadnienia” jest sytuacja, w której jedynym sposobem na zrozumienie tego, co robi kod, jest przechodzenie przez wiersz po wierszu jak maszyna Turinga dla wszystkich danych wejściowych. W takim przypadku jedynym sposobem na uzasadnienie czegokolwiek w kodzie jest przekształcenie się w komputer i wykonanie go w głowie. Te najgorsze przykłady można łatwo zobaczyć w zaciemnionych konkursach programistycznych, takich jak 3 linie PERL, które odszyfrowują RSA:

#!/bin/perl -sp0777i<X+d*lMLa^*lN%0]dsXx++lMlN/dsM0<j]dsj
$/=unpack('H*',$_);$_=`echo 16dio\U$k"SK$/SM$n\EsN0p[lN*1
lK[d2%Sa2/d0$^Ixp"|dc`;s/\W//g;$_=pack('H*',/((..)*)$/)

Dla łatwości rozumowania, termin ten jest bardzo kulturalny. Musisz wziąć pod uwagę:

  • Jakie umiejętności ma rozumujący? Ile doświadczenia?
  • Jakie pytania może mieć osoba odpowiedzialna na temat kodu?
  • jak pewny musi być powód?

Każdy z nich wpływa inaczej na „łatwy do uzasadnienia”. Weź przykład umiejętności rozumującego. Kiedy zaczynałem w mojej firmie, zalecano mi rozwijanie skryptów w MATLAB-ie, ponieważ „łatwo jest to uzasadnić”. Dlaczego? Cóż, wszyscy w firmie znali MATLAB. Gdybym wybrał inny język, każdemu byłoby trudniej mnie zrozumieć. Nieważne, że czytelność MATLAB jest okropna w przypadku niektórych zadań, po prostu dlatego, że nie została dla nich zaprojektowana. Później, w miarę rozwoju mojej kariery, Python stał się coraz bardziej popularny. Nagle kod MATLAB stał się „trudny do uzasadnienia”, a Python był językiem preferowanym do pisania kodu, który był łatwy do uzasadnienia.

Zastanów się także, jakie treści może mieć czytelnik. Jeśli możesz polegać na czytniku, który rozpoznaje FFT w określonej składni, „łatwiej jest uzasadnić” kod, jeśli trzymasz się tej składni. Pozwala im spojrzeć na plik tekstowy jako płótno, na którym namalowałeś FFT, bez konieczności wchodzenia w szczegółowe szczegóły. Jeśli używasz C ++, dowiedz się, jak bardzo czytelnicy czują się komfortowo z stdbiblioteką. Jak bardzo lubią programowanie funkcjonalne? Niektóre idiomy wychodzące z bibliotek kontenerów są bardzo zależne od preferowanego stylu idomatycznego.

Ważne jest również zrozumienie, na jakie pytania czytelnik może być zainteresowany odpowiedzią. Czy Twoi czytelnicy najbardziej interesują się powierzchownym zrozumieniem kodu, czy też szukają błędów głęboko w jelitach?

To, jak pewny musi być czytelnik, jest w rzeczywistości interesujące. W wielu przypadkach mgliste rozumowanie w rzeczywistości wystarcza, aby wyprowadzić produkt z domu. W innych przypadkach, takich jak oprogramowanie lotu FAA, czytelnik będzie chciał mieć uzasadnienie ironclad. Natknąłem się na przypadek, w którym argumentowałem za użyciem RAII do określonego zadania, ponieważ „Możesz to po prostu skonfigurować i zapomnieć o tym ... zrobi to dobrze”. Powiedziano mi, że się mylę. Ci, którzy zamierzali zastanowić się nad tym kodem, nie byli ludźmi, którzy „chcą zapomnieć o szczegółach”. Dla nich RAII było bardziej jak wiszący czad, zmuszając ich do myślenia o wszystkich rzeczach, które mogą się zdarzyć po opuszczeniu pola widzenia.

Cort Ammon
źródło
12
Kod Perla jest trudny do odczytania ; bez powodu . Gdybym miał jakiś udział w tym, aby to zrozumieć, usunęłbym kod z pamięci. Kod, który tak naprawdę jest trudny do uzasadnienia, to taki, który wciąż jest trudny do uzasadnienia, kiedy jest ładnie sformatowany z wyraźnymi identyfikatorami wszystkiego i bez sztuczek golfowych.
Kaz