Skomentowano krótki wymyślny kod vs. nieprzypisany dłuższy, łatwy do zrozumienia kod - który jest preferowany?

18

Czasami algorytm można zapisać na dwa sposoby:

  • Krótki, fantazyjny sposób; lub
  • Dłuższy, łatwy do zrozumienia sposób.

Na przykład, tutaj jest dłuższy, łatwiejszy sposób kopiowania łańcucha sourcedo destC:

*dest = *source;
while (*source != '\0') {
    source++;
    dest++;
    *dest = *source;
} (true);

A oto krótki, fantazyjny sposób.

// Copy string source to dest
while (*dest++ = *source++);

Zawsze słyszałem i czytałem, że należy unikać wymyślnego kodu i zazwyczaj się zgadzam. Ale co, jeśli weźmiemy pod uwagę komentarze? Załóżmy, że podobnie jak w powyższych przykładach mamy niepomentowany, dłuższy i rzekomo łatwiejszy do zrozumienia kod oraz dobrze skomentowany, krótki, fantazyjny kod? Czy nadal nie jest pożądany kod?

EDYCJA: Wielu skomentowało nazwy zmiennych, więc zmodyfikowałem przykładowy kod, aby nie uczynić tego czynnikiem, gdy wolę nad innymi. Próbowałem usunąć podwójne przypisanie w pierwszym przykładzie, ale to tylko uczyniło kod mniej czytelnym.

Być może nie był to najlepszy z przykładów, ponieważ wielu uważa, że ​​„fantazyjny” kod jest bardziej czytelny i zrozumiały niż dłuższy kod. Chodziło o to, aby mieć jeden dłuższy kod, który był znacznie łatwiejszy do zrozumienia niż bardzo krótki, ale skomplikowany kod.

EDIT2: Oto nowy przykład, który dostałem od SO :

Skomentowana fantazyjna wersja:

//direct formula for xoring all numbers from 1 to N
int Sum = (N & (N % 2 ? 0 : ~0) | ( ((N & 2)>>1) ^ (N & 1) ) );

Długa wersja bez komentarza:

int Sum = 0;
for (int i = 1; i < N; ++i)
{
   Sum ^= i; //or Sum = Sum ^ i;
}
gablin
źródło
2
@gablin: „dobrze” to słowo, ale jest ono przymiotnikiem o nieco innym znaczeniu niż „dobry” i nie jest już powszechnie używane (przynajmniej w Stanach Zjednoczonych). Przysłówkowa forma „dobra” to „dobrze” jak w „dobrze skomentowanym kodzie”.
John M Gant,
@John: Ach tak, oczywiście. Odpowiednio zaktualizowałem pytanie. Dzięki.
gablin
2
W jaki sposób krótka droga jest „fantazyjna”? Jest to tekstowy fragment kodu C, który każdy, kto twierdzi, że zna C, powinien móc czytać bez żadnych problemów. Zwięzłość nie oznacza „fantazji”.
JBRWilkinson
@JBRWilkinson: Jak powiedziałem w zmodyfikowanej części pytania, ten przykład nie jest dobry, ponieważ „fantazyjna” wersja najwyraźniej nie jest wcale taka fantazyjna. Celem było stworzenie jednego krótkiego, ale niezrozumiałego sposobu z komentarzem, a następnie dłuższego, ale o wiele bardziej czytelnego sposobu bez komentarza.
gablin
@gablin: Twoja edycja narusza aktualne odpowiedzi.
Maniero,

Odpowiedzi:

28

Generalnie wolałbym wyodrębnić fantazyjny kod we własnej metodzie.

Zamiast komentować wymyślny kod, nazwa metody powinna być wszystkim, czego potrzeba, aby wszystko było jasne.

char *copy_string(char *s, const char *t) {    
    while (*s++ = *t++); 
    return s;
}
Mongus Pong
źródło
3
Świetny punkt Wyodrębnienie samodzielnego kodu jest znacznie lepsze niż komentarze. Kompilator wie, jak wstawić go w razie potrzeby.
dbkk
Ach tak, to świetny sposób, aby komentarz stał się nieaktualny. Ale czy mimo to zawsze wolałbyś wymyślny kod niż dłuższy?
gablin
Ogólnie tak. Wydaje mi się, że krótkie, dobrze nazwane metody ułatwiają czytanie i utrzymywanie kodu. Złożoność uwzględniona we własnych metodach sprawia, że ​​ponowne użycie jest bardziej dostępne i widoczne. Zawsze są wyjątki, ostatecznie zależy to od kodu.
Mongus Pong,
Z pewnością twoja funkcja powinna mieć nazwę „strcpy”?
JBRWilkinson
Wystąpił błąd w tym fragmencie kodu. Co jest zwracane? Co miało zostać zwrócone? Po prostu zrób z tego pustą metodę i skończ z nią;)
Christian Mann
10

