Czytelność a łatwość konserwacji, specjalny przypadek pisania zagnieżdżonych wywołań funkcji

57

Mój styl kodowania dla zagnieżdżonych wywołań funkcji jest następujący:

var result_h1 = H1(b1);
var result_h2 = H2(b2);
var result_g1 = G1(result_h1, result_h2);
var result_g2 = G2(c1);
var a = F(result_g1, result_g2);

Niedawno zmieniłem na dział, w którym bardzo często stosuje się następujący styl kodowania:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Wynikiem mojego sposobu kodowania jest to, że w przypadku awarii program Visual Studio może otworzyć odpowiedni zrzut i wskazać linię, w której występuje problem (szczególnie martwię się o naruszenia praw dostępu).

Obawiam się, że w przypadku awarii z powodu tego samego problemu zaprogramowanego w pierwszej kolejności, nie będę w stanie wiedzieć, która funkcja spowodowała awarię.

Z drugiej strony, im więcej przetwarzania włożysz w linię, tym więcej logiki znajdziesz na jednej stronie, co poprawia czytelność.

Czy mój strach jest prawidłowy, czy coś mi brakuje i ogólnie, co jest preferowane w środowisku komercyjnym? Czytelność czy łatwość konserwacji?

Nie wiem, czy to jest istotne, ale pracujemy w C ++ (STL) / C #.

Dominique
źródło
17
@gnat: odwołujesz się do ogólnego pytania, podczas gdy szczególnie interesuje mnie wspomniany przypadek zagnieżdżonych wywołań funkcji i konsekwencje w przypadku analizy zrzutu awaryjnego, ale dzięki za łącze, zawiera on całkiem interesujące informacje.
Dominique
9
Zauważ, że jeśli ten przykład miałby być zastosowany do C ++ (jak wspominasz o tym, że jest używany w twoim projekcie), to nie jest to tylko kwestia stylu, ponieważ kolejność oceny HXi GXwywołań może ulec zmianie w jednym wierszu, ponieważ kolejność oceny argumentów funkcji jest nieokreślona. Jeśli z jakiegoś powodu zależysz od kolejności skutków ubocznych (świadomie lub nieświadomie) w wywołaniach, to „refaktoryzacja stylu” może skończyć się czymś więcej niż tylko czytelnością / konserwacją.
drri
4
Czy nazwa zmiennej result_g1jest rzeczywiście używana, czy też ta wartość faktycznie reprezentuje coś o rozsądnej nazwie; np percentageIncreasePerSecond. To byłby mój test, aby zdecydować między nimi
Richard Tingle
3
Niezależnie od twoich odczuć co do stylu kodowania, powinieneś postępować zgodnie z konwencją, która już obowiązuje, chyba że jest wyraźnie błędna (nie wydaje się, że tak jest w tym przypadku).
n00b
4
@ t3chb0t Możesz głosować w dowolny sposób, ale pamiętaj, że w celu zachęcania do zadawania dobrych, użytecznych, tematycznych pytań na tej stronie (i zniechęcania złych), celem głosowania w górę lub w dół jest pytanie wskazanie, czy pytanie jest użyteczne i jasne, dlatego głosowanie z innych powodów, takich jak głosowanie jako środek do krytyki niektórych przykładowych kodów zamieszczonych w celu ułatwienia kontekstu pytania, ogólnie nie jest pomocne w utrzymaniu jakości witryny : softwareengineering.stackexchange.com/help/privileges/vote-down
Ben Cottrell

Odpowiedzi:

111

Jeśli czujesz się zmuszony do rozszerzenia jednego liniowca

 a = F(G1(H1(b1), H2(b2)), G2(c1));

Nie obwiniłbym cię. To nie tylko trudne do odczytania, ale także trudne do debugowania.

Dlaczego?

  1. Jest gęsta
  2. Niektóre debuggery podświetlą wszystko od razu
  3. Jest wolny od opisowych nazw

