Celowe pisanie „głupiego” kodu

10

Wdałem się ostatnio w dyskusję_13th_Dragon odnośnie celowego pisania „głupiego” kodu i ciekawi mnie Wasza opinia na ten temat.

Co było zarzewiem dyskusji — czy kod scanf("%d%d",&a,&b) nie stałby się lepszy, gdyby zapisać go jako scanf("%d %d", &a, &b). W mojej opinii — tak, gdyż ta nadmiarowa spacja pomiędzy %d powoduje, że od razu widać, że funkcja oczekuje dwóch rozdzielonych intów i nie trzeba wiedzieć, jak działa parser, żeby widać było, że jest to poprawny zapis i że nie skończy się tak, że będzie próbował jakoś na siłę podzielić wczytywany ciąg znaków na dwie liczby ani nie zrobi czegoś równie dziwnego. W opinii _13th_Dragona jest to głupie, bo jest to w dokumentacji, a „[c]zytanie dokumentacji - robisz raz, zaś dodawanie zbędnej spacji przez całe życie”.

Ja uważam, że kod się powinno właśnie, w miarę możliwości, pisać możliwie „głupi”. Oczywiście jeśli ma to mieć skutki praktyczne — mniejszą wydajność, mniejszą elastyczność, dłuższą kompilację, cokolwiek — to wtedy te czynniki mogą przeważyć, ale przy braku innych przesłanek (lub ich miałkości, typu „oszczędzę dziesiątą część sekundy na pacnięcie klawisza”) im mniej wystarczy wiedzieć, żeby kod przeczytać i zrozumieć, tym lepiej. Bo czytać go będą różni ludzie, w tym i zmęczeni ludzie, mniej doświadczeni ludzie, czy ludzie pod presją czasu. I, ogólnie, zdarzało mi się żałować, że byłem „sprytny” i coś napisałem w mniej oczywisty sposób (który, rzecz jasna, dokumentacja wyjaśniała; ale udało mi się o tym zapomnieć); natomiast nigdy nie żałowałem, że zrobiłem coś zbyt prosto.

_13th_Dragon uważa inaczej. Dla niego taki kod jest nieprofesjonalny, bo powinniśmy się uczyć i pamiętać, wiedzieć coraz więcej; a nie zostawać w tyle.

Czy chciałbym pracować z kimś, kto nie wie, co robi poniższy kod?

r=y^((x^y)&-(x<y));

Nie, nie chciałbym. Ale pewnie będę. A co więcej, to mogę być kiedyś ja sam, jak mi się będzie spieszyć, jak będę po zarwanej nocce, albo klepał nadgodziny. I wolę mieć kod, który jest „głupi”, niż ryzykować, że ktoś to przeoczy, albo że będzie tracił czas, żeby się zastanowić, czy to minimum, czy maksimum.

I tak samo uważam o kodzie, który potrzebuje dokumentacji. Jak mogę, to piszę jawnie nazwy argumentów do funkcji, by nikt nie tracił czasu, żeby wiedzieć, czy w findPathThroughPoint kolejność to source, midpoint, destination, czy może source, destination, midpoint, czy jakakolwiek inna; tylko wywołam to sobie jako findPathThroughPoint(source=(0, 0), midpoint=fire_target1, destination=fire_target2).

Waszym zdaniem — przesadzam, czy raczej ma to sens?

2

Moim zdaniem obaj macie rację. Ale rozumiem, że nie takiej odpowiedzi oczekujesz. :) Zobaczymy, co powiedzą inni.

8

Ale to chyba nie pytanie czy pisać "głupi" kod tylko czy pisać czytelny.
Więc moja odpowiedź - tak, warto celowo pisać czytelny kod ;)

15

kod jest dla czlowieka nie dla komputera

Kod powinien byc jak najbardziej prosty, przejrzysty i czytelny jak tylko to mozliwe

Jezeli musze sie zastanawiac dluzsza chwile (powyzej 5 sekund?) co robi dana linijka kodu. Dla mnie jest to kod nieczytelny. Czasem ma to swoje uzasadnienie

argument ze spacja (ze trzeba pisac cale zycie) jest niepoprawny (delikatnie mowiac). Na przykladzie ponizej:
Skoro tak bardzo lepszy jest zipowany kod do minimum + dokumentacja to czemu nie zrobic makra i nie pisac po prostu samymi makrami? Jeszcze mniej pisania! (no moze oprocz dokumentacji)