Jestem za dłuższą wersją. Problem z krótką wersją kodu, poza tym, że jest trudniejszy do odczytania dla niektórych programistów, polega na tym, że przez większość czasu trudniej jest dostrzec błędy, patrząc na nie.

Mam przykład z prawdziwego życia. W naszym produkcie mieliśmy następujący fragment kodu:

if (++someCounter < MAX_VALUE) {
    // Do something that has to be done only MAX_VALUE times
}

Ten kod na pierwszy rzut oka wygląda zupełnie rozsądnie, ale po długim czasie ten licznik przepełnia się i psuje warunek (w prawdziwym scenariuszu zmiażdżyliśmy system produkcyjny naszego klienta OOM). Ten kod jest nieco mniej „wyrafinowany”, ale jasne jest, że robi to, co powinien:

if (someCounter < MAX_VALUE) {
    ++someCounter;
    // Do whatever it is we came here for
}

I możesz powiedzieć, że programista, który napisał ten kod, po prostu nie był wystarczająco dobry (co nie jest prawdą, był raczej bystry), ale myślę, że jeśli twoja technika kodowania wymaga od ciebie umiejętności programowania w trybie super-duper BOGA w aby uzyskać poprawny kod, robisz coś złego. Twój kod powinien być jak najbardziej głupi ze względu na ciebie i dla każdego, kto musi zachować ten kod po tobie (który może nie być tak mądry jak ty).

Więc - jestem za prostotą i jasnością.

