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)