To co praktykuje _13th_Dragon dla mnie jest na rowni z https://www.ioccc.org/

ktos madry powiedzial, ze kod powinien byc pisany w taki sposob, ze Twoja Mama powinna go zrozumiec (z wysokiej perspektywy bez konkretnych szczegolow)

Wystarczy popatrzec na automatyczne formatery kodu jak formatuja kod. W Twoim przykaldzie na scanf, taki formeter zrobilby dokladnie to co Ty podales.
Wiec nawet nie musisz pisac tej spacji! Niech formater doda ja za Ciebie ;)

2

Obaj macie rację. Smoku może nazbyt się spacji czepiał, ale jego punkt widzenia szanuję o tyle, że problemem jest nieznajomość używanych technologii.
Tzn. może jestem spaczony bo siedzę głównie w C++ a ostatnio analizuję źródła boosta, więc może to trochę specyficzne, niemniej:
Ile razy ktoś się na rzucał, bo uparłem się, że fora na review nie puszczę skoro ma gotowy algorytm w STLu, to nie zliczę. Hm, no dobra, zliczę, ale to nie jest rzadkość. Ludzie nie znają technologii, w których piszą. Raczej nie jest to specyficzne dla cpp, bo w Pythonie i C też pracowałem i też było analogicznie.

Pierwsza rzecz: znać własne narzędzia pracy. Język to m.in. narzędzie pracy.
Druga: CI, lintery, auto-format, coding standard. W obecnej pracy jak źle przecinek wyrównam to mi tego commita linter nie wpuści. Pracowałem w projekcie gdzie tego nie było. Ile się nasłuchałem, że się czepiam i mam ludzkie gusta szanować... Serio, ta spacja tam może być lub nie, to jest sprawa gustu, poważnie. Ważniejsze, żeby w ramach większego codebase'u było wszędzie tak samo.

5

@alagner:

Ważniejsze, żeby w ramach większego codebase'u było wszędzie tak samo.

A to ja z kolei uważam, że „tak samo” jest przereklamowane. Czy może inaczej — jeśli się umawiamy, że od teraz robimy wszyscy tak, i poprawiamy stary kod w ramach grzebania w nim, by też był „tak”, to to jest super i tak trzeba robić; ale jak się wyciąga argument, że „kiedyś robiliśmy (źle), to teraz też róbmy (źle); no chyba że komuś się chce przejrzeć sto miliardów linii kodu i poprawić wszystkie (źle) na (dobrze)”, to to… no nie jest najlepsze.

2

@Althorion: ale weź mi nie przypisuj skrajnego niemyślenia proszę ;), jasne, niech to wszystko będzie w ramach jakiegoś konsensusu, to nie o to chodzi żeby robić poteżne refaktoringi. Z drugiej strony: po to jest clang-format i podobne narzędzia, żeby zrobić pewne rzeczy raz a dobrze. Nie mam problemu ze stopniową zmianą, ale w ramach tej zmiany też powinna być konsekwencja.

@fasadin odn. Twojego komentarza: nie zgodzę się. Znajomość (chociaż zgrubna) stdliba to znajomość technologii właśnie. Możesz napisać
if(not isdigit(x)) { //costam } albo if( (x<'0') && (x>'9') ) {//costam}. Które jest czytelniejsze? To nie o to chodzi, żeby to znać na pamięć. Ale masz wiedzieć, gdzie tego szukać. Albo nie wiem, ludzie przesiadający się z C na pythona iterujący po indeksie < len(kontener) bo nie wiedzą, że for jest defaultowo for-eachem. Taki kod to hałas.

2

ja mam na to wyj..... :) pisze tak jak mi sie podoba

0

Jak narzędzia są dobre to warto z nich korzystać, jak nie to nie. Te nowe algorytmy z C++11 i w górę są raczej spoko — dobrze nazwane, z sensowną kolejnością parametrów itd. Więc też bym się upierał, żeby ich używać, i to używać najwęższego mającego zastosowanie.

isdigit jest bardzo spoko, bo nazwa wprost mówi, o co chodzi — więc jest czytelny, zatem i dobry. Jakby jednak w jakimś języku była funkcja belongs_to_class(character, class), a te class to były tam inty… to bym wolał, żeby ktoś pisał if( (x<'0') && (x>'9') ) niż if(not belongs_to_class(x, 5)). Chociaż jak najbardziej można to znaleźć w dokumentacji i jak się w takim dzikim wytworze pisze, to wypada to wiedzieć — ale wolę pisać kod „głupi”, bo „głupi” kod zrozumie i mądry, i głupi; a „mądry” kod — tylko mądrzy.