Edycja: Aha, a jeśli chodzi o komentarze - to nie ma znaczenia. Wiele osób i tak nie przeczyta komentarzy ( http://www.javaspecialists.co.za/archive/Issue039.html ), a ci, którzy nie zrozumieją Twojego kodu bez komentarzy, nie zrozumieją go wystarczająco dobrze, aby mogli mogę to utrzymać. Celem jest, aby pomóc ludziom zobaczyć, że określony kod jest „poprawny”, komentarze nie mogą w tym pomóc.

Hila
źródło
1
„zmiażdżył system produkcyjny naszego klienta” - to całkiem źle: -S
„Problem z krótką wersją kodu, poza tym, że trudniej jest niektórym programistom odczytać” - każdy fragment kodu może być trudny „dla niektórych programistów”. Musisz tylko znaleźć wystarczająco głupich programistów. Nie ogłuszaj kodu do najniższego mianownika.
quant_dev
@quant_dev: odwrotnie: „Debugowanie jest dwa razy trudniejsze niż pisanie kodu. Dlatego, jeśli piszesz kod tak sprytnie, jak to możliwe, z definicji nie jesteś wystarczająco inteligentny, aby go debugować”. - Brian W. Kernighan
Michael Borgwardt
@MichaelBorgwardt Nie zastosowałbym ślepo Kernighana do każdej możliwej sytuacji. Przykład OP jest funkcją napisaną „tak sprytnie, jak to możliwe” (lub blisko niej), a jednak powinna być łatwa do debugowania, jeśli potrzebne jest jakiekolwiek debugowanie. Z drugiej strony skomplikowane, kręcące się nieco oneliner może być bardzo sprytne, ale z pewnością będzie jeszcze trudniejsze do debugowania. (Także: Kernighan zakłada, że ​​umiejętności kodowania = umiejętności debugowania. Nie musi tak być. Udało mi się zdebugować kod, którego nie byłbym w stanie napisać.)
quant_dev
6

Ogólnie wolałbym dłuższą wersję. Przychodzą mi na myśl dwa główne powody:

  • Więcej osób prawdopodobnie zrozumie to przy mniejszym wysiłku (zakładając, że nie jest to tak „standardowy” przykład jak ta kopia ciągu).
  • Możliwe jest umieszczenie punktów przerwania na poszczególnych instrukcjach i przejście przez debugger.

Dla najlepszego z obu światów, zawiń kod w funkcję, której nazwa komentuje za Ciebie:

void copy_string(char *s, char *t)
{
    *s = *t;
    while (*t != '\0') {
        t++;
        s++;
        *s = *t;
    }
}

Jedynym powodem, aby tego nie robić, byłby problem z wydajnością, a profilowanie rzeczywiście pokazało, że narzut funkcji jest znaczący.

Paul Stephenson
źródło
1
Czy nie po to jest inline?
Antsan,
Rzeczywiście, mimo że inlinizacja wiąże się z wieloma problemami, takimi jak konieczność ujawnienia treści funkcji w plikach nagłówkowych. Czy i tak nowoczesne kompilatory nie są automatycznie wbudowane, jeśli jest to rozsądne?
Paul Stephenson,
4

Cokolwiek jest najczystsze. Często jest to kompaktowy, łatwy do zrozumienia fragment kodu, który nie wymaga komentarza ... jak twój drugi przykład.

Zasadniczo krótsze fragmenty kodu są łatwiejsze do zrozumienia, ponieważ czytelnik ma mniej czasu na głowie. Oczywiście jest limit, kiedy kod jest nadmiernie zaciemniony i nie mam na myśli tego, że białe pola zwiększające przejrzystość powinny zostać przycięte.

Komentarze nigdy nie powinny zawierać niczego, co jest oczywiste z kodu, co tylko sprawia, że ​​czytelnik czyta to samo dwa razy. Dla mnie pierwszy fragment wymaga wyjaśnienia. Dlaczego programista nie użył do...whilepętli do usunięcia duplikatu *s = *t;zadania? Muszę przeanalizować więcej kodu, aby zdać sobie sprawę, że implementuje kopię ciągu. Komentarz byłby bardziej pomocny w przypadku dłuższego kodu niż krótszego.

Komentarz do drugiego fragmentu jest prawie zbędny, ponieważ podczas gdy pętla jest praktycznie idiomatyczna, ale mówi, co robi kod na wyższym poziomie niż sam kod, co czyni go użytecznym komentarzem.

CB Bailey
źródło
Deweloper (ja) nie używał do ... whilepo prostu dlatego, że nie pomyślałem o tym, kiedy napisałem pytanie. Dzięki za zwrócenie na to uwagi. ^^ Drugi fragment może być dla ciebie oczywisty, ale z pewnością nie jest dla mnie.
gablin
4

Problem polega na tym, że nie ma jasnej definicji, short fancy codeponieważ zależy to bardzo od poziomu programisty. Podczas gdy niektórzy ludzie nie mają problemu ze zrozumieniem while(*s++ = *t++);wyrażenia, inni tak.

Osobiście uważam, że jest on while(*s++ = *t++);doskonale czytelny, a dłuższy jest trudniejszy do odczytania. Inni mogą się jednak nie zgodzić.

Niezależnie od tego, chodzi tylko o zdrowy rozsądek. Czytelność powinna zdecydowanie być priorytetem, ale jest taki moment, w którym dłuższy kod staje się mniej czytelny, gdy staje się dłuższy. Szczegółowość często zmniejsza czytelność z mojego doświadczenia.

Wolph
źródło
Zasmuca mnie to, że tematem wielu z tych odpowiedzi jest „programiści mogą nie rozpoznać standardowego sposobu pisania strcpy () w C”
AShelly,
Nie powinno - nie znaczy to, że nie ma już dobrych programistów, a raczej: a) nie uczymy już więcej C ib) że siła C jest również jego słabością - w tym czasie, gdy ta pętla jest zrozumiałe dla praktykujących w C, jest również dość tajemnicze, a w świecie, który stara się pisać bezpieczny kod dość przerażający, co sugeruje o tym, co możesz zrobić w C
Murph
Mamy więc kolejne wyzwanie do naszych codziennych zadań. Potrzebuję odgadnąć ogólny poziom zrozumienia języka przez naszych bezpośrednich partnerów, aby nasze recenzje kodu pozostały konstruktywne. Nie wchodzenie w bitwy, którego „poprawnym”, czystszym wyrazem intencji jest.
frogstarr78
2

Nie zgadzam się z większością innych odpowiedzi tutaj - myślę (przynajmniej w tym przypadku), że krótszy kod jest lepszy. Wbrew twierdzeniom, dłuższy kod nie jest „łatwiejszy”, przynajmniej dla czytelnika. Jeśli już, wydaje się, że przestałeś chcieć wydłużyć go, nawet jeśli utrudnia to zrozumienie kodu i / lub zapewnienie prawidłowego działania.

W szczególności, przypisanie pierwszego bajtu łańcucha poza pętlę, oddzielne od przypisania dla innych bajtów, oznacza, że ​​trzeba być znacznie bardziej ostrożnym w czytaniu, aby mieć pewność, że wszystkie bajty zostały poprawnie skopiowane. Podstawowa sekwencja działań w krótszej wersji jest znacznie łatwiejsza do zweryfikowania. Jedynym prawdziwym problemem jest formatowanie - gdy masz celowo puste ciało pętli, najlepiej to wyjaśnić, na przykład:

while (*dest++ = *source++)
    ;