Jeśli rozszerzysz go o wyniki pośrednie, otrzymasz

 var result_h1 = H1(b1);
 var result_h2 = H2(b2);
 var result_g1 = G1(result_h1, result_h2);
 var result_g2 = G2(c1);
 var a = F(result_g1, result_g2);

i nadal jest trudny do odczytania. Dlaczego? Rozwiązuje dwa problemy i wprowadza czwarty:

  1. Jest gęsta
  2. Niektóre debuggery podświetlą wszystko od razu
  3. Jest wolny od opisowych nazw
  4. Jest zaśmiecona nieopisowymi nazwami

Jeśli rozszerzysz go o nazwy, które dodają nowe, dobre, semantyczne znaczenie, jeszcze lepiej! Dobre imię pomaga mi zrozumieć.

 var temperature = H1(b1);
 var humidity = H2(b2);
 var precipitation = G1(temperature, humidity);
 var dewPoint = G2(c1);
 var forecast = F(precipitation, dewPoint);

Teraz przynajmniej opowiada historię. Rozwiązuje problemy i jest wyraźnie lepszy niż cokolwiek innego oferowanego tutaj, ale wymaga wymyślenia nazw.

Jeśli robisz to z bezsensownymi nazwami, takimi jak result_thisi result_thatponieważ po prostu nie potrafisz wymyślić dobrych imion, naprawdę wolałbym, abyś oszczędził nam bezsensownego bałaganu nazw i rozwinął go za pomocą starej dobrej białej spacji:

int a = 
    F(
        G1(
            H1(b1), 
            H2(b2)
        ), 
        G2(c1)
    )
;

Jest tak samo czytelny, jeśli nie bardziej, niż ten z bezsensownymi nazwami wyników (nie dlatego, że te nazwy funkcji są tak świetne).

  1. Jest gęsta
  2. Niektóre debuggery podświetlą wszystko od razu
  3. Jest wolny od opisowych nazw
  4. Jest zaśmiecona nieopisowymi nazwami

Kiedy nie możesz wymyślić dobrych imion, to jest tak dobre, jak to tylko możliwe.

Z jakiegoś powodu debugery uwielbiają nowe linie, dlatego powinieneś przekonać się, że debugowanie nie jest trudne:

wprowadź opis zdjęcia tutaj

Jeśli to nie wystarczy, wyobraź sobie, że G2()został powołany w więcej niż jednym miejscu, a potem tak się stało:

Exception in thread "main" java.lang.NullPointerException
    at composition.Example.G2(Example.java:34)
    at composition.Example.main(Example.java:18)

Myślę, że to miłe, że ponieważ każde G2()połączenie odbywa się na jego własnej linii, ten styl przenosi Cię bezpośrednio do obrażającego połączenia głównego.

Dlatego nie używaj problemów 1 i 2 jako pretekstu, aby poradzić sobie z problemem 4. Używaj dobrych nazwisk, kiedy możesz o nich myśleć. Unikaj bezsensownych nazw, kiedy nie możesz.

Lekkość Wyścigi w komentarzu Orbity poprawnie wskazują, że te funkcje są sztuczne i same mają martwe, złe nazwy. Oto przykład zastosowania tego stylu do dzikiego kodu:

var user = db.t_ST_User.Where(_user => string.Compare(domain,  
_user.domainName.Trim(), StringComparison.OrdinalIgnoreCase) == 0)
.Where(_user => string.Compare(samAccountName, _user.samAccountName.Trim(), 
StringComparison.OrdinalIgnoreCase) == 0).Where(_user => _user.deleted == false)
.FirstOrDefault();

Nienawidzę patrzeć na ten strumień hałasu, nawet gdy zawijanie słów nie jest potrzebne. Oto jak to wygląda w tym stylu:

