Rozmawiam z moimi nowymi kolegami o komentarzach. Oboje lubimy Clean Code , i jestem całkowicie w porządku z faktem, że należy unikać komentarzy do kodu wbudowanego, a nazwy klas i metod powinny być używane do wyrażania tego, co robią.
Jednak jestem wielkim fanem dodawania małych podsumowań klas, które próbują wyjaśnić cel klasy i to, co faktycznie reprezentuje, przede wszystkim po to, aby łatwo było utrzymać zasadę pojedynczej odpowiedzialności . Przyzwyczaiłem się również do dodawania jednoetapowych podsumowań do metod, które wyjaśniają, co ma zrobić ta metoda. Typowym przykładem jest prosta metoda
public Product GetById(int productId) {...}
Dodaję następujące podsumowanie metody
/// <summary>
/// Retrieves a product by its id, returns null if no product was found.
/// </summary
Uważam, że fakt, że metoda zwraca null, powinien zostać udokumentowany. Deweloper, który chce wywołać metodę, nie powinien otwierać mojego kodu, aby sprawdzić, czy metoda zwraca wartość null lub zgłasza wyjątek. Czasami jest to część interfejsu, więc programista nawet nie wie, który kod źródłowy jest uruchomiony?
Jednak moi koledzy uważają, że tego rodzaju komentarze są „ zapachem kodu ” i że „komentarze są zawsze błędami” ( Robert C. Martin ).
Czy istnieje sposób na wyrażenie i przekazanie tego rodzaju wiedzy bez dodawania komentarzy? Ponieważ jestem wielkim fanem Roberta C. Martina, jestem trochę zdezorientowany. Czy streszczenia są takie same jak komentarze, a zatem zawsze zawodzą?
To nie jest pytanie o komentarze w linii.
źródło
Odpowiedzi:
Jak powiedzieli inni, istnieje różnica między komentarzami dokumentującymi API a komentarzami wbudowanymi. Z mojej perspektywy główna różnica polega na tym, że komentarz wbudowany jest czytany obok kodu , podczas gdy komentarz do dokumentacji jest czytany obok podpisu tego, co komentujesz.
Biorąc to pod uwagę, możemy zastosować tę samą zasadę DRY . Czy komentarz mówi to samo co podpis? Spójrzmy na twój przykład:
Ta część po prostu powtarza to, co już widzimy z nazwy
GetById
plus typ zwrotuProduct
. Rodzi to także pytanie, na czym polega różnica między „pobieraniem” a „odzyskiwaniem” i jaki kod namiarny względem komentarza ma to rozróżnienie. Jest to więc niepotrzebne i nieco mylące. Jeśli już, przeszkadza to naprawdę przydatna, druga część komentarza:Ach! Jest to coś, czego na pewno nie wiemy na podstawie samego podpisu i dostarcza użytecznych informacji.
Teraz przejdź o krok dalej. Gdy ludzie mówią o komentarzach, gdy zapach pachnie, pytanie nie brzmi, czy kod taki, jaki jest, wymaga komentarza, ale czy komentarz wskazuje, że kod można lepiej napisać, aby wyrazić informacje w komentarzu. To właśnie oznacza „zapach kodu” - to nie znaczy „nie rób tego!”, Oznacza „jeśli to robisz, może to oznaczać, że wystąpił problem”.
Więc jeśli twoi koledzy powiedzą ci, że ten komentarz o wartości null jest zapachem kodu, powinieneś po prostu zapytać ich: „W porządku, jak mam to wyrazić?” Jeśli mają wykonalną odpowiedź, nauczyłeś się czegoś. Jeśli nie, prawdopodobnie zabiją ich skargi.
Jeśli chodzi o ten konkretny przypadek, ogólnie wiadomo, że problem zerowy jest trudny. Istnieje powód, dla którego podstawy kodu są wypełnione klauzulami ochronnymi, dlaczego sprawdzanie wartości zerowej jest popularnym warunkiem umów o kod, dlaczego istnienie wartości zerowej nazwano „błędem w wysokości miliarda dolarów”. Nie ma tak wielu wykonalnych opcji. Jednak jednym z popularnych w C # jest
Try...
konwencja:W innych językach idiomatyczne może być użycie typu (często nazywanego jak
Optional
lubMaybe
) w celu wskazania wyniku, który może, ale nie musi:W pewnym sensie ta postawa anty-komentarzowa gdzieś nas zaprowadziła: przynajmniej zastanawialiśmy się, czy ten komentarz reprezentuje zapach i jakie mogą być dla nas alternatywy.
To, czy rzeczywiście powinniśmy preferować je w stosunku do oryginalnego podpisu, to zupełnie inna debata, ale mamy przynajmniej opcje wyrażania za pomocą kodu zamiast komentowania tego, co dzieje się, gdy nie znajdzie się żadnego produktu. Powinieneś przedyskutować ze swoimi kolegami, które z tych opcji są lepsze i dlaczego, i mam nadzieję, że pomogą ci wyjść poza ogólne dogmatyczne wypowiedzi na temat komentarzy.
źródło
Linq
-equivalent zTry...
,...OrDefault
, który powrócidefault(T)
, jeśli klauzula doprowadziłoby do pustego wyniku.TryGetValue
Wzór jest rozsądny sposób to zrobić w C #, ale w językach najbardziej funkcjonalne mają lepszy sposób reprezentowania brakujących wartości. Przeczytaj więcej tutajT TryGetValue(params, ref bool success)
dla dowolnego typu T lubT TryGetValue(params)
, z zerowym wskazaniem niepowodzenia, dla typu T ograniczonego przez klasę, aleTryGetXX
wzór, który zwraca,bool
jest niezgodny z kowariancją.Optional<Product>
aby wskazać, że produkt może nie zostać zwrócony z metodyCytat Roberta C. Martina jest wyjęty z kontekstu. Oto cytat z nieco większym kontekstem:
(Skopiowano z tego miejsca , ale oryginalny cytat pochodzi z Clean Code: A Handbook of Agile Software Craftsmanship )
To, jak ten cytat sprowadza się do „Komentarze są zawsze porażkami”, jest dobrym przykładem tego, jak niektórzy ludzie wyciągną rozsądny cytat z kontekstu i zamieni go w głupi dogmat.
Dokumentacja API (jak javadoc) ma udokumentować API, aby użytkownik mógł z niego korzystać bez konieczności odczytywania kodu źródłowego . W takim przypadku dokumentacja powinna wyjaśniać, co robi metoda. Teraz możesz argumentować, że „Pobiera produkt według jego identyfikatora” jest zbędny, ponieważ jest już wskazany przez nazwę metody, ale informacje, które
null
można zwrócić, są zdecydowanie ważne w dokumentacji, ponieważ nie jest to w żaden sposób oczywiste.Jeśli chcesz uniknąć konieczności komentarza, musisz usunąć podstawowy problem (którym jest użycie
null
jako prawidłowej wartości zwracanej), czyniąc interfejs API bardziej wyraźnym. Na przykład możesz zwrócić jakiś rodzajOption<Product>
, więc sam podpis typu wyraźnie informuje, co zostanie zwrócone, jeśli produkt nie zostanie znaleziony.Ale w każdym razie nie jest realistyczne udokumentowanie API w pełni tylko poprzez nazwy metod i podpisy typów. Użyj komentarzy do wszelkich dodatkowych nieoczywistych informacji, które użytkownik powinien wiedzieć. Załóżmy, że dokumentacja API z
DateTime.AddMonths()
BCL:Nie ma możliwości wyrażenia tego za pomocą nazwy i podpisu metody! Oczywiście twoja dokumentacja klasowa może nie wymagać takiego poziomu szczegółowości, to tylko przykład.
Komentarze wbudowane też nie są złe.
Złe komentarze są złe. Na przykład komentarze, które wyjaśniają tylko to, co można w prosty sposób zobaczyć z kodu, klasycznym przykładem jest:
Komentarze, które wyjaśniają coś, co można wyjaśnić przez zmianę nazwy zmiennej lub metody lub w inny sposób restrukturyzację kodu, to zapach kodu:
Takie są uwagi, które Martin szykuje. Komentarz jest symptomem braku napisania czystego kodu - w tym przypadku należy użyć zrozumiałych nazw dla zmiennych i metod. Sam komentarz oczywiście nie jest problemem, problem polega na tym, że potrzebujemy komentarza, aby zrozumieć kod.
Ale komentarze powinny być użyte do wyjaśnienia wszystkiego, co nie jest oczywiste z kodu, np. Dlaczego kod jest napisany w pewien nieoczywisty sposób:
Komentarze, które wyjaśniają, co robi nadmiernie skomplikowany fragment kodu, są również zapachem, ale poprawka nie polega na zakazaniu komentarzy, poprawka polega na naprawieniu kodu! Mówiąc inaczej, zdarza się zawiły kod (miejmy nadzieję, że tylko tymczasowo do momentu refaktoryzacji), ale żaden zwykły programista nie pisze za pierwszym razem idealnego czystego kodu. Kiedy zdarza się zawiły kod, znacznie lepiej jest napisać komentarz wyjaśniający, co robi, niż nie pisać komentarza. Ten komentarz ułatwi również później refaktoryzację.
Czasami kod jest nieuchronnie złożony. Może to być skomplikowany algorytm lub kod zmniejszający przejrzystość ze względu na wydajność. Ponownie komentarze są konieczne.
źródło
x++
może być dobry, jeśli jest to coś w rodzaju „zwiększaj x jeden, zawijaj, jeśli jest to UINT32_MAX”; każdy, kto zna specyfikację języka, wiedziałby, że zwiększenieuint32_t
owijania będzie zawijać, ale bez komentarza można nie wiedzieć, czy takie owijanie było oczekiwaną częścią implementowanego algorytmu.Istnieje różnica między komentowaniem kodu a dokumentowaniem kodu .
Komentarze są potrzebne, aby zachować kod później, czyli zmienić sam kod.
Komentarze mogą rzeczywiście być postrzegane jako problematyczne. Skrajnym punktem byłoby powiedzenie, że zawsze wskazują na problem, albo w kodzie (kod zbyt trudny do zrozumienia), albo w języku (język nie może być wystarczająco wyrazisty; na przykład
null
można wyrazić fakt, że metoda nigdy nie zwraca wartości poprzez umowy Code w języku C #, ale nie ma sposobu, aby wyrazić to za pomocą kodu, powiedzmy, PHP).Potrzebna jest dokumentacja , aby móc korzystać z opracowanych obiektów (klas, interfejsów). Grupy docelowej jest inna: to nie osoby, które będą utrzymywać swój kod i zmienić go, że mówimy o tutaj, ale osoby, które ledwo trzeba używać go.
Usuwanie dokumentacji, ponieważ kod jest wystarczająco przejrzysty, jest szalone, ponieważ dokumentacja ma na celu umożliwienie korzystania z klas i interfejsów bez konieczności odczytywania tysięcy wierszy kodu .
źródło
Wygląda na to, że twój kolega czyta książki, przyjmuje to, co mówią, i stosuje to, czego się nauczył, bez zastanowienia i bez uwzględnienia kontekstu.
Twój komentarz na temat tego, co robi funkcja, powinien być taki, abyś mógł wyrzucić kod implementacyjny, czytam komentarz funkcji i mogę napisać zamiennik implementacji, bez żadnych problemów.
Jeśli komentarz nie mówi mi, czy zostanie zgłoszony wyjątek, czy też zostanie zwrócone zero, nie mogę tego zrobić. Ponadto, jeśli komentarz nie mówi , czy wyjątek jest zgłaszany, czy też zwracany jest zero, wówczas przy każdym wywołaniu funkcji należy upewnić się, że kod działa poprawnie, niezależnie od tego, czy wyjątek zostanie zgłoszony, czy zwrócony zero.
Więc twój kolega jest całkowicie w błędzie. Idź dalej i przeczytaj wszystkie książki, ale pomyśl sam.
PS. Widziałem twoją linię „czasami jest to część interfejsu, więc nawet nie wiesz, jaki kod jest uruchomiony.” Nawet z funkcjami wirtualnymi nie wiesz, który kod działa. Co gorsza, jeśli napisałeś abstrakcyjną klasę, nie ma nawet żadnego kodu! Jeśli więc masz klasę abstrakcyjną z funkcją abstrakcyjną, komentarze dodane do funkcji abstrakcyjnej są jedyną rzeczą, którą musi poprowadzić implementator konkretnej klasy. Te komentarze mogą być również jedyną rzeczą, która może poprowadzić użytkownika klasy, na przykład jeśli wszystko, co masz, to klasa abstrakcyjna i fabryka zwracająca konkretną implementację, ale nigdy nie widzisz żadnego kodu źródłowego implementacji. (I oczywiście nie powinienem patrzeć na kod źródłowy jednej implementacji).
źródło
Istnieją dwa rodzaje komentarzy do rozważenia - widoczne dla osób posiadających kod i używane do generowania dokumentacji.
Rodzaj komentarza, do którego odnosi się wujek Bob, jest widoczny tylko dla osób mających kod. To, za czym on opowiada, to forma SUSZENIA . Dla osoby, która patrzy na kod źródłowy, kod źródłowy powinien być potrzebną dokumentacją. Nawet w przypadku, gdy ludzie mają dostęp do kodu źródłowego, komentarze nie zawsze są złe. Czasami algorytmy są skomplikowane lub musisz uchwycić, dlaczego podejmujesz nieoczywiste podejście, aby inni nie złamali kodu, jeśli spróbują naprawić błąd lub dodać nową funkcję.
Komentarze, które opisujesz, są dokumentacją API. Są to rzeczy widoczne dla osób korzystających z Twojej implementacji, ale mogą nie mieć dostępu do Twojego kodu źródłowego. Nawet jeśli mają dostęp do kodu źródłowego, mogą pracować nad innymi modułami i nie patrzeć na kod źródłowy. Dla tych osób przydatne byłoby udostępnienie tej dokumentacji w IDE podczas pisania kodu.
źródło
Wartość komentarza mierzona jest wartością przekazywanych informacji minus wysiłek potrzebny do ich przeczytania i / lub zignorowania. Więc jeśli przeanalizujemy komentarz
pod względem wartości i kosztów widzimy trzy rzeczy:
Retrieves a product by its id
powtarza to, co mówi nazwa funkcji, więc jest to koszt bez wartości. Należy go usunąć.returns null if no product was found
jest bardzo cenną informacją. Prawdopodobnie skraca to czas, w którym inni koderzy będą musieli spojrzeć na implementację funkcji. Jestem pewien, że oszczędza więcej czytania niż sam koszt czytania, który sam sobie przedstawia. Powinien zostać.Linie
nie wolno przenosić żadnych informacji. Są czystym kosztem dla czytelnika komentarza. Mogą być uzasadnione, jeśli twój generator dokumentacji ich potrzebuje, ale w takim przypadku prawdopodobnie powinieneś pomyśleć o innym generatorze dokumentacji.
To jest powód, dla którego użycie generatorów dokumentacji jest dyskusyjnym pomysłem: na ogół wymagają one wielu dodatkowych komentarzy, które nie zawierają żadnych informacji lub powtarzają oczywiste rzeczy, tylko ze względu na dopracowany dokument.
Obserwacja, której nie znalazłem w żadnej z pozostałych odpowiedzi:
Nawet komentarze, które nie są konieczne do zrozumienia / używania kodu, mogą być bardzo cenne. Oto jeden z takich przykładów:
Prawdopodobnie dużo tekstu, całkowicie niepotrzebne do zrozumienia / użycia kodu. Wyjaśnia jednak powód, dla którego kod wygląda tak, jak wygląda. Powstrzyma ludzi od krótkiego spojrzenia na kod, zastanowi się, dlaczego nie jest to zrobione w oczywisty sposób, i zacznie refaktoryzować kod, dopóki nie zorientuje się, dlaczego kod został napisany w ten sposób. I nawet jeśli czytnik jest wystarczająco inteligentny, aby nie przejść bezpośrednio do refaktoryzacji, wciąż muszą dowiedzieć się, dlaczego kod wygląda tak, jak wygląda, zanim zdadzą sobie sprawę, że najlepiej pozostać taki, jaki jest. Ten komentarz mógłby dosłownie zaoszczędzić godziny pracy. Zatem wartość jest wyższa niż koszt.
Podobnie, komentarze mogą komunikować zamiary kodu, a nie tylko jego działanie. I mogą namalować duży obraz, który zwykle gubi się w najdrobniejszych szczegółach samego kodu. Jako taki, masz rację, opowiadając się za komentarzami klasy. Najbardziej cenię komentarze klasowe, jeśli wyjaśniają one intencje klasy, jej interakcje z innymi klasami, sposób, w jaki należy ją wykorzystywać itp. Niestety, nie jestem wielkim autorem takich komentarzy ...
źródło
GetById
rodzi pytanie, co to jest id, a także, co dostać skąd. Komentarz do dokumentacji powinien umożliwiać środowisku programistycznemu wyświetlanie odpowiedzi na te pytania. O ile nie zostanie to wyjaśnione w innym miejscu w komentarzach do modułu, byłoby to również jedno miejsce, w którym można by powiedzieć, dlaczego i tak dostaniemy identyfikator.Kod niekomentowany to zły kod. Jest to powszechny (jeśli nie uniwersalny) mit, że kod można odczytać w taki sam sposób jak, powiedzmy, angielski. Musi być interpretowany i dla każdego, oprócz najbardziej trywialnego kodu, który wymaga czasu i wysiłku. Ponadto każdy ma inny poziom umiejętności czytania i pisania w danym języku. Różnice między pisarzem a stylami kodowania i możliwościami czytelnika stanowią silne bariery dla dokładnej interpretacji. Jest to także mit, że intencję autora można wywnioskować z implementacji kodu. Z mojego doświadczenia wynika, że rzadko dodawanie dodatkowych komentarzy jest błędne.
Robert Martin i in. traktuj to jako „nieprzyjemny zapach”, ponieważ być może kod został zmieniony, a komentarze nie. Mówię, że to dobra rzecz (dokładnie w ten sam sposób, w jaki do gazu domowego dodaje się „nieprzyjemny zapach”, aby ostrzec użytkownika o wycieku). Czytanie komentarzy stanowi przydatny punkt wyjścia do interpretacji rzeczywistego kodu. Jeśli się zgadzają, zwiększysz zaufanie do kodu. Jeśli różnią się, oznacza to, że wykryłeś ostrzegawczy zapach i musisz zbadać sprawę dalej. Lekarstwem na „nieprzyjemny zapach” nie jest jego usunięcie, lecz uszczelnienie wycieku.
źródło
i++; // Adjust for leap years.
W niektórych językach (na przykład F #) cały komentarz / dokumentacja może być faktycznie wyrażona w podpisie metody. Wynika to z faktu, że w języku F # null nie jest ogólnie dozwoloną wartością, chyba że jest wyraźnie dozwolony.
To, co jest powszechne w F # (a także w kilku innych językach funkcjonalnych) to to, że zamiast null używasz typu opcji,
Option<T>
który może byćNone
alboSome(T)
. Język zrozumiałby to i zmusiłby cię (lub ostrzec, jeśli tego nie zrobisz) w obu przypadkach, gdy próbujesz go użyć.Na przykład w języku F # możesz mieć podpis, który wygląda tak
I wtedy byłaby to funkcja, która przyjmuje jeden parametr (int), a następnie zwraca produkt lub wartość Brak.
A potem możesz użyć tego w ten sposób
Otrzymasz ostrzeżenia kompilatora, jeśli nie pasujesz do obu możliwych przypadków. Nie ma więc możliwości uzyskania wyjątków zerowej referencji (chyba że oczywiście zignorujesz ostrzeżenia kompilatora) i wiesz wszystko, czego potrzebujesz, patrząc na sygnaturę funkcji.
Oczywiście wymaga to, aby Twój język został zaprojektowany w ten sposób - czego nie jest w wielu popularnych językach, takich jak C #, Java, C ++ itp. W obecnej sytuacji może Ci to nie być pomocne. Ale (mam nadzieję) miło wiedzieć, że istnieją języki, które pozwalają wyrażać tego rodzaju informacje w sposób statyczny, bez uciekania się do komentarzy itp. :)
źródło
Jest tu kilka doskonałych odpowiedzi i nie chcę powtarzać tego, co mówią. Ale pozwól mi dodać kilka komentarzy. (Nie jest przeznaczona gra słów).
Istnieje wiele stwierdzeń, które inteligentni składają - na temat rozwoju oprogramowania i wielu innych tematów - które są bardzo dobrymi radami, gdy są rozumiane w kontekście, ale które są głupie, niż wyjmowanie z kontekstu lub zastosowanie w niewłaściwych sytuacjach lub śmieszne skrajności.
Pomysł, że kod powinien być samodokumentujący, jest jednym z takich doskonałych pomysłów. Ale w prawdziwym życiu istnieją ograniczenia dotyczące praktyczności tego pomysłu.
Jednym z haczyków jest to, że język może nie zapewniać funkcji dokumentujących to, co należy udokumentować w sposób jasny i zwięzły. Wraz z poprawą języków komputerowych staje się to coraz mniejszym problemem. Ale nie sądzę, że całkowicie zniknęło. W czasach, gdy pisałem w asemblerze, sensowne było umieszczenie komentarza w stylu „cena całkowita = cena pozycji niepodlegających opodatkowaniu plus cena pozycji podlegających opodatkowaniu plus cena pozycji podlegających opodatkowaniu * stawka podatkowa”. Dokładnie to, co było w rejestrze w danym momencie, niekoniecznie było oczywiste. Wykonanie prostej operacji wymagało wielu kroków. Itd. Ale jeśli piszesz w nowoczesnym języku, taki komentarz byłby tylko powtórzeniem jednej linii kodu.
Zawsze denerwuję się, gdy widzę komentarze typu „x = x + 7; // dodaj 7 do x”. Na przykład dziękuję, gdybym zapomniał, co oznacza znak plus, co mogłoby być bardzo pomocne. To, co naprawdę mnie dezorientuje, to wiedza o tym, co to jest „x” lub dlaczego w tym konkretnym momencie trzeba było dodać do niego 7. Ten kod można samokontraktować, nadając bardziej znaczącą nazwę „x” i używając stałej symbolicznej zamiast 7. Na przykład, jeśli napisałeś „total_price = total_price + MEMBERSHIP_FEE;”, to prawdopodobnie komentarz wcale nie jest potrzebny .
Wspaniale jest powiedzieć, że nazwa funkcji powinna dokładnie powiedzieć, co robi funkcja, aby wszelkie dodatkowe komentarze były zbędne. Mam dobre wspomnienia z czasu, gdy napisałem funkcję, która sprawdzała, czy numer pozycji znajduje się w naszej tabeli pozycji bazy danych, zwracając wartość prawda czy fałsz, i którą nazwałem „ValidateItemNumber”. Wydawało się, że to porządne imię. Potem przyszedł ktoś inny i zmodyfikował tę funkcję, aby również utworzyć zamówienie na przedmiot i zaktualizować bazę danych, ale nigdy nie zmienił nazwy. Teraz nazwa była bardzo myląca. Brzmiało to tak, jakby zrobił jedną małą rzecz, podczas gdy naprawdę zrobił znacznie więcej. Później ktoś nawet wziął udział w sprawdzaniu poprawności numeru przedmiotu i zrobił to gdzie indziej, ale nadal nie zmienił nazwy. Więc nawet jeśli funkcja nie miała teraz nic wspólnego z sprawdzaniem poprawności numerów pozycji,
Ale w praktyce często nie jest możliwe, aby nazwa funkcji całkowicie opisała, co robi funkcja, gdyby nazwa funkcji nie była tak długa, jak kod tworzący tę funkcję. Czy nazwa powie nam dokładnie, jakie weryfikacje są wykonywane dla wszystkich parametrów? Co dzieje się na warunkach wyjątkowych? Wyjaśnić każdą możliwą niejednoznaczność? W pewnym momencie nazwa będzie tak długa, że stanie się po prostu myląca. Zaakceptowałbym String BuildFullName (imię String, nazwisko String) jako przyzwoitą sygnaturę funkcji. Chociaż nie wyjaśnia to, czy nazwa jest wyrażona jako „pierwsza ostatnia”, czy „ostatnia, pierwsza”, czy jakaś inna odmiana, to co robi, jeśli jedna lub obie części nazwy są puste, jeśli nakłada ograniczenia na łączną długość i co robi, jeśli zostanie przekroczone,
źródło