lub nawet:

while (*dest++ = *source++)
    ; /* no body */

Niektóre osoby wolą zamiast tego używać nawiasów klamrowych:

while (*dest++ = *source++)
    {}

Bez względu na to, jakie preferujesz formatowanie, popełniono wystarczająco dużo błędów, takich jak:

if (x>y);
     x = z;

... że ważne jest, aby mieć pewność, że 1) oczywiste jest, co jest właściwie kontrolowane przez dowolną kontrolę przepływu, oraz 2) oczywiste jest, że kod został napisany, wiedząc, co jest kontrolowane przez niego, więc ktoś go czytający nie marnuje czasu na próby aby dowiedzieć się, czy właśnie znaleźli błąd.

Jerry Coffin
źródło
Tak, z perspektywy tego konkretnego przykładu, wersja „fantazyjna” jest prawdopodobnie bardziej czytelna niż wersja dłuższa. Gdybym mógł wymyślić lepszy przykład, zrobiłbym to, ale w tej chwili nie jestem w stanie. Ale nie „zepsułem się”, by wydłużyć - tak na początku napisałbym kopię ciągu. To może nie być najlepsze podejście, ale nie zostało wykonane dłużej niż było to konieczne z intencji.
gablin
2
Keep it simple, stupid!

Upewnij się, że każdy inny programista rozumie, co robi Twój kod - a nawet lepiej, jeśli jest na pierwszy rzut oka (w tym miejscu pojawiają się dobre nazwy i komentarze).

Fantazyjne kręcenie bitów kung-fu i inline montaż są świetne w imię (być może niepotrzebnej i przedwczesnej) optymalizacji, ale kiedy nawet nie możesz zrozumieć kodu, który napisałeś, gdy napotkasz w nim błąd dwa miesiące później ... Jaki był sens? Będziesz kosztować siebie czas i wysiłek.

W takich sytuacjach często odnoszę się do Zen Pythona . W szczególności:

Explicit is better than implicit.
Simple is better than complex.
Humphrey Bogart
źródło
1

Nie pisz wyszukanego kodu w oprogramowaniu produkcyjnym. Wiem, że dobrze jest móc to napisać i jest krótsza. Pisanie fantazyjnego kodu znacznie zwiększa wartość „WTF / minutę”, innymi słowy obniża jakość.

alternatywny tekst

Tamás Szelei
źródło
1

To oczywiście całkowicie zależy od okoliczności. ale ogólnie uważam, że jest to dobra zasada:

Debugowanie jest dwa razy trudniejsze niż pisanie kodu. Dlatego, jeśli piszesz kod tak sprytnie, jak to możliwe, z definicji nie jesteś wystarczająco inteligentny, aby go debugować.

~ Brian Kernighan

fioletowy313
źródło
dłuższe, ale bardziej znaczące, nawet eleganckie , niż bardziej zwarty akronim KISS ~ mam nadzieję, że ironia się nie zgubi; p
violet313
0

Z mojego doświadczenia wynika, że ​​największą wygraną, jeśli chodzi o krótki wymyślny kod, jest raczej rekurencja niż iteracja. Jest to również jedna z najtrudniejszych rzeczy do zrozumienia na pierwszy rzut oka, chyba że pracujesz w języku, w którym wszystko jest rekurencyjne. Nadal będę go faworyzować za elegancję i szybkość rozwoju, jakie oferuje, ale staram się, aby miał szczegółowe komentarze, jeśli wygląda na to, że będzie nieprzejrzysty dla przyszłych opiekunów lub mojego przyszłego ja.

glenatron
źródło
0

Zaczynam nigdy nie ufać komentarzom. Zbyt często komentarze nie są aktualizowane, gdy kod jest aktualizowany, i są znacznie nieaktualne lub reprezentują reguły od klientów / kierownictwa, które już nie są istotne.

Doszło do tego, że w niektórych przypadkach komentarze nawet nie pasują do kodu, który opisują.

Jeśli muszę wybierać między krótkim / fantazyjnym a dłuższym / łatwiejszym do zrozumienia, zawsze wybieram ten drugi, chyba że tak naprawdę jest dobry powód.

Steven Evers
źródło
0

Czytelność i łatwość konserwacji są kluczowe, a twój drugi przykład (tj. Dłuższy) to znacznie więcej. Ale po co ograniczać się do dwóch opcji? Nawet dłuższy kod jest zbyt skomplikowany (IMHO), aby nie notować dalej. Umieściłbym to w swojej własnej metodzie z odpowiednim Javadoc (lub czymkolwiek) i odpowiednią nazwą metody.

Chris Knight
źródło