var user = db
    .t_ST_User
    .Where(
        _user => string.Compare(
            domain, 
            _user.domainName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(
        _user => string.Compare(
            samAccountName, 
            _user.samAccountName.Trim(), 
            StringComparison.OrdinalIgnoreCase
        ) == 0
    )
    .Where(_user => _user.deleted == false)
    .FirstOrDefault()
;

Jak widać, zauważyłem, że ten styl działa dobrze z kodem funkcjonalnym, który porusza się w przestrzeni zorientowanej obiektowo. Jeśli potrafisz wymyślić dobre nazwiska, aby zrobić to w stylu pośrednim, to więcej mocy dla ciebie. Do tego czasu używam tego. Ale w każdym razie, znajdź jakiś sposób na uniknięcie bezsensownych nazw wyników. Bolą mnie oczy.

candied_orange
źródło
20
@ Steve i nie mówię ci, żebyś tego nie robił. Błagam o sensowne imię. Często widziałem styl pośredni bezmyślnie. Złe nazwy palą mój mózg bardziej niż rzadki kod na linię. Nie pozwalam, aby względy szerokości i długości motywowały mnie do tego, aby mój kod był gęsty lub krótkie. Pozwalam im zmotywować mnie do dalszego rozkładu. Jeśli dobre imię się nie zdarzy, rozważ tę pracę, aby uniknąć bezsensownego hałasu.
candied_orange
6
Dodaję do twojego postu: Mam małą ogólną zasadę: jeśli nie możesz jej nazwać, może to oznaczać, że nie jest dobrze zdefiniowana. Używam go w obiektach, właściwościach, zmiennych, modułach, menu, klasach pomocniczych, metodach itp. W wielu sytuacjach ta niewielka reguła ujawniła poważną wadę projektową. W ten sposób dobre nazewnictwo nie tylko przyczynia się do czytelności i łatwości konserwacji, ale także pomaga zweryfikować projekt. Oczywiście są wyjątki od każdej prostej reguły.
Alireza
4
Wersja rozszerzona wygląda brzydko. Jest tam zbyt wiele białych znaków, co zmniejsza efekt, ponieważ każdy z nich jest phasizedwithit, co oznacza nic.
Mateen Ulhaq
5
@MateenUlhaq Jedyną dodatkową spacją jest kilka znaków nowej linii i wcięcia, a wszystko to jest starannie umieszczone na znaczących granicach. Twój komentarz zamiast tego umieszcza białe znaki na nieistotnych granicach. Sugeruję, abyś spojrzał nieco bliżej i bardziej otwarcie.
jpmc26
3
W przeciwieństwie do @MateenUlhaq, w tym konkretnym przykładzie jestem po omacku ​​białych znaków z takimi nazwami funkcji, ale z prawdziwymi nazwami funkcji (które mają więcej niż dwa znaki długości, prawda?) To może być to, co wybrałbym.
Wyścigi lekkości z Moniką
50

Z drugiej strony, im więcej przetwarzania włożysz w linię, tym więcej logiki znajdziesz na jednej stronie, co poprawia czytelność.

Całkowicie się z tym nie zgadzam. Samo spojrzenie na dwa przykłady kodu wywołuje to jako niepoprawne:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

słyszy się czytać. „Czytelność” nie oznacza gęstości informacji; oznacza „łatwy do odczytania, zrozumienia i utrzymania”.

Czasami kod jest prosty i warto użyć jednej linii. Innym razem sprawia to, że trudniej jest czytać, bez żadnej oczywistej korzyści poza wbijaniem więcej w jedną linię.

Zadzwonię jednak również do ciebie, twierdząc, że „łatwa do zdiagnozowania awaria” oznacza, że ​​kod jest łatwy w utrzymaniu. Kod, który nie ulega awarii, jest o wiele łatwiejszy do utrzymania. „Łatwy w utrzymaniu” jest osiągany przede wszystkim dzięki temu, że kod jest łatwy do odczytania i zrozumienia, poparty dobrym zestawem automatycznych testów.

Jeśli więc zamieniasz jedno wyrażenie w wielowierszowe z wieloma zmiennymi tylko dlatego, że kod często ulega awarii i potrzebujesz lepszych informacji debugowania, przestań to robić i zamiast tego popraw kod. Wolisz pisać kod, który nie wymaga debugowania, niż kod łatwy do debugowania.

David Arno
źródło
37
Chociaż zgadzam się, że F(G1(H1(b1), H2(b2)), G2(c1))jest to trudne do odczytania, nie ma to nic wspólnego z zbyt gęstym stłoczeniem. (Nie jestem pewien, czy chciałeś to powiedzieć, ale można to interpretować w ten sposób.) Zagnieżdżenie trzech lub czterech funkcji w jednym wierszu może być doskonale czytelne, w szczególności jeśli niektóre z funkcji są prostymi operatorami infix. Problem stanowią tutaj nieopisowe nazwy, ale problem ten jest jeszcze gorszy w wersji wieloliniowej, w której wprowadzono jeszcze więcej nieopisowych nazw . Dodanie tylko płyty kotłowej prawie nigdy nie poprawia czytelności.
leftaroundabout
23
@leftaroundabout: Dla mnie trudność polega na tym, że nie jest oczywiste, czy G1bierze 3 parametry, czy tylko 2 i G2jest kolejnym parametrem F. Muszę zmrużyć oczy i policzyć nawiasy.
Matthieu M.
4
@MatthieuM. może to stanowić problem, chociaż jeśli funkcje są dobrze znane, często oczywiste jest, ile argumentów. W szczególności, jak powiedziałem, w przypadku funkcji infiksów natychmiast widać, że przyjmują dwa argumenty. (Również składnia w nawiasach-krotek, z której korzysta większość języków, nasila ten problem; w języku preferującym Currying jest on automatycznie wyraźniejszy F (G1 (H1 b1) (H2 b2)) (G2 c1)
:.
5
Osobiście wolę bardziej zwartą formę, o ile jest wokół niej stylizacja, jak w moim poprzednim komentarzu, ponieważ gwarantuje mniejszy stan mentalnego śledzenia - result_h1nie można jej ponownie użyć, jeśli nie istnieje, a hydraulika między 4 zmiennymi jest oczywisty.
Izkata
8
Odkryłem, że kod, który łatwo jest debugować, to na ogół kod, który nie wymaga debugowania.
Rob K
25

Twój pierwszy przykład, formularz pojedynczego przydziału, jest nieczytelny, ponieważ wybrane nazwy są całkowicie pozbawione znaczenia. To może być artefakt, który próbuje nie ujawniać wewnętrznych informacji z twojej strony, prawdziwy kod może być w tym względzie w porządku, nie możemy powiedzieć. Tak czy inaczej, jest on długo rozwiązywany ze względu na wyjątkowo niską gęstość informacji, co zwykle nie jest łatwe do zrozumienia.

Twój drugi przykład jest skondensowany w absurdalnym stopniu. Jeśli funkcje mają przydatne nazwy, może być dobrze i dobrze czytelne, ponieważ nie ma ich zbyt wiele , ale jak to jest mylące w przeciwnym kierunku.

Po wprowadzeniu znaczących nazw, możesz sprawdzić, czy jedna z form wydaje się naturalna, czy też istnieje złoty środek, za którym można strzelać.

Teraz, gdy masz czytelny kod, większość błędów będzie oczywista, a innym trudniej będzie się przed tobą ukryć.

Deduplikator
źródło
17

Jak zawsze, jeśli chodzi o czytelność, awaria znajduje się w skrajności . Możesz skorzystać z każdej dobrej porady programistycznej, zmienić ją w regułę religijną i użyć jej do stworzenia całkowicie nieczytelnego kodu. (Jeśli nie wierz mi na to, sprawdź te dwa IOCCC zwycięzców borsanyi i Goren i przyjrzeć się, jak różnie wykorzystują funkcje do renderowania kodu zupełnie nieczytelny. Wskazówka: Borsanyi używa dokładnie jedną funkcję, Goren dużo, dużo więcej ...)

W twoim przypadku dwie skrajności to: 1) użycie tylko wyrażeń pojedynczych i 2) łączenie wszystkiego w duże, zwięzłe i złożone. Każde z tych podejść do ekstremum powoduje, że kod jest nieczytelny.

Twoim zadaniem jako programisty jest osiągnięcie równowagi . Do każdego pisma, które piszesz, Twoim zadaniem jest odpowiedzieć na pytanie: „Czy to stwierdzenie jest łatwe do uchwycenia i czy służy ono do odczytania mojej funkcji?”


Chodzi o to, że nie ma jednej mierzalnej złożoności instrukcji, która decydowałaby o tym, co warto zawrzeć w jednej instrukcji. Weźmy na przykład linię:

double d = sqrt(square(x1 - x0) + square(y1 - y0));

Jest to dość złożone stwierdzenie, ale każdy programista wart swojej soli powinien być w stanie natychmiast zrozumieć, co to robi. Jest to dość dobrze znany wzór. Jako taki jest znacznie bardziej czytelny niż jego odpowiednik

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

który dzieli dobrze znany wzór na pozornie bezsensowną liczbę prostych kroków. Jednak stwierdzenie z twojego pytania

var a = F(G1(H1(b1), H2(b2)), G2(c1));

wydaje mi się zbyt skomplikowane, nawet jeśli jest to jedna operacja mniejsza niż obliczenie odległości . Oczywiście, że jest bezpośrednią konsekwencją mnie nie wiedząc nic na temat F(), G1(), G2(), H1(), lub H2(). Mógłbym inaczej zdecydować, gdybym wiedział o nich więcej. Ale to jest właśnie problem: wskazana złożoność instrukcji silnie zależy od kontekstu i zaangażowanych operacji. A Ty, jako programista, musisz spojrzeć na ten kontekst i zdecydować, co uwzględnić w pojedynczej instrukcji. Jeśli zależy Ci na czytelności, nie możesz odciążyć tej odpowiedzialności od niektórych zasad statycznych.

cmaster
źródło
14

@Dominique, myślę, że w analizie twojego pytania popełniasz błąd, że „czytelność” i „łatwość konserwacji” to dwie odrębne rzeczy.

Czy jest możliwe utrzymanie kodu, który można utrzymać, ale jest nieczytelny? I odwrotnie, jeśli kod jest wyjątkowo czytelny, dlaczego stałby się niemożliwy do utrzymania ze względu na jego czytelność? Nigdy nie słyszałem o żadnym programiście, który grałby te czynniki przeciwko sobie, który musiałby wybrać jeden lub drugi!

Jeśli chodzi o decyzję, czy użyć zmiennych pośrednich do wywołań funkcji zagnieżdżonych, w przypadku 3 podanych zmiennych wywołuje 5 oddzielnych funkcji, a niektóre wywołania zagnieżdżają 3 głębokie, chciałbym użyć co najmniej niektórych zmiennych pośrednich, aby to rozbić, tak jak zrobiłeś.

Ale z całą pewnością nie posuwam się do stwierdzenia, że ​​wywołania funkcji nigdy nie mogą być zagnieżdżone. Jest to kwestia oceny sytuacji.

Powiedziałbym, że następujące punkty odnoszą się do wyroku:

  1. Jeśli wywoływane funkcje reprezentują standardowe operacje matematyczne, są one bardziej podatne na zagnieżdżanie niż funkcje reprezentujące niejasną logikę domeny, której wyniki są nieprzewidywalne i niekoniecznie muszą być ocenione mentalnie przez czytelnika.

  2. Funkcja z jednym parametrem jest bardziej zdolna do uczestniczenia w gnieździe (jako funkcja wewnętrzna lub zewnętrzna) niż funkcja z wieloma parametrami. Mieszanie funkcji różnych rodzajów na różnych poziomach zagnieżdżania jest podatne na pozostawienie kodu wyglądającego jak ucho świni.

  3. Gniazdo funkcji, które programiści są przyzwyczajeni widzieć, wyrażone w określony sposób - być może dlatego, że reprezentuje standardową technikę matematyczną lub równanie, które ma standardową implementację - może być trudniejsze do odczytania i sprawdzenia, czy jest ono podzielone na zmienne pośrednie.

  4. Niewielkie gniazdo wywołań funkcji, które wykonuje prostą funkcjonalność i jest już czytelne, a następnie nadmiernie rozbite i rozpylone, może być trudniejsze do odczytania niż takie, które nie zostało w ogóle rozbite.

Steve
źródło
3
Od +1 do „Czy można mieć kod, który można utrzymać, ale jest nieczytelny?”. To była moja pierwsza myśl.
RonJohn
4

Oba są nieoptymalne. Rozważ komentarze.

// Calculating torque according to Newton/Dominique, 4th ed. pg 235
var a = F(G1(H1(b1), H2(b2)), G2(c1));

Lub konkretne funkcje zamiast ogólnych:

var a = Torque_NewtonDominique(b1,b2,c1);

Podejmując decyzję, które wyniki należy przeliterować, należy pamiętać o koszcie (kopiowanie a odniesienie, wartość l vs wartość r), czytelność i ryzyko, indywidualnie dla każdego stwierdzenia.

Na przykład przenoszenie prostych konwersji jednostek / typów na własne linie nie ma żadnej wartości dodanej, ponieważ są one łatwe do odczytania i bardzo mało prawdopodobne, aby zawiodły:

var radians = ExtractAngle(c1.Normalize())
var a = Torque(b1.ToNewton(),b2.ToMeters(),radians);

Jeśli chodzi o obawy związane z analizą zrzutów awaryjnych, sprawdzanie poprawności danych wejściowych jest zwykle o wiele ważniejsze - istnieje prawdopodobieństwo, że rzeczywista awaria nastąpi w obrębie tych funkcji zamiast w linii, która je wywołuje, a nawet jeśli nie, zwykle nie trzeba dokładnie mówić, gdzie dokładnie wszystko wybuchło. O wiele ważniejsze jest wiedzieć, gdzie rzeczy zaczęły się rozpadać, niż wiedzieć, gdzie w końcu wybuchły, i to właśnie łapie sprawdzanie poprawności danych wejściowych.

Piotr
źródło
Ponowny koszt przekazania argumentu: Istnieją dwie zasady optymalizacji. 1) Nie. 2) (tylko dla ekspertów) Jeszcze nie .
RubberDuck
1