2

@Althorion: to piszesz metodę fn isDigit (ref x) ->bool { return belongs_to_class(x, 5) } i masz (wymyśliłem pseudojęzyk na poczekaniu, on jest do czegoś podobny?;P). Palce nie odpadną. Tzn. znam takich, którym by odpadły, ale szanujmy się.
Na erase-remove idiom też praktycznie wszędzie mieliśmy wrapper typu erase-if(kontener&, value).

0

No tak bym właśnie zrobił, ale idea była właśnie taka, że jak jakieś narzędzie jest proste, to świetnie i nie ma go co wymyślać na nowo (chyba że, jak w pierwszym poście, są ku temu powody wydajnościowo-różne); ale są narzędzia które proste nie są — w szczególności, scanf IMO nie jest — i wtedy rozpisanie czegoś swojego, a nie zrobienie tego „idiomatycznie” jest IMO raczej potrzebne.

Co oznacza „rozpisanie” — różnie bywa. Czasem to dodatkowa funkcja, czasem jawne wymienienie nazw argumentów, czasem komentarz…

0

dopóki wiadomo co kod robi to wg mnie problemu nie ma. Spacją w tą czy tamtą... Jeżeli działa i jest czytelne to nie ma tematu.

2

Ale spacja po przecinkach (a także przed i po =, + itp.) to coś standardowego, tak się zwykle pisze przecież w szerokim świecie.

1

Spacja w stringu to coś dalece innego, niż światło miedzy operatorami i wartościami.
Akurat TEJ funkcji TA spacja jest obojętna, ale to nawet w ramach scanf'a wyjątek od zasady

Jest jakieś API biblioteki, i poglądy na elegancję nie mają miejsca (ten jeden rzadki kontekst jest WYJATKIEM)

1

@ledi12: tak i nie.

for (std::vector<int>::iterator i=num.begin(), e=num.end(); i != e; ++i) {
  if(*i == 666) { 
   std::cout << "satan\n";
   break;
  }
}

//VS
if(std::find(num.begin(), num.end(), 666) != num.end()) {
   std::cout << "satan\n"
}

Czy w Pythonie

for i in range(len(num)):
  if num[i] == 666: 
    print("satan")
    break

# VS
if 666 in num:
  print("satan")

I teraz. W pierwszym przypadku widzę for, iterację potencjalnie mutującą całą kolekcję, która robi coś tam w ifie jak coś znajdzie.
W drugim kod jest +- 2x krótszy, widoczna jest intencja i złożoność cyklomatyczna też będzie niższa.

EDIT: oczywiście, jak się uprzemy na spacji w tym scanfie to inna sprawa się z tego robi. Ale generalnie "jest w miarę czytelny, to będzie ok" uważam za zbyt duże uproszczenie.

2

Do tej pory żyłem w przekonaniu, że @_13th_Dragon celowo obfuskuje kod, coś w rodzaju Lawful evil od @kq

8

Jak już zostałem wywołany do tablicy to powiem, że wg mnie kod jest dla człowieka, ale nie możemy zmieniać logiki pod czytelność. scanf("%d %d", ...) jest dla mnie nieczytelne, bo od razu zacząłem się zastanawiać po co jest tam ta spacja - spacje w funkcjach z rodziny scanf są przecież znaczące. Dla %d to może być bez znaczenia, ale już wczytanie "%c%c" vs "%c %c" da zupełnie inne rezultaty.

2

Ja tam w if (a && b || c && d) wolę dodać nawiasy.

1

Ja nie wiem co robi poniższy kod

r=y^((x^y)&-(x<y));

i spodziewałbym się jakiegoś komentarza, a jeszcze lepiej extract function i czytelna nazwa.

Nie wiem czy „głupi kod” to jest szczęśliwe określenie. Kod powinien być m. in. prosty, żebym nie musiał spędzać pół życia na code review zastanawiając się ocb. Dlatego nazywamy ładnie zmienne, dbamy o strukturę kodu, piszemy czytelne testy itp itd. Dyskusja chyba na poziomie Clean Codu.

