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 #.
źródło
HX
iGX
wywoł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ą.result_g1
jest rzeczywiście używana, czy też ta wartość faktycznie reprezentuje coś o rozsądnej nazwie; nppercentageIncreasePerSecond
. To byłby mój test, aby zdecydować między nimiOdpowiedzi:
Jeśli czujesz się zmuszony do rozszerzenia jednego liniowca
Nie obwiniłbym cię. To nie tylko trudne do odczytania, ale także trudne do debugowania.
Dlaczego?
Jeśli rozszerzysz go o wyniki pośrednie, otrzymasz
i nadal jest trudny do odczytania. Dlaczego? Rozwiązuje dwa problemy i wprowadza czwarty:
Jest gęstaNiektóre debuggery podświetlą wszystko od razuJeśli rozszerzysz go o nazwy, które dodają nowe, dobre, semantyczne znaczenie, jeszcze lepiej! Dobre imię pomaga mi zrozumieć.
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_this
iresult_that
ponieważ 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:Jest tak samo czytelny, jeśli nie bardziej, niż ten z bezsensownymi nazwami wyników (nie dlatego, że te nazwy funkcji są tak świetne).
Jest gęstaNiektóre debuggery podświetlą wszystko od razuJest zaśmiecona nieopisowymi nazwamiKiedy 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:
Jeśli to nie wystarczy, wyobraź sobie, że
G2()
został powołany w więcej niż jednym miejscu, a potem tak się stało: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:
Nienawidzę patrzeć na ten strumień hałasu, nawet gdy zawijanie słów nie jest potrzebne. Oto jak to wygląda w tym stylu:
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.
źródło
Całkowicie się z tym nie zgadzam. Samo spojrzenie na dwa przykłady kodu wywołuje to jako niepoprawne:
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.
źródło
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.G1
bierze 3 parametry, czy tylko 2 iG2
jest kolejnym parametremF
. Muszę zmrużyć oczy i policzyć nawiasy.F (G1 (H1 b1) (H2 b2)) (G2 c1)
result_h1
nie można jej ponownie użyć, jeśli nie istnieje, a hydraulika między 4 zmiennymi jest oczywisty.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ć.
źródło
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ę:
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
który dzieli dobrze znany wzór na pozornie bezsensowną liczbę prostych kroków. Jednak stwierdzenie z twojego pytania
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()
, lubH2()
. 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.źródło
@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:
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.
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.
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.
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.
źródło
Oba są nieoptymalne. Rozważ komentarze.
Lub konkretne funkcje zamiast ogólnych:
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:
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.
źródło
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.
źródło
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
Możesz pisać wyrażenie oneline i nadal wskazywać, gdzie jest problem, po prostu patrząc na ślad stosu.
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ć:
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:
źródło
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ę”:
Po podzieleniu na etapy (z pewnością łatwiejsze do testowania) traci cały kontekst, jak podano powyżej:
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:
Ten ma bardziej „zbiorowy” sens i łatwiej jest stworzyć warunki testowe:
To stwierdzenie jest po prostu losowym ciągiem znaków, widzianym z perspektywy samok dokumentowania:
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ć.
źródło