Czytelność jest główną częścią łatwości konserwacji. Wątpisz we mnie? Wybierz duży projekt w języku, którego nie znasz (prawdopodobnie zarówno język programowania, jak i język programistów) i zobacz, jak byś go przerobił ...

Umieściłbym czytelność jako pomiędzy 80 a 90 łatwości konserwacji. Pozostałe 10–20 procent to podatność na refaktoryzację.

To powiedziawszy, skutecznie przekazujesz 2 zmienne do swojej ostatecznej funkcji (F). Te 2 zmienne są tworzone przy użyciu 3 innych zmiennych. Lepiej byłoby przekazać b1, b2 i c1 do F, jeśli F już istnieje, to utwórz D, który tworzy skład dla F i zwraca wynik. W tym momencie chodzi tylko o nadanie D dobrego imienia i nie będzie miało znaczenia, jakiego stylu użyjesz.

Jeśli chodzi o pokrewną nie, mówisz, że więcej logiki na stronie pomaga w czytelności. To niepoprawne, metryka nie jest stroną, to metoda, a im mniej logiki zawiera metoda, tym bardziej jest ona czytelna.

Czytelny oznacza, że ​​programista może trzymać logikę (wejście, wyjście i algorytm) w głowie. Im więcej to robi, tym mniej programista może to zrozumieć. Przeczytaj o złożoności cyklicznej.

jmoreno
źródło
1
Zgadzam się ze wszystkim, co mówisz o czytelności. Ale nie zgadzam się, że pękanie logiczną operację na odrębne sposoby, niekoniecznie czyni go bardziej czytelny niż pękanie go w oddzielnych liniach (obie techniki, które mogą , gdy nadużywane, sprawiają prosta logika mniej czytelne, i sprawiają, że cały program bardziej zaśmiecone) - jeśli rozbijesz metody zbyt daleko, skończysz na emulowaniu makr języka asemblerowego i stracisz z oczu sposób, w jaki integrują się one jako całość. Ponadto w tej osobnej metodzie nadal będziesz mieć do czynienia z tym samym problemem: zagnieżdżaj wywołania lub rozbij je na zmienne pośrednie.
Steve
@ Steve: Nie powiedziałem, aby zawsze to robić, ale jeśli myślisz o użyciu 5 wierszy, aby uzyskać pojedynczą wartość, istnieje duża szansa, że ​​funkcja byłaby lepsza. Jeśli chodzi o wiele linii vs. linia złożona: jeśli jest to funkcja o dobrej nazwie, obie będą działać równie dobrze.
jmoreno
1

Niezależnie od tego, czy jesteś w C # lub C ++, tak długo jak jesteś w kompilacji debugowania, możliwym rozwiązaniem jest zawijanie funkcji

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Możesz pisać wyrażenie oneline i nadal wskazywać, gdzie jest problem, po prostu patrząc na ślad stosu.