Cytując Grabaża - „całe szczęścię polega na tym, by z prostych rzeczy nie tworzyć intelektualnych labiryntów”.

0

A te scanfy z przykladu sa rownowazne? (nie pamietam c++ ale na logike te wywolanai zwroca cos innego.
A jesli chodzi o formatowanie, gdzie wkladac spacje itp. to ustawiamy formater i on to robi za nas. byle wszysyc uzywali w projekcie takiego samego.

13

[c]zytanie dokumentacji - robisz raz, zaś dodawanie zbędnej spacji przez całe życie”.

Jeszcze 6 lat temu bym się z tym zupełnie zgodził. Nagminnie odsyłałem ludzi do dokumentacji i wkurzałem się, że nie czytają.
Ale teraz uważam, że jeśli kod, framework, język wymaga zaglądania do dokumentacji to po prostu nie jest dobrze zrobiony.
Programista teraz ma do ogarnięcia 2 frameworki i 5 bibliotek na miesiąc. Do tego co 4 miesiące nowy język programowania - jechanie po dokumentacjach jest za wolne.

A wniosek jest taki, że scanf jest do bani, bo nie jest idiotoodporne. Funkcje biblioteczne/api powinno być pisane, żeby korzystanie było oczywiste.

Mam częściej inny problem: czy pisać bardziej idiotoodporne API, za to istotnie trudniejsze w użyciu. Czy łatwiejsze w użyciu, ale za to łatwiej będzie sobie strzelić w stopę.

2

Jarku, wiele prawdy w tym co mówisz, ale C i jego stdlib jakby z definicji idiotoodporne nie są i nie będą. Jeżeli już korzystać z C jako języka aplikacyjnego to jest multum lepszych bibliotek „standardowych”.

1

Czy to nie zależy od języka, w którym się pisze?

C / C++ to (chyba wręcz z założenia) pola minowe: jeśli ktoś nie zna bardzo dobrze specyfikacji tych języków, to strzeli sobie w stopę i to poważnie. Dlatego, być może, w przypadku tych języków bezwzględne wymaganie znajomości dokumentacji i pisanie kodu nieczytelnego dla ludzi nie mających w małym palcu wszystkich niuansów jest korzystne, gdyż tacy ludzie w ogóle nie powinni tykać się kodu pisanego w tych językach (inaczej zaraz będzie UB).

Natomiast większość innych języków odchodzi od takiego podejścia. Raczej jest chyba założenie, że stack technologiczny będący polem minowym to zły stack, chwalona jest za to least astonishment principle. W tych językach argumentacja @jarekr000000 czy @Althorion już ma sens.

1

C++ zwłaszcza (C mniej) jest trudny, fakt (aczkolwiek da się w nich pisać dobrze nie znając specyfikacji na pamięć, kwestia jak są uczone, no ale to inna dyskusja).

Ale patrz na moje przykłady w Pythonie parę postów wstecz. Fakt, nic tam nie wybuchnie, ale mimo prostego API da się w tych prostszych językach pisać po prostu źle.

0
alagner napisał(a):

Czy w Pythonie

for i in range(len(num)):
  if num[i] == 666: 
    print("satan")

# VS
if 666 in num:
  print("satan")

I teraz. W pierwszym przypadku widzę for, iterację potencjalnie mutującą całą kolekcję, która robi coś tam w ifie jak coś znajdzie.
W drugim kod jest +- 2x krótszy, widoczna jest intencja i złożoność cyklomatyczna też będzie niższa.

To zależy.

Dla Ciebie i p-dobnie dla większości forumowiczów tego forum drugi snippet jest o niebo lepszy.

Ale (a przynajmniej tak było za "moich czasów") licea uczyły konstrukcji języka programowania od najbardziej podstawowych. Przeciętny licealista pewnie nie miałby bladego pojęcia, co robi if 666 in num. Jest przyzwyczajony do konstrukcji takich:

for(int i = 0; i < num.size(); i++)
{
    if(num[i] == 666)
    {
        cout << "satan" << endl;
    }
}

Dla kogoś takiego pierwszy snippet mógłby być bardziej czytelny. I tym samym bardziej idiotoodporny.

No chyba, że ten drugi snippet wymusiłby na licealiście doszkolenie się w zakresie innych konstrukcji niż "głupia" pętla for, więc mamy punkt dla @_13th_Dragon

6

wymaganie znajomości dokumentacji i pisanie kodu nieczytelnego dla ludzi nie mających w małym palcu wszystkich niuansów jest korzystne, gdyż tacy ludzie w ogóle nie powinni tykać się kodu pisanego w tych językach (inaczej zaraz będzie UB).

To, że coś jest niebezpieczne i nie powinno być ruszane bez czytania i tak niewielu powstrzyma. Poza tym - Alzheimer programistyczny i tak Cię dopadnie w najmniej spodziewanym momencie.

1
jarekr000000 napisał(a):

Ale teraz uważam, że jeśli kod, framework, język wymaga zaglądania do dokumentacji to po prostu nie jest dobrze zrobiony.

alagner napisał(a):

Jarku, wiele prawdy w tym co mówisz, ale C i jego stdlib jakby z definicji idiotoodporne nie są i nie będą. Jeżeli już korzystać z C jako języka aplikacyjnego to jest multum lepszych bibliotek „standardowych”.

Zgadzam się, że C (i C++, ale tam inaczej akcentuję) jest be.

char * jakaś_funkcja(int a, char * x)

Jak (@jarekr000000) BEZ CZYTANIA dokumentacji prawidłowo użyć?
Powtarza argument (z przesunieciem N ???)
alokuje ? Trzeba zwolnić?
zwraca statyczny bufor? A może jakiś round-robin ?

1

Pytanie czy taki design czegokolwiek jest dobry (bo w koncu jest manual) czy nie? https://blog.codinghorror.com/content/images/uploads/2010/03/6a0120a85dcdae970b01310fd5f5e9970c-800wi.png

1

Kłótnia o jedną spację. Programiści to osobny gatunek człowieka.

jarekr000000 napisał(a):

[c]zytanie dokumentacji - robisz raz, zaś dodawanie zbędnej spacji przez całe życie”.

Jeszcze 6 lat temu bym się z tym zupełnie zgodził. Nagminnie odsyłałem ludzi do dokumentacji i wkurzałem się, że nie czytają.
Ale teraz uważam, że jeśli kod, framework, język wymaga zaglądania do dokumentacji to po prostu nie jest dobrze zrobiony.
Programista teraz ma do ogarnięcia 2 frameworki i 5 bibliotek na miesiąc. Do tego co 4 miesiące nowy język programowania - jechanie po dokumentacjach jest za wolne.

W sumie tak, ale.... czy to jest w ogóle możliwe?

Tzn. nauczenie się frameworka bez przeczytania jego dokumentacji.

Tylko co to jest dokumentacja dla Ciebie Jarek?

Jakiś suchy zrzut metod wszystkich klas, czyli coś w rodzaju tego co produkuje np. javadoc?

Czy może pełen kurs prowadzący użytkownika za rączkę?

Dla mnie dobry framework to taki, gdzie raz przeczytam kurs / wprowadzenie, a potem do dokumentacji API / metod praktycznie nie muszę zaglądać, bo IDE podpowiada mi metody frameworka wraz z krótkim komentarzem.

7

Myślę, że część osób tu się za bardzo czepia takich mikrooptymalizacji czytelności kodu. Lubię jak jest wszędzie spójny styl i zapewne wolałbym aby ktoś użył filter zamiast robić pętlę for. Ale jednak są to dla mnie rzeczy drugorzędne. Jak będzie for albo gdzieś zabraknie spacji i zmarnuje 5 sekund więcej na zrozumienie linijki to wielkiej straty nie ma. No i takie rzeczy powinien załatwiać linter / formatter a one są zadziwiająco dobre w te klocki i to powinno zamykać sprawe roztrząsania takich rzeczy na review.

Natomiast z drugiej strony przez nadmierne skupienie się na powierzchownej czytelności kodu można nie zauważyć lasu innych problemów. Np dla mnie istotne jest ile szczegółów muszę poznać aby zrozumieć co / jak robi dany fragment. I niestety najczęściej okazuje się, że kod jest tak zagmatwany a abstrakcje tak bez sensu że ilość stanu który muszę mieć w głowie aby kod zrozumieć jest znacząco większa niż możliwości mojego mózgu, a przynajmniej tej części podręcznej, szybkiej, krótkoterminowej. I trochę jak z cache CPU, jak coś się nie mieści w cache to potrafi działać 10-100x wolniej.

1 użytkowników online, w tym zalogowanych: 0, gości: 1