Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00297 005040 14830218 na godz. na dobę w sumie
Czysty kod. Podręcznik dobrego programisty - książka
Czysty kod. Podręcznik dobrego programisty - książka
Autor: Liczba stron: 424
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-0234-1 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> agile - programowanie
Porównaj ceny (książka, ebook, audiobook).

Poznaj najlepsze metody tworzenia doskonałego kodu

O tym, ile problemów sprawia niedbale napisany kod, wie każdy programista. Nie wszyscy jednak wiedzą, jak napisać ten świetny, „czysty” kod i czym właściwie powinien się on charakteryzować. Co więcej – jak odróżnić dobry kod od złego? Odpowiedź na te pytania oraz sposoby tworzenia czystego, czytelnego kodu znajdziesz właśnie w tej książce. Podręcznik jest obowiązkową pozycją dla każdego, kto chce poznać techniki rzetelnego i efektywnego programowania.

W książce „Czysty kod. Podręcznik dobrego programisty” szczegółowo omówione zostały zasady, wzorce i najlepsze praktyki pisania czystego kodu. Podręcznik zawiera także kilka analiz przypadków o coraz większej złożoności, z których każda jest doskonałym ćwiczeniem porządkowania zanieczyszczonego bądź nieudanego kodu. Z tego podręcznika dowiesz się m.in., jak tworzyć dobre nazwy, obiekty i funkcje, a także jak tworzyć testy jednostkowe i korzystać z programowania sterowanego testami. Nauczysz się przekształcać kod zawierający problemy w taki, który jest solidny i efektywny.