returnType F( params)
{
    returnType RealF( params);
}

Oczywiście, jeśli wywołasz tę samą funkcję wiele razy w tym samym wierszu, nie możesz wiedzieć, która to funkcja, ale nadal możesz ją zidentyfikować:

  • Patrząc na parametry funkcji
  • Jeśli parametry są identyczne, a funkcja nie ma skutków ubocznych, wówczas dwa identyczne wywołania stają się 2 identycznymi wywołaniami itp.

To nie jest srebrna kula, ale niezły sposób w połowie drogi.

Nie wspominając, że zawijanie grupy funkcji może być nawet bardziej korzystne dla czytelności kodu:

type CallingGBecauseFTheorem( T b1, C b2)
{
     return G1( H1( b1), H2( b2));
}

var a = F( CallingGBecauseFTheorem( b1,b2), G2( c1));
Twórca gier
źródło
1

Moim zdaniem, samodokumentujący się kod jest lepszy zarówno pod względem łatwości konserwacji, jak i czytelności, niezależnie od języka.

Powyższe stwierdzenie jest gęste, ale „samo dokumentuje się”:

double d = sqrt(square(x1 - x0) + square(y1 - y0));

Po podzieleniu na etapy (z pewnością łatwiejsze do testowania) traci cały kontekst, jak podano powyżej:

double dx = x1 - x0;
double dy = y1 - y0;
double dxSquare = square(dx);
double dySquare = square(dy);
double dSquare = dxSquare + dySquare;
double d = sqrt(dSquare);

Oczywiście użycie nazw zmiennych i funkcji, które jasno określają ich przeznaczenie, jest nieocenione.

Nawet bloki „jeśli” mogą być dobre lub złe w samo-dokumentowaniu. Jest to złe, ponieważ nie można łatwo zmusić pierwszych 2 warunków do przetestowania trzeciego ... wszystkie są niezwiązane:

if (Bill is the boss) && (i == 3) && (the carnival is next weekend)

Ten ma bardziej „zbiorowy” sens i łatwiej jest stworzyć warunki testowe:

if (iRowCount == 2) || (iRowCount == 50) || (iRowCount > 100)

To stwierdzenie jest po prostu losowym ciągiem znaków, widzianym z perspektywy samok dokumentowania:

var a = F(G1(H1(b1), H2(b2)), G2(c1));

Patrząc na powyższe stwierdzenie, łatwość utrzymania jest nadal dużym wyzwaniem, jeśli zarówno funkcje H1, jak i H2 zmieniają te same „zmienne stanu systemu” zamiast być zunifikowane w pojedynczej funkcji „H”, ponieważ ktoś ostatecznie zmieni H1, nawet nie myśląc, że istnieje Funkcja H2, na którą można spojrzeć i może uszkodzić H2.

Uważam, że dobry projekt kodu jest bardzo trudny, ponieważ nie ma twardych i szybkich reguł, które można systematycznie wykrywać i egzekwować.

Ozymandias
źródło