Niech stworzony przez Ciebie kod imponuje czystością!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: Clean Code: A Handbook of Agile Software Craftsmanship Tłumaczenie: Paweł Gonera Projekt okładki: Mateusz Obarek, Maciej Pokoński ISBN: 978-83-283-0234-1 Authorized translation from the English language edition, entitled: Clean Code: A Handbook of Agile Software Craftsmanship, First Edition, ISBN 0132350882, by Robert C. Martin, published by Pearson Education, Inc., publishing as Prentice Hall. Copyright © 2009 by Pearson Education, Inc. Polish language edition published by Helion S.A. Copyright © 2014. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education Inc. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Materiały graficzne na okładce zostały wykorzystane za zgodą iStockPhoto Inc. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/czykov Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/czykov.zip Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Słowo wstępne Wstęp 1. Czysty kod Niech stanie się kod... W poszukiwaniu doskonałego kodu... Całkowity koszt bałaganu Rozpoczęcie wielkiej zmiany projektu Postawa Największa zagadka Sztuka czystego kodu? Co to jest czysty kod? Szkoły myślenia Jesteśmy autorami Zasada skautów Poprzednik i zasady Zakończenie Bibliografia 2. Znaczące nazwy Wstęp Używaj nazw przedstawiających intencje Unikanie dezinformacji Tworzenie wyraźnych różnic Tworzenie nazw, które można wymówić Korzystanie z nazw łatwych do wyszukania Unikanie kodowania Notacja węgierska Przedrostki składników Interfejsy i implementacje Unikanie odwzorowania mentalnego Nazwy klas Nazwy metod Nie bądź dowcipny Wybieraj jedno słowo na pojęcie Nie twórz kalamburów! Korzystanie z nazw dziedziny rozwiązania Korzystanie z nazw dziedziny problemu Dodanie znaczącego kontekstu Nie należy dodawać nadmiarowego kontekstu Słowo końcowe S P I S T R E Ś C I 13 19 23 24 24 25 26 27 28 28 28 34 35 36 36 36 37 39 39 40 41 42 43 44 45 45 46 46 47 47 47 48 48 49 49 49 50 51 52 5 Kup książkęPoleć książkę 3. Funkcje Małe funkcje! Bloki i wcięcia Wykonuj jedną czynność Sekcje wewnątrz funkcji Jeden poziom abstrakcji w funkcji Czytanie kodu od góry do dołu — zasada zstępująca Instrukcje switch Korzystanie z nazw opisowych Argumenty funkcji Często stosowane funkcje jednoargumentowe Argumenty znacznikowe Funkcje dwuargumentowe Funkcje trzyargumentowe Argumenty obiektowe Listy argumentów Czasowniki i słowa kluczowe Unikanie efektów ubocznych Argumenty wyjściowe Rozdzielanie poleceń i zapytań Stosowanie wyjątków zamiast zwracania kodów błędów Wyodrębnienie bloków try-catch Obsługa błędów jest jedną operacją Przyciąganie zależności w Error.java Nie powtarzaj się Programowanie strukturalne Jak pisać takie funkcje? Zakończenie SetupTeardownIncluder Bibliografia 4. Komentarze Komentarze nie są szminką dla złego kodu Czytelny kod nie wymaga komentarzy Dobre komentarze Komentarze prawne Komentarze informacyjne Wyjaśnianie zamierzeń Wyjaśnianie Ostrzeżenia o konsekwencjach Komentarze TODO Wzmocnienie Komentarze Javadoc w publicznym API Złe komentarze Bełkot Powtarzające się komentarze Mylące komentarze Komentarze wymagane Komentarze dziennika 6 S P I S T R E Ś C I 53 56 57 57 58 58 58 59 61 62 62 63 63 64 64 65 65 65 66 67 67 68 69 69 69 70 70 71 71 73 75 77 77 77 77 78 78 79 80 80 81 81 81 81 82 84 85 85 Kup książkęPoleć książkę Komentarze wprowadzające szum informacyjny Przerażający szum Nie używaj komentarzy, jeżeli można użyć funkcji lub zmiennej Znaczniki pozycji Komentarze w klamrach zamykających Atrybuty i dopiski Zakomentowany kod Komentarze HTML Informacje nielokalne Nadmiar informacji Nieoczywiste połączenia Nagłówki funkcji Komentarze Javadoc w niepublicznym kodzie Przykład Bibliografia 5. Formatowanie Przeznaczenie formatowania Formatowanie pionowe Metafora gazety Pionowe odstępy pomiędzy segmentami kodu Gęstość pionowa Odległość pionowa Uporządkowanie pionowe Formatowanie poziome Poziome odstępy i gęstość Rozmieszczenie poziome Wcięcia Puste zakresy Zasady zespołowe Zasady formatowania wujka Boba 6. Obiekty i struktury danych Abstrakcja danych Antysymetria danych i obiektów Prawo Demeter Wraki pociągów Hybrydy Ukrywanie struktury Obiekty transferu danych Active Record Zakończenie Bibliografia 7. Obsługa błędów Użycie wyjątków zamiast kodów powrotu Rozpoczynanie od pisania instrukcji try-catch-finally Użycie niekontrolowanych wyjątków Dostarczanie kontekstu za pomocą wyjątków Definiowanie klas wyjątków w zależności od potrzeb wywołującego 86 87 88 88 88 89 89 90 91 91 91 92 92 92 95 97 98 98 99 99 101 101 105 106 106 107 109 110 110 111 113 113 115 117 118 118 119 119 120 121 121 123 124 125 126 127 127 S P I S T R E Ś C I 7 Kup książkęPoleć książkę Definiowanie normalnego przepływu Nie zwracamy null Nie przekazujemy null Zakończenie Bibliografia 8. Granice Zastosowanie kodu innych firm Przeglądanie i zapoznawanie się z granicami Korzystanie z pakietu log4j Zalety testów uczących Korzystanie z nieistniejącego kodu Czyste granice Bibliografia 9. Testy jednostkowe Trzy prawa TDD Zachowanie czystości testów Testy zwiększają możliwości Czyste testy Języki testowania specyficzne dla domeny Podwójny standard Jedna asercja na test Jedna koncepcja na test F.I.R.S.T. Zakończenie Bibliografia 10. Klasy Organizacja klas Hermetyzacja Klasy powinny być małe! Zasada pojedynczej odpowiedzialności Spójność Utrzymywanie spójności powoduje powstanie wielu małych klas Organizowanie zmian Izolowanie modułów kodu przed zmianami Bibliografia 11. Systemy Jak budowałbyś miasto? Oddzielenie konstruowania systemu od jego używania Wydzielenie modułu main Fabryki Wstrzykiwanie zależności Skalowanie w górę Separowanie (rozcięcie) problemów Pośredniki Java 8 S P I S T R E Ś C I 129 130 131 132 132 133 134 136 136 138 138 139 140 141 142 143 144 144 147 147 149 150 151 152 152 153 153 154 154 156 158 158 164 166 167 169 170 170 171 172 172 173 176 177 Kup książkęPoleć książkę Czyste biblioteki Java AOP Aspekty w AspectJ Testowanie architektury systemu Optymalizacja podejmowania decyzji Korzystaj ze standardów, gdy wnoszą realną wartość Systemy wymagają języków dziedzinowych Zakończenie Bibliografia 12. Powstawanie projektu Uzyskiwanie czystości projektu przez jego rozwijanie Zasada numer 1 prostego projektu — system przechodzi wszystkie testy Zasady numer 2 – 4 prostego projektu — przebudowa Brak powtórzeń Wyrazistość kodu Minimalne klasy i metody Zakończenie Bibliografia 13. Współbieżność W jakim celu stosować współbieżność? Mity i nieporozumienia Wyzwania Zasady obrony współbieżności Zasada pojedynczej odpowiedzialności Wniosek — ograniczenie zakresu danych Wniosek — korzystanie z kopii danych Wniosek — wątki powinny być na tyle niezależne, na ile to tylko możliwe Poznaj używaną bibliotekę Kolekcje bezpieczne dla wątków Poznaj modele wykonania Producent-konsument Czytelnik-pisarz Ucztujący filozofowie Uwaga na zależności pomiędzy synchronizowanymi metodami Tworzenie małych sekcji synchronizowanych Pisanie prawidłowego kodu wyłączającego jest trudne Testowanie kodu wątków Traktujemy przypadkowe awarie jako potencjalne problemy z wielowątkowością Na początku uruchamiamy kod niekorzystający z wątków Nasz kod wątków powinien dać się włączać Nasz kod wątków powinien dać się dostrajać Uruchamiamy więcej wątków, niż mamy do dyspozycji procesorów Uruchamiamy testy na różnych platformach Uzbrajamy nasz kod w elementy próbujące wywołać awarie i wymuszające awarie Instrumentacja ręczna Instrumentacja automatyczna Zakończenie Bibliografia 178 181 182 183 183 184 184 185 187 187 188 188 189 191 192 192 192 193 194 195 196 196 197 197 197 198 198 198 199 199 200 200 201 201 202 202 203 203 203 204 204 204 205 205 206 207 208 S P I S T R E Ś C I 9 Kup książkęPoleć książkę 14. Udane oczyszczanie kodu Implementacja klasy Args Args — zgrubny szkic Argumenty typu String Zakończenie 15. Struktura biblioteki JUnit Biblioteka JUnit Zakończenie 16. Przebudowa klasy SerialDate Na początek uruchamiamy Teraz poprawiamy Zakończenie Bibliografia 17. Zapachy kodu i heurystyki Komentarze C1. Niewłaściwe informacje C2. Przestarzałe komentarze C3. Nadmiarowe komentarze C4. Źle napisane komentarze C5. Zakomentowany kod Środowisko E1. Budowanie wymaga więcej niż jednego kroku E2. Testy wymagają więcej niż jednego kroku Funkcje F1. Nadmiar argumentów F2. Argumenty wyjściowe F3. Argumenty znacznikowe F4. Martwe funkcje Ogólne G1. Wiele języków w jednym pliku źródłowym G2. Oczywiste działanie jest nieimplementowane G3. Niewłaściwe działanie w warunkach granicznych G4. Zdjęte zabezpieczenia G5. Powtórzenia G6. Kod na nieodpowiednim poziomie abstrakcji G7. Klasy bazowe zależne od swoich klas pochodnych G8. Za dużo informacji G9. Martwy kod G10. Separacja pionowa G11. Niespójność G12. Zaciemnianie G13. Sztuczne sprzężenia G14. Zazdrość o funkcje G15. Argumenty wybierające G16. Zaciemnianie intencji G17. Źle rozmieszczona odpowiedzialność 1 0 S P I S T R E Ś C I 209 210 216 228 261 263 264 276 277 278 280 293 294 295 296 296 296 296 297 297 297 297 297 298 298 298 298 298 298 298 299 299 299 300 300 301 302 302 303 303 303 303 304 305 305 306 Kup książkęPoleć książkę G18. Niewłaściwe metody statyczne G19. Użycie opisowych zmiennych G20. Nazwy funkcji powinny informować o tym, co realizują G21. Zrozumienie algorytmu G22. Zamiana zależności logicznych na fizyczne G23. Zastosowanie polimorfizmu zamiast instrukcji if-else lub switch-case G24. Wykorzystanie standardowych konwencji G25. Zamiana magicznych liczb na stałe nazwane G26. Precyzja G27. Struktura przed konwencją G28. Hermetyzacja warunków G29. Unikanie warunków negatywnych G30. Funkcje powinny wykonywać jedną operację G31. Ukryte sprzężenia czasowe G32. Unikanie dowolnych działań G33. Hermetyzacja warunków granicznych G34. Funkcje powinny zagłębiać się na jeden poziom abstrakcji G35. Przechowywanie danych konfigurowalnych na wysokim poziomie G36. Unikanie nawigacji przechodnich Java J1. Unikanie długich list importu przez użycie znaków wieloznacznych J2. Nie dziedziczymy stałych J3. Stałe kontra typy wyliczeniowe Nazwy N1. Wybór opisowych nazw N2. Wybór nazw na odpowiednich poziomach abstrakcji N3. Korzystanie ze standardowej nomenklatury tam, gdzie jest to możliwe N4. Jednoznaczne nazwy N5. Użycie długich nazw dla długich zakresów N6. Unikanie kodowania N7. Nazwy powinny opisywać efekty uboczne Testy T1. Niewystarczające testy T2. Użycie narzędzi kontroli pokrycia T3. Nie pomijaj prostych testów T4. Ignorowany test jest wskazaniem niejednoznaczności T5. Warunki graniczne T6. Dokładne testowanie pobliskich błędów T7. Wzorce błędów wiele ujawniają T8. Wzorce pokrycia testami wiele ujawniają T9. Testy powinny być szybkie Zakończenie Bibliografia A Współbieżność II Przykład klient-serwer Serwer Dodajemy wątki Uwagi na temat serwera Zakończenie 306 307 307 308 308 309 310 310 311 312 312 312 312 313 314 314 315 316 317 317 317 318 319 320 320 321 322 322 323 323 323 324 324 324 324 324 324 324 324 325 325 325 325 327 327 327 329 329 331 S P I S T R E Ś C I 1 1 Kup książkęPoleć książkę 331 332 333 336 336 336 337 338 339 340 340 342 343 344 344 345 346 346 346 346 347 347 348 348 349 351 352 352 352 355 357 411 413 415 Możliwe ścieżki wykonania Liczba ścieżek Kopiemy głębiej Zakończenie Poznaj używaną bibliotekę Biblioteka Executor Rozwiązania nieblokujące Bezpieczne klasy nieobsługujące wątków Zależności między metodami mogą uszkodzić kod współbieżny Tolerowanie awarii Blokowanie na kliencie Blokowanie na serwerze Zwiększanie przepustowości Obliczenie przepustowości jednowątkowej Obliczenie przepustowości wielowątkowej Zakleszczenie Wzajemne wykluczanie Blokowanie i oczekiwanie Brak wywłaszczania Cykliczne oczekiwanie Zapobieganie wzajemnemu wykluczaniu Zapobieganie blokowaniu i oczekiwaniu Umożliwienie wywłaszczania Zapobieganie oczekiwaniu cyklicznemu Testowanie kodu wielowątkowego Narzędzia wspierające testowanie kodu korzystającego z wątków Zakończenie Samouczek. Pełny kod przykładów Klient-serwer bez wątków Klient-serwer z użyciem wątków B org.jfree.date.SerialDate C Odwołania do heurystyk Epilog Skorowidz 1 2 S P I S T R E Ś C I Kup książkęPoleć książkę R O Z D Z I A Ł 4 . Komentarze Nie komentuj złego kodu — popraw go. Brian W. Kernighan i P.J. Plaugher1 N IEWIELE JEST RZECZY TAK POMOCNYCH, jak dobrze umieszczony komentarz. Jednocześnie nic tak nie zaciemnia modułu, jak kilka zbyt dogmatycznych komentarzy. Nic nie jest tak szkodliwe, jak stary komentarz szerzący kłamstwa i dezinformację. Komentarze nie są jak „Lista Schindlera”. Nie są one „czystym dobrem”. W rzeczywistości ko- mentarze są w najlepszym przypadku koniecznym złem. Jeżeli nasz język programowania jest wy- starczająco ekspresyjny lub mamy wystarczający talent, by wykorzystywać ten język, aby wyrażać nasze zamierzenia, nie będziemy potrzebować zbyt wielu komentarzy. 1 [KP78], s. 144. 7 5 Kup książkęPoleć książkę Prawidłowe zastosowanie komentarzy jest kompensowaniem naszych błędów przy tworzeniu kodu. Proszę zwrócić uwagę, że użyłem słowa błąd. Dokładnie to miałem na myśli. Obecność komentarzy zawsze sygnalizuje nieporadność programisty. Musimy korzystać z nich, ponieważ nie zawsze wiemy, jak wyrazić nasze intencje bez ich użycia, ale ich obecność nie jest powodem do świętowania. Gdy uznamy, że konieczne jest napisanie komentarza, należy pomyśleć, czy nie istnieje sposób na wyrażenie tego samego w kodzie. Za każdym razem, gdy wyrazimy to samo za pomocą kodu, po- winniśmy odczuwać satysfakcję. Za każdym razem, gdy piszemy komentarz, powinniśmy poczuć smak porażki. Dlaczego jestem tak przeciwny komentarzom? Ponieważ one kłamią. Nie zawsze, nie rozmyślnie, ale nader często. Im starsze są komentarze, tym większe prawdopodobieństwo, że są po prostu błędne. Powód jest prosty. Programiści nie są w stanie utrzymywać ich aktualności. Kod zmienia się i ewoluuje. Jego fragmenty są przenoszone w różne miejsca. Fragmenty te są roz- dzielane, odtwarzane i ponownie łączone. Niestety, komentarze nie zawsze za nimi podążają — nie zawsze mogą być przenoszone. Zbyt często komentarze są odłączane od kodu, który opisują, i stają się osieroconymi notatkami o stale zmniejszającej się dokładności. Dla przykładu warto spojrzeć, co się stało z komentarzem i wierszem, którego dotyczył: MockRequest request; private final String HTTP_DATE_REGEXP = [SMTWF][a-z]{2}\\,\\s[0-9]{2}\\s[JFMASOND][a-z]{2}\\s + [0-9]{4}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\sGMT ; private Response response; private FitNesseContext context; private FileResponder responder; private Locale saveLocale; // Przykáad: Tue, 02 Apr 2003 22:18:49 GMT Pozostałe zmienne instancyjne zostały prawdopodobnie później dodane pomiędzy stałą HTTP_ ´DATE_REGEXP a objaśniającym ją komentarzem. Można oczywiście stwierdzić, że programiści powinni być na tyle zdyscyplinowani, aby utrzymy- wać komentarze w należytym stanie. Zgadzam się, powinni. Wolałbym jednak, aby poświęcona na to energia została spożytkowana na zapewnienie takiej precyzji i wyrazistości kodu, by komentarze okazały się zbędne. Niedokładne komentarze są znacznie gorsze niż ich brak. Kłamią i wprowadzają w błąd. Powodują powstanie oczekiwań, które nigdy nie są spełnione. Definiują stare zasady, które nie są już po- trzebne lub nie powinny być stosowane. Prawda znajduje się w jednym miejscu: w kodzie. Jedynie kod może niezawodnie przedstawić to, co realizuje. Jest jedynym źródłem naprawdę dokładnych informacji. Dlatego choć komentarze są czasami niezbędne, poświęcimy sporą ilość energii na zminimalizowanie ich liczby. 7 6 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę Komentarze nie sÈ szminkÈ dla zïego kodu Jednym z często spotykanych powodów pisania komentarzy jest nieudany kod. Napisaliśmy moduł i zauważamy, że jest źle zorganizowany. Wiemy, że jest chaotyczny. Mówimy wówczas: „Hm, będzie lepiej, jak go skomentuję”. Nie! Lepiej go poprawić! Precyzyjny i czytelny kod z małą liczbą komentarzy jest o wiele lepszy niż zabałaganiony i złożony kod z mnóstwem komentarzy. Zamiast spędzać czas na pisaniu kodu wyjaśniającego bałagan, jaki zrobiliśmy, warto poświęcić czas na posprzątanie tego bałaganu. Czytelny kod nie wymaga komentarzy W wielu przypadkach kod mógłby zupełnie obejść się bez komentarzy, a jednak programiści wolą umieścić w nim komentarz, zamiast zawrzeć objaśnienia w samym kodzie. Spójrzmy na poniższy przykład. Co wolelibyśmy zobaczyć? To: // Sprawdzenie, czy pracownik ma prawo do wszystkich korzyĞci if ((employee.flags HOURLY_FLAG) (employee.age 65)) czy to: if (employee.isEligibleForFullBenefits()) Przeznaczenie tego kodu jest jasne po kilku sekundach myślenia. W wielu przypadkach jest to wy- łącznie kwestia utworzenia funkcji, która wyraża to samo co komentarz, jaki chcemy napisać. Dobre komentarze Czasami komentarze są niezbędne lub bardzo przydatne. Przedstawimy kilka przypadków, w których uznaliśmy, że warto poświęcić im czas. Należy jednak pamiętać, że naprawdę dobry komentarz to taki, dla którego znaleźliśmy powód, aby go nie pisać. Komentarze prawne Korporacyjne standardy kodowania czasami wymuszają na nas pisanie pewnych komentarzy z powodów prawnych. Na przykład informacje o prawach autorskich są niezbędnym elementem umieszczanym w komentarzu na początku każdego pliku źródłowego. Przykładem może być standardowy komentarz, jaki umieszczaliśmy na początku każdego pliku źródłowego w FitNesse. Na szczęście nasze środowisko IDE ukrywa te komentarze przez ich auto- matyczne zwinięcie. // Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved. // Released under the terms of the GNU General Public License version 2 or later. K O M E N T A R Z E 7 7 Kup książkęPoleć książkę Tego typu komentarze nie powinny być wielkości umów lub kodeksów. Tam, gdzie to możliwe, warto odwoływać się do standardowych licencji lub zewnętrznych dokumentów, a nie umieszczać w komentarzu wszystkich zasad i warunków. Komentarze informacyjne Czasami przydatne jest umieszczenie w komentarzu podstawowych informacji. Na przykład w po- niższym komentarzu objaśniamy wartość zwracaną przez metodę abstrakcyjną. // Zwraca testowany obiekt Responder. protected abstract Responder responderInstance(); Komentarze tego typu są czasami przydatne, ale tam, gdzie to możliwe, lepiej jest skorzystać z nazwy funkcji do przekazania informacji. Na przykład w tym przypadku komentarz może stać się niepotrzebny, jeżeli zmienimy nazwę funkcji: responderBeingTested. Poniżej mamy nieco lepszy przypadek: // Dopasowywany format kk:mm:ss EEE, MMM dd, yyyy Pattern timeMatcher = Pattern.compile( \\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d* ); W tym przypadku komentarze pozwalają nam poinformować, że użyte wyrażenie regularne ma dopasować czas i datę sformatowane za pomocą funkcji SimpleDateFormat.format z użyciem zdefiniowanego formatu. Nadal lepiej jest przenieść kod do specjalnej klasy pozwalającej na kon- wertowanie formatów daty i czasu. Po tej operacji komentarz najprawdopodobniej stanie się zbędny. WyjaĂnianie zamierzeñ W niektórych przypadkach komentarze zawierają informacje nie tylko o implementacji, ale także o powodach podjęcia danej decyzji. W poniższym przypadku widzimy interesującą decyzję udo- kumentowaną w postaci komentarza. Przy porównywaniu obiektów autor zdecydował o tym, że obiekty jego klasy będą po posortowaniu wyżej niż obiekty pozostałych klas. public int compareTo(Object o) { if(o instanceof WikiPagePath) { WikiPagePath p = (WikiPagePath) o; String compressedName = StringUtil.join(names, ); String compressedArgumentName = StringUtil.join(p.names, ); return compressedName.compareTo(compressedArgumentName); } return 1; // JesteĞmy wiĊksi, poniewaĪ jesteĞmy wáaĞciwego typu. } Poniżej pokazany jest lepszy przykład. Możemy nie zgadzać się z rozwiązaniem tego problemu przez programistę, ale przynajmniej wiemy, co próbował zrobić. public void testConcurrentAddWidgets() throws Exception { WidgetBuilder widgetBuilder = new WidgetBuilder(new Class[]{BoldWidget.class}); String text = bold text ; 7 8 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę ParentWidget parent = new BoldWidget(new MockWidgetRoot(), bold text ); AtomicBoolean failFlag = new AtomicBoolean(); failFlag.set(false); //Jest to nasza próba uzyskania wyĞcigu //przez utworzenie duĪej liczby wątków. for (int i = 0; i 25000; i++) { WidgetBuilderThread widgetBuilderThread = new WidgetBuilderThread(widgetBuilder, text, parent, failFlag); Thread thread = new Thread(widgetBuilderThread); thread.start(); } assertEquals(false, failFlag.get()); } WyjaĂnianie Czasami przydatne jest wytłumaczenie znaczenia niejasnych argumentów lub zwracanych wartości. Zwykle lepiej jest znaleźć sposób na to, by ten argument lub zwracana wartość były bardziej czytelne, ale jeżeli są one częścią biblioteki standardowej lub kodu, którego nie możemy zmieniać, to wyjaśnienia w komentarzach mogą być użyteczne. public void testCompareTo() throws Exception { WikiPagePath a = PathParser.parse( PageA ); WikiPagePath ab = PathParser.parse( PageA.PageB ); WikiPagePath b = PathParser.parse( PageB ); WikiPagePath aa = PathParser.parse( PageA.PageA ); WikiPagePath bb = PathParser.parse( PageB.PageB ); WikiPagePath ba = PathParser.parse( PageB.PageA ); assertTrue(a.compareTo(a) == 0); // a == a assertTrue(a.compareTo(b) != 0); // a != b assertTrue(ab.compareTo(ab) == 0); // ab == ab assertTrue(a.compareTo(b) == -1); // a b assertTrue(aa.compareTo(ab) == -1); // aa ab assertTrue(ba.compareTo(bb) == -1); // ba bb assertTrue(b.compareTo(a) == 1); // b a assertTrue(ab.compareTo(aa) == 1); // ab aa assertTrue(bb.compareTo(ba) == 1); // bb ba } Istnieje oczywiście spore ryzyko, że komentarze objaśniające są nieprawidłowe. Warto przeanali- zować poprzedni przykład i zobaczyć, jak trudno jest sprawdzić, czy są one prawidłowe. Wyjaśnia to, dlaczego niezbędne są objaśnienia i dlaczego są one ryzykowne. Tak więc przed napisaniem tego typu komentarzy należy sprawdzić, czy nie istnieje lepszy sposób, a następnie poświęcić im więcej uwagi, aby były precyzyjne. K O M E N T A R Z E 7 9 Kup książkęPoleć książkę Ostrzeĝenia o konsekwencjach Komentarze mogą również służyć do ostrzegania innych programistów o określonych konsekwencjach. Poniższy komentarz wyjaśnia, dlaczego przypadek testowy jest wyłączony: // Nie uruchamiaj, chyba Īe masz nieco czasu do zagospodarowania. public void _testWithReallyBigFile() { writeLinesToFile(10000000); response.setBody(testFile); response.readyToSend(this); String responseString = output.toString(); assertSubString( Content-Length: ´1000000000 , responseString); assertTrue(bytesSent 1000000000); } Obecnie oczywiście wyłączamy przypadek testowy przez użycie atrybutu @Ignore z odpowiednim tekstem wyjaśniającym. @Ignore( Zajmuje zbyt dušo czasu ). Jednak w czasach przed JUnit 4 umieszczenie podkreślenia przed nazwą metody było często stosowaną konwencją. Komentarz, choć nonszalancki, dosyć dobrze wskazuje powód. Poniżej pokazany jest inny przykład: public static SimpleDateFormat makeStandardHttpDateFormat() { //SimpleDateFormat nie jest bezpieczna dla wątków, //wiĊc musimy kaĪdy obiekt tworzyü niezaleĪnie. SimpleDateFormat df = new SimpleDateFormat( EEE, dd MMM yyyy HH:mm:ss z ); df.setTimeZone(TimeZone.getTimeZone( GMT )); return df; } Można narzekać, że istnieją lepsze sposoby rozwiązania tego problemu. Mogę się z tym zgodzić. Jednak zastosowany tu komentarz jest całkiem rozsądny. Może on powstrzymać nadgorliwego programistę przed użyciem statycznego inicjalizera dla zapewnienia lepszej wydajności. Komentarze TODO Czasami dobrym pomysłem jest pozostawianie notatek „do zrobienia” w postaci komentarzy //TODO. W zamieszczonym poniżej przypadku komentarz TODO wyjaśnia, dlaczego funkcja ma zdegenerowaną implementację i jaka powinna być jej przyszłość. //TODO-MdM Nie jest potrzebna. // Oczekujemy, Īe zostanie usuniĊta po pobraniu modelu. protected VersionInfo makeVersion() throws Exception { return null; } 8 0 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę Komentarze TODO oznaczają zadania, które według programisty powinny być wykonane, ale z pewnego powodu nie można tego zrobić od razu. Może to być przypomnienie o konieczności usunięcia przestarzałej funkcji lub prośba do innej osoby o zajęcie się problemem. Może to być żą- danie, aby ktoś pomyślał o nadaniu lepszej nazwy, lub przypomnienie o konieczności wprowadze- nia zmiany zależnej od planowanego zdarzenia. Niezależnie od tego, czym jest TODO, nie może to być wymówka dla pozostawienia złego kodu w systemie. Obecnie wiele dobrych IDE zapewnia specjalne funkcje lokalizujące wszystkie komentarze TODO, więc jest mało prawdopodobne, aby zostały zgubione. Nadal jednak nie jest korzystne, by kod był nafasze- rowany komentarzami TODO. Należy więc regularnie je przeglądać i eliminować wszystkie, które się da. Wzmocnienie Komentarz może być użyty do wzmocnienia wagi operacji, która w przeciwnym razie może wyda- wać się niekonsekwencją. String listItemContent = match.group(3).trim(); // Wywoáanie trim jest naprawdĊ waĪne. Usuwa początkowe // spacje, które mogą spowodowaü, Īe element bĊdzie // rozpoznany jako kolejna lista. new ListItemWidget(this, listItemContent, this.level + 1); return buildList(text.substring(match.end())); Komentarze Javadoc w publicznym API Nie ma nic bardziej pomocnego i satysfakcjonującego, jak dobrze opisane publiczne API. Przykła- dem tego może być standardowa biblioteka Java. Bez niej pisanie programów Java byłoby trudne, o ile nie niemożliwe. Jeżeli piszemy publiczne API, to niezbędne jest napisanie dla niego dobrej dokumentacji Javadoc. Jednak należy pamiętać o pozostałych poradach z tego rozdziału. Komentarze Javadoc mogą być równie mylące, nie na miejscu i nieszczere jak wszystkie inne komentarze. Zïe komentarze Do tej kategorii należy większość komentarzy. Zwykle są to podpory złego kodu lub wymówki albo uzasadnienie niewystarczających decyzji znaczące niewiele więcej niż dyskusja programisty ze sobą. Beïkot Pisanie komentarza tylko dlatego, że czujemy, iż powinien być napisany lub też że wymaga tego proces, jest błędem. Jeżeli decydujemy się na napisanie komentarza, musimy poświęcić nieco czasu na upewnienie się, że jest to najlepszy komentarz, jaki mogliśmy napisać. K O M E N T A R Z E 8 1 Kup książkęPoleć książkę Poniżej zamieszczony jest przykład znaleziony w FitNesse. Komentarz był faktycznie przydatny. Jednak autor śpieszył się lub nie poświęcił mu zbyt wiele uwagi. Bełkot, który po sobie zostawił, stanowi nie lada zagadkę: public void loadProperties() { try { String propertiesPath = propertiesLocation + / + PROPERTIES_FILE; FileInputStream propertiesStream = new FileInputStream(propertiesPath); loadedProperties.load(propertiesStream); } catch(IOException e) { // Brak plików wáaĞciwoĞci oznacza zaáadowanie wszystkich wartoĞci domyĞlnych. } } Co oznacza komentarz w bloku catch? Jasne jest, że znaczy on coś dla autora, ale znaczenie to nie zostało dobrze wyartykułowane. Jeżeli otrzymamy wyjątek IOException, najwyraźniej oznacza to brak pliku właściwości, a w takim przypadku ładowane są wszystkie wartości domyślne. Jednak kto ładuje te wartości domyślne? Czy były załadowane przed wywołaniem loadProperties.load? Czy też loadProperties.load przechwytuje wyjątek, ładuje wartości domyślne i przekazuje nam wyjątek do zignorowania? A może loadProperties.load ładuje wszystkie wartości domyślne przed próbą załadowania pliku? Czy autor próbował usprawiedliwić przed samym sobą fakt, że po- zostawił pusty blok catch? Być może — ta możliwość jest nieco przerażająca — autor próbował powiedzieć sobie, że powinien wrócić w to miejsce i napisać kod ładujący wartości domyślne. Jedynym sposobem, aby się tego dowiedzieć, jest przeanalizowanie kodu z innych części systemu i sprawdzenie, co się w nich dzieje. Wszystkie komentarze, które wymuszają zaglądanie do innych modułów w celu ich zrozumienia, nie są warte bitów, które zajmują. PowtarzajÈce siÚ komentarze Na listingu 4.1 zamieszczona jest prosta funkcja z komentarzem w nagłówku, który jest całkowicie zbędny. Prawdopodobnie dłużej zajmuje przeczytanie komentarza niż samego kodu. L I S T I N G 4 . 1 . waitForClose // Metoda uĪytkowa koĔcząca pracĊ, gdy this.closed ma wartoĞü true. Zgáasza wyjątek, // jeĪeli przekroczony zostanie czas oczekiwania. public synchronized void waitForClose(final long timeoutMillis) throws Exception { if(!closed) { wait(timeoutMillis); if(!closed) throw new Exception( MockResponseSender could not be closed ); } } Czemu służy ten komentarz? Przecież nie niesie więcej informacji niż sam kod. Nie uzasadnia on kodu, nie przedstawia zamierzeń ani przyczyn. Nie jest łatwiejszy do czytania od samego kodu. 8 2 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę W rzeczywistości jest mniej precyzyjny niż kod i wymusza na czytelniku zaakceptowanie braku precyzji w imię prawdziwego zrozumienia. Jest on podobny do paplania sprzedawcy używanych samochodów, który zapewnia, że nie musisz zaglądać pod maskę. Spójrzmy teraz na legion bezużytecznych i nadmiarowych komentarzy Javadoc pobranych z pro- gramu Tomcat i zamieszczonych na listingu 4.2. Komentarze te mają za zadanie wyłącznie zaciem- nić i popsuć kod. Nie mają one żadnej wartości dokumentującej. Co gorsza, pokazałem tutaj tylko kilka pierwszych. W tym module znajduje się znacznie więcej takich komentarzy. L I S T I N G 4 . 2 . ContainerBase.java (Tomcat) public abstract class ContainerBase implements Container, Lifecycle, Pipeline, MBeanRegistration, Serializable { /** * The processor delay for this component. */ protected int backgroundProcessorDelay = -1; /** * The lifecycle event support for this component. */ protected LifecycleSupport lifecycle = new LifecycleSupport(this); /** * The container event listeners for this Container. */ protected ArrayList listeners = new ArrayList(); /** * The Loader implementation with which this Container is * associated. */ protected Loader loader = null; /** * The Logger implementation with which this Container is * associated. */ protected Log logger = null; /** * Associated logger name. */ protected String logName = null; /** * The Manager implementation with which this Container is * associated. */ protected Manager manager = null; /** * The cluster with which this Container is associated. */ protected Cluster cluster = null; K O M E N T A R Z E 8 3 Kup książkęPoleć książkę /** * The human-readable name of this Container. */ protected String name = null; /** * The parent Container to which this Container is a child. */ protected Container parent = null; /** * The parent class loader to be configured when we install a * Loader. */ protected ClassLoader parentClassLoader = null; /** * The Pipeline object with which this Container is * associated. */ protected Pipeline pipeline = new StandardPipeline(this); /** * The Realm with which this Container is associated. */ protected Realm realm = null; /** * The resources DirContext object with which this Container * is associated. */ protected DirContext resources = null; MylÈce komentarze Czasami pomimo najlepszych intencji programista zapisuje w komentarzu nieprecyzyjne zdania. Wróćmy na moment do nadmiarowego, ale również nieco mylącego komentarza zamieszczonego na listingu 4.1. Czy Czytelnik zauważył, w czym ten komentarz jest mylący? Metoda ta nie kończy się, gdy this.closed ma wartość true. Kończy się ona, jeżeli this.closed ma wartość true; w przeciw- nym razie czeka określony czas, a następnie zgłasza wyjątek, jeżeli this.closed nadal nie ma wartości true. Ta subtelna dezinformacja umieszczona w komentarzu, który czyta się trudniej niż sam kod, może spowodować, że inny programista naiwnie wywoła tę funkcję, oczekując, że zakończy się od razu, gdy this.closed przyjmie wartość true. Ten biedny programista może zorientować się, o co chodzi, dopiero w sesji debugera, gdy będzie próbował zorientować się, dlaczego jego kod działa tak powoli. 8 4 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę Komentarze wymagane Wymaganie, aby każda funkcja posiadała Javadoc lub aby każda zmienna posiadała komentarz, jest po prostu głupie. Tego typu komentarze tylko zaciemniają kod i prowadzą do powszechnych pomyłek i dezorganizacji. Na przykład wymaganie komentarza Javadoc prowadzi do powstania takich potworów, jak ten zamieszczony na listingu 4.3. Takie komentarze nie wnoszą niczego, za to utrudniają zrozumienie kodu. L I S T I N G 4 . 3 . /** * * @param title Tytuá páyty CD * @param author Autor páyty CD * @param tracks Liczba ĞcieĪek na páycie CD * @param durationInMinutes Czas odtwarzania CD w minutach */ public void addCD(String title, String author, int tracks, int durationInMinutes) { CD cd = new CD(); cd.title = title; cd.author = author; cd.tracks = tracks; cd.duration = duration; cdList.add(cd); } Komentarze dziennika Czasami programiści dodają na początku każdego pliku komentarz informujący o każdej edycji. Ko- mentarze takie tworzą pewnego rodzaju dziennik wszystkich wprowadzonych zmian. Spotkałem się z modułami zawierającymi kilkanaście stron z kolejnymi pozycjami dziennika. * Changes (from 11-Oct-2001) * -------------------------- * 11-Oct-2001 : Re-organised the class and moved it to new package * com.jrefinery.date (DG); * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate * class (DG); * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate * class is gone (DG); Changed getPreviousDayOfWeek(), * getFollowingDayOfWeek() and getNearestDayOfWeek() to correct * bugs (DG); * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG); * 29-May-2002 : Moved the month constants into a separate interface * (MonthConstants) (DG); * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG); * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG); * 13-Mar-2003 : Implemented Serializable (DG); * 29-May-2003 : Fixed bug in addMonths method (DG); * 04-Sep-2003 : Implemented Comparable. Updated the isInRange javadocs (DG); * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG); K O M E N T A R Z E 8 5 Kup książkęPoleć książkę Dawno temu istniały powody tworzenia i utrzymywania takich dzienników na początku każdego modułu. Nie mieliśmy po prostu systemów kontroli wersji, które wykonywały to za nas. Obecnie jednak takie długie dzienniki tylko pogarszają czytelność modułu. Powinny zostać usunięte. Komentarze wprowadzajÈce szum informacyjny Czasami zdarza się nam spotkać komentarze, które nie są niczym więcej jak tylko szumem infor- macyjnym. Przedstawiają one oczywiste dane i nie dostarczają żadnych nowych informacji. /** * Konstruktor domyĞlny. */ protected AnnualDateRule() { } No nie, naprawdę? Albo coś takiego: /** DzieĔ miesiąca. */ private int dayOfMonth; Następnie mamy doskonały przykład nadmiarowości: /** * Zwraca dzieĔ miesiąca. * * @return dzieĔ miesiąca. */ public int getDayOfMonth() { return dayOfMonth; } Komentarze takie stanowią tak duży szum informacyjny, że nauczyliśmy się je ignorować. Gdy czytamy kod, nasze oczy po prostu je pomijają. W końcu komentarze te głoszą nieprawdę, gdy ota- czający kod jest zmieniany. Pierwszy komentarz z listingu 4.4 wydaje się właściwy2. Wyjaśnia powód zignorowania bloku catch. Jednak drugi jest czystym szumem. Najwyraźniej programista był tak sfrustrowany pisaniem bloków try-catch w tej funkcji, że musiał sobie ulżyć. L I S T I N G 4 . 4 . startSending private void startSending() { try { doSending(); } catch(SocketException e) { // Normalne. KtoĞ zatrzymaá Īądanie. } catch(Exception e) 2 Obecny trend sprawdzania poprawności w komentarzach przez środowiska IDE jest zbawieniem dla wszystkich, którzy czytają dużo kodu. 8 6 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę { try { response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch(Exception e1) { // MuszĊ zrobiü przerwĊ! } } } Zamiast szukać ukojenia w bezużytecznych komentarzach, programista powinien zauważyć, że je- go frustracja może być rozładowana przez poprawienie struktury kodu. Powinien skierować swoją energię na wyodrębnienie ostatniego bloku try-catch do osobnej funkcji, jak jest to pokazane na listingu 4.5. L I S T I N G 4 . 5 . startSending (zmodyfikowany) private void startSending() { try { doSending(); } catch(SocketException e) { // Normalne. KtoĞ zatrzymaá Īądanie. } catch(Exception e) { addExceptionAndCloseResponse(e); } } private void addExceptionAndCloseResponse(Exception e) { try { response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch(Exception e1) { } } Warto zastąpić pokusę tworzenia szumu determinacją do wyczyszczenia swojego kodu. Pozwala to stać się lepszym i szczęśliwszym programistą. PrzeraĝajÈcy szum Komentarze Javadoc również mogą być szumem. Jakie jest przeznaczenie poniższych komentarzy Javadoc (ze znanej biblioteki open source)? Odpowiedź: żadne. Są to po prostu nadmiarowe ko- mentarze stanowiące szum informacyjny, napisane w źle pojętej chęci zapewnienia dokumentacji. K O M E N T A R Z E 8 7 Kup książkęPoleć książkę /** Nazwa. */ private String name; /** Wersja. */ private String version; /** nazwaLicencji. */ private String licenceName; /** Wersja. */ private String info; Przeczytajmy dokładniej te komentarze. Czy czytelnik może zauważyć błąd kopiowania i wkleja- nia? Jeżeli autor nie poświęcił uwagi pisaniu komentarzy (lub ich wklejaniu), to czy czytelnik może oczekiwać po nich jakiejś korzyści? Nie uĝywaj komentarzy, jeĝeli moĝna uĝyÊ funkcji lub zmiennej Przeanalizujmy poniższy fragment kodu: // Czy moduá z listy globalnej mod zaleĪy // od podsystemu, którego jest czĊĞcią? if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())) Może to być przeorganizowane bez użycia komentarzy: ArrayList moduleDependees = smodule.getDependSubsystems(); String ourSubSystem = subSysMod.getSubSystem(); if (moduleDependees.contains(ourSubSystem)) Autor oryginalnego kodu prawdopodobnie napisał komentarz na początku (niestety), a następnie kod realizujący zadanie z komentarza. Jeżeli jednak autor zmodyfikowałby kod w sposób, w jaki ja to wykonałem, komentarz mógłby zostać usunięty. Znaczniki pozycji Czasami programiści lubią zaznaczać określone miejsca w pliku źródłowym. Na przykład ostatnio trafiłem na program, w którym znalazłem coś takiego: // Akcje ////////////////////////////////// Istnieją rzadkie przypadki, w których sensowne jest zebranie określonych funkcji razem pod tego rodzaju transparentami. Jednak zwykle powodują one chaos, który powinien być wyeliminowany — szczególnie ten pociąg ukośników na końcu. Transparent ten jest zaskakujący i oczywisty, jeżeli nie widzimy go zbyt często. Tak więc warto używać ich oszczędnie i tylko wtedy, gdy ich zalety są wyraźne. Jeżeli zbyt często używamy tych transparentów, zaczynają być traktowane jako szum tła i ignorowane. Komentarze w klamrach zamykajÈcych Zdarza się, że programiści umieszczają specjalne komentarze po klamrach zamykających, tak jak na listingu 4.6. Choć może to mieć sens w przypadku długich funkcji, z głęboko zagnieżdżonymi strukturami, w małych i hermetycznych funkcjach, jakie preferujemy, tworzą tylko nieład. Jeżeli więc Czytelnik będzie chciał oznaczać klamry zamykające, niech spróbuje zamiast tego skrócić funkcję. 8 8 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę L I S T I N G 4 . 6 . wc.java public class wc { public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line; int lineCount = 0; int charCount = 0; int wordCount = 0; try { while ((line = in.readLine()) != null) { lineCount++; charCount += line.length(); String words[] = line.split( \\W ); wordCount += words.length; } //while System.out.println( wordCount = + wordCount); System.out.println( lineCount = + lineCount); System.out.println( charCount = + charCount); } // try catch (IOException e) { System.err.println( Error: + e.getMessage()); } //catch } //main } Atrybuty i dopiski /* Dodane przez Ricka */ Systemy kontroli wersji świetnie nadają się do zapamiętywania, kto (i kiedy) dodał określony fragment. Nie ma potrzeby zaśmiecania kodu tymi małymi dopiskami. Można uważać, że tego ty- pu komentarze będą przydatne do sprawdzenia, z kim można porozmawiać na temat danego frag- mentu kodu. Rzeczywistość jest inna — zwykle zostają tam przez lata, tracąc na dokładności i uży- teczności. Pamiętajmy — systemy kontroli wersji są lepszym miejscem dla tego rodzaju informacji. Zakomentowany kod Niewiele jest praktyk tak nieprofesjonalnych, jak zakomentowanie kodu. Nie rób tego! InputStreamResponse response = new InputStreamResponse(); response.setBody(formatter.getResultStream(), formatter.getByteCount()); // InputStream resultsStream = formatter.getResultStream(); // StreamReader reader = new StreamReader(resultsStream); // response.setContent(reader.read(formatter.getByteCount())); Inni programiści, którzy zobaczą taki zakomentowany kod, nie będą mieli odwagi go usunąć. Uznają, że jest tam z jakiegoś powodu i że jest zbyt ważny, aby go usunąć. W ten sposób zakomentowany kod zaczyna się odkładać jak osad na dnie butelki zepsutego wina. K O M E N T A R Z E 8 9 Kup książkęPoleć książkę Przeanalizujmy fragment z projektu Apache: this.bytePos = writeBytes(pngIdBytes, 0); //hdrPos = bytePos; writeHeader(); writeResolution(); //dataPos = bytePos; if (writeImageData()) { writeEnd(); this.pngBytes = resizeByteArray(this.pngBytes, this.maxPos); } else { this.pngBytes = null; } return this.pngBytes; Dlaczego te dwa wiersze kodu są zakomentowane? Czy są ważne? Czy jest to pozostałość po wcze- śniejszych zmianach? Czy też są błędami, które ktoś przed laty zakomentował i nie zadał sobie tru- du, aby to wyczyścić? W latach sześćdziesiątych ubiegłego wieku komentowanie kodu mogło być przydatne. Jednak od bardzo długiego czasu mamy już dobre systemy kontroli wersji. Systemy te pamiętają za nas wcze- śniejszy kod. Nie musimy już komentować kodu. Po prostu możemy go usunąć. Nie stracimy go. Gwarantuję. Komentarze HTML Kod HTML w komentarzach do kodu źródłowego jest paskudny, o czym można się przekonać po przeczytaniu kodu zamieszczonego poniżej. Powoduje on, że komentarze są trudne do przeczyta- nia w jedynym miejscu, gdzie powinny być łatwe do czytania — edytorze lub środowisku IDE. Je- żeli komentarze mają być pobierane przez jakieś narzędzie (na przykład Javadoc), aby mogły być wyświetlone na stronie WWW, to zadaniem tego narzędzia, a nie programisty, powinno być opa- trzenie ich stosownymi znacznikami HTML. /** * Zadanie uruchomienia testów sprawnoĞci. * Zadanie uruchamia testy fitnesse i publikuje wyniki. * p/ * pre * Zastosowanie: * lt;taskdef name= quot;execute-fitnesse-tests quot; * classname= quot;fitnesse.ant.ExecuteFitnesseTestsTask quot; * classpathref= quot;classpath quot; / gt; * LUB * lt;taskdef classpathref= quot;classpath quot; * resource= quot;tasks.properties quot; / gt; * p/ * lt;execute-fitnesse-tests * suitepage= quot;FitNesse.SuiteAcceptanceTests quot; * fitnesseport= quot;8082 quot; * resultsdir= quot;${results.dir} quot; * resultshtmlpage= quot;fit-results.html quot; * classpathref= quot;classpath quot; / gt; * /pre */ 9 0 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę Informacje nielokalne Jeżeli konieczne jest napisanie komentarza, to należy upewnić się, że opisuje on kod znajdujący się w pobliżu. Nie należy udostępniać informacji dotyczących całego systemu w kontekście komenta- rzy lokalnych. Weźmy jako przykład zamieszczone poniżej komentarze Javadoc. Pomijając fakt, że są zupełnie zbędne, zawierają one informacje o domyślnym porcie. Funkcja jednak nie ma abso- lutnie żadnej kontroli nad tą wartością domyślną. Komentarz nie opisuje funkcji, ale inną część systemu, znacznie od niej oddaloną. Oczywiście, nie ma gwarancji, że komentarz ten zostanie zmieniony, gdy kod zawierający wartość domyślną ulegnie zmianie. /** * Port, na którym dziaáa fitnesse. DomyĞlnie b 8082 /b . * * @param fitnessePort */ public void setFitnessePort(int fitnessePort) { this.fitnessePort = fitnessePort; } Nadmiar informacji Nie należy umieszczać w komentarzach interesujących z punktu widzenia historii dyskusji lub luź- nych opisów szczegółów. Komentarz zamieszczony poniżej został pobrany z modułu mającego za zadanie sprawdzić, czy funkcja może kodować i dekodować zgodnie ze standardem base64. Osoba czytająca ten kod nie musi znać wszystkich szczegółowych informacji znajdujących się w komentarzu, poza numerem RFC. /* RFC 2045 - Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies section 6.8. Base64 Content-Transfer-Encoding The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single digit in the base64 alphabet. When encoding a bit stream via the base64 encoding, the bit stream must be presumed to be ordered with the most-significant-bit first. That is, the first bit in the stream will be the high-order bit in the first 8-bit byte, and the eighth bit will be the low-order bit in the first 8-bit byte, and so on. */ Nieoczywiste poïÈczenia Połączenie pomiędzy komentarzem a kodem, który on opisuje, powinno być oczywiste. Jeżeli mamy problemy z napisaniem komentarza, to powinniśmy przynajmniej doprowadzić do tego, by czytel- nik patrzący na komentarz i kod rozumiał, o czym mówi dany komentarz. K O M E N T A R Z E 9 1 Kup książkęPoleć książkę Jako przykład weźmy komentarz zaczerpnięty z projektu Apache: /* * Zaczynamy od tablicy, która jest na tyle duĪa, aby zmieĞciü wszystkie piksele * (plus filter bajtów) oraz dodatkowe 200 bajtów na informacje nagáówka. */ this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200]; Co to są bajty filter? Czy ma to jakiś związek z wyrażeniem +1? A może z *3? Z obydwoma? Czy piksel jest bajtem? Dlaczego 200? Zadaniem komentarza jest wyjaśnianie kodu, który sam się nie ob- jaśnia. Jaka szkoda, że sam komentarz wymaga dodatkowego objaśnienia. Nagïówki funkcji Krótkie funkcje nie wymagają rozbudowanych opisów. Odpowiednio wybrana nazwa małej funkcji realizującej jedną operację jest zwykle lepsza niż nagłówek z komentarzem. Komentarze Javadoc w niepublicznym kodzie Komentarze Javadoc są przydatne w publicznym API, ale za to niemile widziane w kodzie nieprze- znaczonym do publicznego rozpowszechniania. Generowanie stron Javadoc dla klas i funkcji we- wnątrz systemu zwykle nie jest przydatne, a dodatkowy formalizm komentarzy Javadoc przyczynia się jedynie do powstania błędów i rozproszenia uwagi. Przykïad Kod zamieszczony na listingu 4.7 został przeze mnie napisany na potrzeby pierwszego kursu XP Immersion. Był on w zamierzeniach przykładem złego stylu kodowania i komentowania. Później Kent Beck przebudował go do znacznie przyjemniejszej postaci na oczach kilkudziesięciu entuzja- stycznie reagujących studentów. Później zaadaptowałem ten przykład na potrzeby mojej książki Agile Software Development, Principles, Patterns, and Practices i pierwszych artykułów Craftman publikowanych w magazynie Software Development. Fascynujące w tym module jest to, że swego czasu byłby on uznawany za „dobrze udokumentowany”. Teraz postrzegamy go jako mały bałagan. Spójrzmy, jak wiele problemów z komentarzami można tutaj znaleźć. L I S T I N G 4 . 7 . GeneratePrimes.java /** * Klasa ta generuje liczby pierwsze do okreĞlonego przez uĪytkownika * maksimum. UĪytym algorytmem jest sito Eratostenesa. * p * Eratostenes z Cyrene, urodzony 276 p.n.e. w Cyrene, Libia -- * zmará 194 p.n.e. w Aleksandrii. Pierwszy czáowiek, który obliczyá * obwód Ziemi. Znany równieĪ z prac nad kalendarzem * z latami przestĊpnymi i prowadzenia biblioteki w Aleksandrii. * p * Algorytm jest dosyü prosty. Mamy tablicĊ liczb caákowitych * zaczynających siĊ od 2. WykreĞlamy wszystkie wielokrotnoĞci 2. Szukamy 9 2 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę * nastĊpnej niewykreĞlonej liczby i wykreĞlamy wszystkie jej wielokrotnoĞci. * Powtarzamy dziaáania do momentu osiągniĊcia pierwiastka kwadratowego z maksymalnej wartoĞci. * * @author Alphonse * @version 13 Feb 2002 atp */ import java.util.*; public class GeneratePrimes { /** * @param maxValue jest limitem generacji. */ public static int[] generatePrimes(int maxValue) { if (maxValue = 2) // Jedyny prawidáowy przypadek. { // Deklaracje. int s = maxValue + 1; // Rozmiar tablicy. boolean[] f = new boolean[s]; int i; // Inicjalizacja tablicy wartoĞciami true. for (i = 0; i s; i++) f[i] = true; // Usuwanie znanych liczb niebĊdących pierwszymi. f[0] = f[1] = false; // Sito. int j; for (i = 2; i Math.sqrt(s) + 1; i++) { if (f[i]) // JeĪeli i nie jest wykreĞlone, wykreĞlamy jego wielokrotnoĞci. { for (j = 2 * i; j s; j += i) f[j] = false; // WielokrotnoĞci nie są pierwsze. } } // Ile mamy liczb pierwszych? int count = 0; for (i = 0; i s; i++) { if (f[i]) count++; // Licznik trafieĔ. } int[] primes = new int[count]; // Przeniesienie liczb pierwszych do wyniku. for (i = 0, j = 0; i s; i++) { if (f[i]) // JeĪeli pierwsza. primes[j++] = i; } return primes; // Zwracamy liczby pierwsze. } else // maxValue 2 return new int[0]; // Zwracamy pustą tablicĊ, jeĪeli niewáaĞciwe dane wejĞciowe. } } K O M E N T A R Z E 9 3 Kup książkęPoleć książkę Na listingu 4.8 zamieszczona jest przebudowana wersja tego samego modułu. Warto zauważyć, że znacznie ograniczona jest liczba komentarzy. W całym module znajdują się tylko dwa komentarze. Oba są z natury opisowe. L I S T I N G 4 . 8 . PrimeGenerator.java (przebudowany) /** * Klasa ta generuje liczby pierwsze do okreĞlonego przez uĪytkownika * maksimum. UĪytym algorytmem jest sito Eratostenesa. * Mamy tablicĊ liczb caákowitych zaczynających siĊ od 2. * Wyszukujemy pierwszą nieokreĞloną liczbĊ i wykreĞlamy wszystkie jej * wielokrotnoĞci. Powtarzamy, aĪ nie bĊdzie wiĊcej wielokrotnoĞci w tablicy. */ public class PrimeGenerator { private static boolean[] crossedOut; private static int[] result; public static int[] generatePrimes(int maxValue) { if (maxValue 2) return new int[0]; else { uncrossIntegersUpTo(maxValue); crossOutMultiples(); putUncrossedIntegersIntoResult(); return result; } } private static void uncrossIntegersUpTo(int maxValue) { crossedOut = new boolean[maxValue + 1]; for (int i = 2; i crossedOut.length; i++) crossedOut[i] = false; } private static void crossOutMultiples() { int limit = determineIterationLimit(); for (int i = 2; i = limit; i++) if (notCrossed(i)) crossOutMultiplesOf(i); } private static int determineIterationLimit() { // KaĪda wielokrotnoĞü w tablicy ma podzielnik bĊdący liczbą pierwszą // mniejszą lub równą pierwiastkowi kwadratowemu wielkoĞci tablicy, // wiĊc nie musimy wykreĞlaü wielokrotnoĞci wiĊkszych od tego pierwiastka. double iterationLimit = Math.sqrt(crossedOut.length); return (int) iterationLimit; } private static void crossOutMultiplesOf(int i) { for (int multiple = 2*i; multiple crossedOut.length; multiple += i) crossedOut[multiple] = true; 9 4 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę } private static boolean notCrossed(int i) { return crossedOut[i] == false; } private static void putUncrossedIntegersIntoResult() { result = new int[numberOfUncrossedIntegers()]; for (int j = 0, i = 2; i crossedOut.length; i++) if (notCrossed(i)) result[j++] = i; } private static int numberOfUncrossedIntegers() { int count = 0; for (int i = 2; i crossedOut.length; i++) if (notCrossed(i)) count++; return count; } } Można się spierać, że pierwszy komentarz jest nadmiarowy, ponieważ czyta się go podobnie jak samą funkcję genratePrimes. Uważam jednak, że komentarz ułatwia czytelnikowi poznanie algo- rytmu, więc zdecydowałem o jego pozostawieniu. Drugi komentarz jest niemal na pewno niezbędny. Wyjaśnia powody zastosowania pierwiastka jako ograniczenia pętli. Można sprawdzić, że żadna z prostych nazw zmiennych ani inna struktura kodu nie pozwala na wyjaśnienie tego punktu. Z drugiej strony, użycie pierwiastka może być próż- nością. Czy faktycznie oszczędzam dużo czasu przez ograniczenie liczby iteracji do pierwiastka liczby? Czy obliczenie pierwiastka nie zajmuje więcej czasu, niż uda się nam zaoszczędzić? Warto o tym pomyśleć. Zastosowanie pierwiastka jako limitu pętli zadowala siedzącego we mnie eksperta C i asemblera, ale nie jestem przekonany, że jest to warte czasu i energii osoby, która ma za zadanie zrozumieć ten kod. Bibliografia [KP78]: Kernighan i Plaugher, The Elements of Programming Style, McGraw-Hill 1978. K O M E N T A R Z E 9 5 Kup książkęPoleć książkę 9 6 R O Z D Z I A Ł 4 . Kup książkęPoleć książkę A Abstract Factory, 46, 172 abstrakcja danych, 113 abstrakcje, 300 ABY, 59 ACMEPort, 128 Active Record, 120 Agile, 16, 142 akapity ABY, 59 akcesory, 47 analiza pokrycia kodu, 266 antysymetria danych i obiektów, 115 AOP, 176 API Windows C, 45 aplikacje jednowątkowe, 194 WWW, 194 architektura EJB, 176 architektura EJB2, 176 Args, 210 implementacja klasy, 210 ArgsException, 253, 260 argumenty funkcji, 62 argumenty obiektowe, 64 argumenty typu String, 228 argumenty wybierające, 305 argumenty wyjściowe, 62, 66, 298 argumenty znacznikowe, 63, 298 asercje, 149 ASM, 177, 206 AspectJ, 181 Aspect-Oriented Framework, 206 aspekty, 176 AspectJ, 181 assertEquals(), 64 atrybuty, 89 automatyczne sterowanie serializacją, 282 B Basic, 45 BDUF, 182 bean, 119 SKOROWIDZ Beck Kent, 24, 187, 264 biblioteka JUnit, 264 Big Design Up Front, 182 bloki, 57 bloki try-catch, 68 blokowanie po stronie klienta, 201 blokowanie po stronie serwera, 201 błędy, 123 Booch Grady, 30 break, 70 brodzenie, 25 budowanie, 297 C CGLIB, 177, 206 Clover, 278 ConcurrentHashMap, 199 ConTest, 207 continue, 70 CountDownLatch, 199 Cunningham Ward, 33 czasowniki, 65 czyste biblioteki Java AOP, 178 czyste granice, 139 czyste testy, 144 zasady, 151 czystość, 14, 15, 31 czystość projektu, 187 czystość testów, 143 czysty kod, 16, 23, 28, 34 czytanie kodu, 35 od góry do dołu, 58 czytelnik-pisarz, 200 czytelność, 30 D dane, 115 wejściowe, 66 wyjściowe, 288 DAO, 179 DBMS, 176 4 1 5 Kup książkęPoleć książkę definiowanie klasy wyjątków, 127 normalny przepływ, 129 deklaracje zmiennych, 102 dekorator, 179, 284 Dependency Injection, 172 dezinformacja, 42 DI, 172 Dijkstra Edserer, 70 DIP, 36, 167 długie listy importu, 317 długie nazwy, 323 długość wierszy kodu, 106 dobre komentarze, 77 dobry kod, 16, 24 Don’t Repeat Yourself, 300 dopiski, 89 dostarczenie produktu na rynek, 14 dostęp do danych, 179 DRY, 69, 300 DSL, 183 DTO, 119, 176 dyscyplina, 14 dziedziczenie stałych, 318 E efekty uboczne, 65, 323 sprzężenie czasowe, 66 efektywność, 29 EJB, 176, 194 EJB1, 174 EJB2, 174, 175, 176 EJB3, 180 eliminacja nadmiarowych instrukcji, 273 Entity Bean, 174 enum, 319 Error.java, 69 Evans Eric, 322 F F.I.R.S.T., 151 fabryka abstrakcyjna, 46, 60, 172, 284 fabryki, 172, 284 Feathers Michael, 31 Feature Envy, 288 final, 286 fizyka oprogramowania, 182 4 1 6 S K O R O W I D Z format danych wyjściowych, 288 formatowanie, 97 deklaracje zmiennych, 102 funkcje zależne, 103 gazeta, 99 gęstość pionowa, 101 koligacja koncepcyjna, 105 łamanie wcięć, 110 odległość pionowa, 101 pionowe, 98 pionowe odstępy pomiędzy segmentami kodu, 99 poziome, 106 poziome odstępy, 106 przeznaczenie, 98 puste zakresy, 110 rozmieszczenie poziome, 107 uporządkowanie pionowe, 105 wcięcia, 109 zasady zespołowe, 110 zmienne instancyjne, 102 formatowanie HTML, 280 Fortran, 45 Fowler Martin, 304 funkcje, 53, 298 argumenty, 62 argumenty obiektowe, 64 argumenty wyjściowe, 62, 66 argumenty znacznikowe, 63 bezargumentowe, 62 bloki, 57 bloki try-catch, 68 break, 70 continue, 70 czasowniki, 65 dane wejściowe, 66 długość, 56 dwuargumentowe, 63 efekty uboczne, 65 goto, 70 jednoargumentowe, 62 kody błędów, 67 listy argumentów, 65 nagłówki, 92 nazwy, 40, 61, 269, 307 Nie powtarzaj się, 69 obsługa błędów, 69 poziom abstrakcji, 58 return, 70 rozdzielanie poleceń i zapytań, 67 Kup książkęPoleć książkę sekcje, 58 słowa kluczowe, 65 sprzężenie czasowe, 66 switch, 59 trzyargumentowe, 64 uporządkowane składniki jednej wartości, 64 wcięcia, 57 wieloargumentowe, 62 wyjątki, 67 wykonywane czynności, 57 zależne funkcje, 103 zasada konstruowania, 56 zasady pisania, 70 zdarzenia, 63 zwracanie kodów błędów, 67 zwracanie wyniku, 62 G Ga-Jol, 16 Gamm Eric, 264 gazeta, 99 gettery, 113 gęstość pionowa, 101 Gilbert David, 277 given-when-then, 150 globalna strategia konfiguracji, 171 goto, 70 granice, 133 czyste granice, 139 korzystanie z nieistniejącego kodu, 138 przeglądanie, 136 testy graniczne, 138 testy uczące, 138 uczenie się obcego kodu, 136 zastosowanie kodu innych firm, 134 H hermetyzacja, 127, 154 hermetyzacja warunków, 312 warunki graniczne, 314 HTML, 90 Hunt Andy, 29, 300 hybrydowe struktury danych, 118 hybrydy, 118 hypotenuse, 41 I idiom późnej inicjalizacji, 170 if, 286, 300, 309 implementacja interfejsu, 46 include, 70 informacja, 42 informacje nielokalne, 91 instrukcje switch, 59 interfejsy, 46 Inversion of Control, 172 IoC, 172 J jar, 302 Java, 46, 317 JUnit, 263 klasy, 153 pośredniki, 177 współbieżność, 198 Java Swing, 156 java.util.Calendar, 278 java.util.concurrent, 198 java.util.Date, 278 java.util.Map, 134 Javadoc, 81, 280, 286 Javassist, 177 JBoss, 179 JBoss AOP, 178, 181 JCommon, 277, 280 JDBC, 179 JDK, 177 jedna asercja na test, 149 jedna koncepcja na test, 150 jedno słowo na jedno abstrakcyjne pojęcie, 48 jednoznaczne nazwy, 322 Jeffries Ron, 32 język, 298 język DSL, 184 język dziedzinowy, 183 języki testowania specyficzne dla domeny, 147 JFrame, 156 JNDI, 173 JUnit, 55, 149, 226, 263 analiza pokrycia kodu, 266 przypadki testowe, 264 S K O R O W I D Z 4 1 7 Kup książkęPoleć książkę K klasy, 153 bazowe, 301 bazowe zależne od swoich klas pochodnych, 301 DIP, 167 hermetyzacja, 154 izolowanie modułów kodu przed zmianami, 166 Java, 153 liczba zmiennych instancyjnych, 158 metody prywatne, 164 nazwy, 40, 47, 156 OCP, 166 odpowiedzialności, 154 organizacja, 153, 157 organizacja zmian, 164 prywatne funkcje użytkowe, 154 prywatne zmienne statyczne, 153 przebudowa, 277 publiczne stałe statyczne, 153 rozmiar, 154 spójność, 158 utrzymywanie spójności, 158 zasada odwrócenia zależności, 167 zasada otwarty-zamknięty, 166 zasada pojedynczej odpowiedzialności, 156 zmiany, 164, 166 klasyfikacja wyjątków, 127 kod, 24 kod innych firm, 134 kod na nieodpowiednim poziomie abstrakcji, 300 kod testów, 144 kod za przyjemny w czytaniu, 29 kody błędów, 67 kody powrotu, 124 kolekcje bezpieczne dla wątków, 198 koligacja koncepcyjna, 105 komentarze, 75, 282, 293, 296 atrybuty, 89 bełkot, 81 czytelny kod, 77 dopiski, 89 dziennik, 85 HTML, 90 informacje nielokalne, 91 informacyjne, 78 Javadoc, 81, 92 klamry zamykające, 88 4 1 8 S K O R O W I D Z mylące komentarze, 84 nadmiar informacji, 91 nagłówki funkcji, 92 nieoczywiste połączenia, 91 nieudany kod, 77 objaśniające, 79 ostrzeżenia o konsekwencjach, 80 powody pisania, 77 powtarzające się komentarze, 82 prawne, 77 szum, 87 TODO, 80 wprowadzanie szumu informacyjnego, 86 wyjaśnianie zamierzeń, 78, 79 wymagane komentarze, 85 wzmocnienie wagi operacji, 81 zakomentowany kod, 89 złe komentarze, 81 znaczniki pozycji, 88 komunikaty błędów, 127 konstruowanie systemu, 170 kontekst kodu, 40 kontekst nazwy, 50 kontener, 173 kontrolowane wyjątki, 126 korzystanie z nieistniejącego kodu, 138 korzystanie ze standardów, 183 koszt utrzymania zestawu testów, 143 listy argumentów, 65 listy importu, 317 log4j, 136 łamanie wcięć, 110 L Ł M magiczne liczby, 310 magnesy zależności, 69 main, 171 Map, 134 martwe funkcje, 298 martwy kod, 302 mechanika pisania funkcji, 71 Kup książkęPoleć książkę metody
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Czysty kod. Podręcznik dobrego programisty
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: