Java. 97 rzeczy, które powinieneś wiedzieć - ebook
Java. 97 rzeczy, które powinieneś wiedzieć - ebook
Co powinien wiedzieć każdy programista Javy? To zależy. Zależy od tego, kogo pytamy, dlaczego pytamy i kiedy pytamy. Sugestii jest co najmniej tyle, ile punktów widzenia.
W tej książce znajdziesz niektóre z tych wielu punktów widzenia, aby stworzyć razem pewien przekrój i przedstawić sposób myślenia w technologii Java. To nie będzie jedyne słuszne podejście, ale 97 spojrzeń 73 autorów.
97 rzeczy, które zostały poruszone w tej książce, obejmują następujące zagadnienia: język, JVM, techniki testowania, JDK, społeczność, historię, zwinne myślenie, wiedzę wdrożeniową, profesjonalizm, styl, treść, paradygmaty programowania, programistów jako ludzi, architekturę oprogramowania, umiejętności wykraczające poza kod, narzędzia, mechanikę GC, języki JVM inne niż Java... i nie tylko.
Kategoria: | Informatyka |
Zabezpieczenie: |
Watermark
|
ISBN: | 978-83-01-21730-3 |
Rozmiar pliku: | 3,0 MB |
FRAGMENT KSIĄŻKI
ANDERS NORAS
Pracując nad pierwszą dużą wersją programu Visual Studio, zespół firmy Microsoft przedstawił światu trzy postacie programistów: Morta, Elvisa i Einsteina.
Mort był oportunistycznym deweloperem, robiącym szybkie poprawki, wymyślającym rozwiązania i idącym dalej. Elvis był pragmatycznym programistą budującym rozwiązania na lata, uczącym się w pracy. Einstein natomiast uchodził za paranoicznego programistę, mającego obsesję na punkcie projektowania najbardziej wydajnego rozwiązania i sprawdzania wszystkich możliwości przed napisaniem swojego kodu.
Będąc po stronie Javy – biorąc pod uwagę religijny podział języków programowania – śmialiśmy się z Mortów i chcieliśmy być Einsteinami budującymi frameworki, które zapewnią, że Elvisowie napiszą swój kod we „właściwy sposób”.
To był początek ery frameworków i jeśli nie byłeś biegły w najnowszym i najlepszym narzędziu do mapowania obiektowo-relacyjnego i frameworku z odwróconym sterowaniem, to nie byłeś dobrym programistą Javy. Biblioteki rozrosły się we frameworki o określonej architekturze. A ponieważ te ostatnie stały się technologicznymi ekosystemami, wielu z nas zapomniało o prostym języku, który był na początku – o Javie.
Java to wspaniały język, a jego biblioteka klas zawiera coś na każdą okazję. Potrzebujesz pracować z plikami? java.nio zapewni ci wsparcie. Bazy danych? java.sql to miejsce, do którego należy się udać. Prawie każda dystrybucja Javy zawiera nawet pełnowartościowy serwer HTTP, chociaż nie znajdziemy go w pakiecie java, a w com.sun.net.httpserver.
Teraz, gdy nasze aplikacje przechodzą w kierunku architektur bezserwerowych, w których jednostki wdrożeniowe mogą być pojedynczymi funkcjami, korzyści, jakie uzyskujemy z frameworków aplikacji, maleją. Dzieje się tak, ponieważ zapewne spędzamy mniej czasu na rozwiązywaniu problemów technicznych i infrastrukturalnych, skupiając nasze wysiłki programistyczne na założeniach biznesowych realizowanych przez nasze programy.
Jak to ujął Bruce Joyce:
Od czasu do czasu musimy wymyślać koło na nowo, nie dlatego, że potrzebujemy wielu kół, ale dlatego, że potrzeba nam wielu wynalazców.
Wielu próbowało zbudować ogólne frameworki dla logiki biznesowej, aby zmaksymalizować ich ponowne wykorzystanie. Większość z nich nie spełniła oczekiwań, gdyż tak naprawdę nie ma żadnych ogólnych problemów biznesowych. Robienie czegoś wyjątkowego w określony sposób jest tym, co odróżnia jeden biznes od innego. Dlatego mamy gwarancję, że będziemy pisać odrębną logikę biznesową w prawie każdym projekcie. W imię wymyślenia czegoś ogólnego i do wielokrotnego wykorzystania można by pokusić się o stworzenia silnika reguł lub czegoś podobnego. Ostatecznie skonfigurowanie silnika reguł byłoby programowaniem, często w języku gorszym od Javy. Dlaczego zatem nie spróbować po prostu użyć Javy? Będziesz zaskoczony, że wynik końcowy będzie łatwy do analizy, co z kolei sprawi, że kod będzie łatwy w utrzymaniu – nawet dla programistów spoza języka Java.
Dość często zauważysz, że biblioteka klas Javy jest trochę ograniczona i możesz potrzebować czegoś, co sprawi, że praca z datami, sieciami lub czymś innym będzie wygodniejsza. W porządku. Skorzystaj z innej biblioteki. Różnica polega na tym, że będziesz teraz używać tej biblioteki, ponieważ wystąpiła konkretna potrzeba, a nie dlatego, że była częścią stosu, z którego zawsze korzystałeś.
Następnym razem, gdy przyjdzie ci do głowy pomysł na mały program, odśwież swoją wiedzę o bibliotece klas Javy, zamiast sięgać po generator kodu JHipster. Hipsterstwo jest passe; proste życie jest tam, gdzie właśnie jesteśmy. Założę się, że Mort kochał proste życie.TESTOWANIE ZATWIERDZAJĄCE
EMILY BACHE
Czy kiedykolwiek zdarzyło ci się napisać twierdzenie testowe z błędną lub pustą wartością? Coś takiego:
assertEquals("", functionCall())
Wtedy functionCall zwraca łańcuch, a ty nie jesteś pewien, jaki dokładnie powinien on być, ale będziesz wiedział, że jest właściwy, gdy go zobaczysz. Oczywiście po pierwszym uruchomieniu testu kończy się on niepowodzeniem, ponieważ functionCall zwraca łańcuch, który nie jest pusty. (Być może trzeba wykonać kilka prób, aż zwracana wartość będzie wyglądać poprawnie.) Następnie wklejasz tę wartość zamiast pustego łańcucha w assertEquals. Teraz test powinien zakończyć się pomyślnie. Sukces! To właśnie nazwałabym testem zatwierdzającym.
Decydującym krokiem jest tutaj podjęcie decyzji, który wynik jest poprawny i użycie go jako wartości oczekiwanej. Wynik „zatwierdzamy” – jest na tyle dobry, że można go przyjąć. Spodziewam się, że robiłeś coś takiego, nawet nie myśląc o tym. Być może nadasz temu inną nazwę: określa się to również testowaniem migawkowym lub testowaniem golden master. Z mojego doświadczenia wynika, że jeśli masz platformę testową zaprojektowaną specjalnie do tego typu testów, wszystko dobrze się składa i testowanie w ten sposób staje się łatwiejsze.
W przypadku klasycznego frameworka dla testów jednostkowych, takiego jak JUnit, aktualizacja takich oczekiwanych łańcuchów, gdy się zmieniają, może być nieco żmudna. Kończy się to wklejaniem różnych rzeczy bezpośrednio w kodzie źródłowym. W przypadku narzędzia do testów zatwierdzających, tego typu łańcuch zostanie za to zapisany w pliku. To od razu otwiera nowe możliwości. Możesz użyć odpowiedniego narzędzia do porównywania, aby przejść przez zmiany i scalić je jedna po drugiej. Możesz mieć możliwość podświetlania składni dla łańcuchów JSON i tym podobnych. Możesz wyszukiwać i zamieniać aktualizacje między testami w różnych klasach.
Jakie są więc dobre sytuacje dla testów zatwierdzających? Oto kilka:
Kod bez testów jednostkowych, który trzeba zmienić
Jeśli kod jest w produkcji, to wszystko, co robi, jest domyślnie uważane za poprawne i może zostać zatwierdzone. Wymagająca część tworzenia testów zamienia się w problem ze znalezieniem powiązań i wyłuskaniem elementów logiki, które zwracają coś interesującego, co można zatwierdzić.
REST API i funkcje, które zwracają JSON lub XML
Jeśli wynikiem jest dłuższy łańcuch, to przechowywanie go poza kodem źródłowym jest dobrym wyborem. JSON i XML mogą być formatowane przy użyciu białych znaków, dzięki czemu można je łatwo porównać z oczekiwaną wartością. Jeśli w formacie JSON lub XML istnieją wartości, które są innego typu – na przykład daty i godziny – może być konieczne sprawdzenie ich oddzielnie przed zastąpieniem ich ustalonym łańcuchem i zatwierdzeniem pozostałej części.
Logika biznesowa, która buduje i zwraca złożony obiekt
Zacznij od napisania klasy Printer, która może przyjąć zwracany obiekt złożony i sformatować go jako łańcuch. Pomyśl o Receipt, Prescription lub Order. Każda z tych klas może być przedstawiona jako wielowierszowy łańcuch czytelny dla człowieka. Twoja klasa Printer może wyświetlać tylko podsumowanie – przejście przez graf obiektu, aby wyciągnąć odpowiednie szczegóły. Twoje testy będą następnie wykorzystywać różne reguły biznesowe i używać Printer w celu utworzenia łańcucha do zatwierdzenia. Jeśli masz właściciela produktu lub analityka biznesowego, którzy nie są programistami, mogą oni nawet przeczytać wyniki testów i sprawdzić, czy są poprawne.
Jeśli dysponujesz już testami, z których wynika, że będą istnieć łańcuchy dłuższe niż jeden wiersz, polecam dowiedzieć się więcej o testowaniu zatwierdzającym i zacząć używać narzędzia, które je obsługuje.ROZSZERZENIE JAVADOC PRZEZ ASCIIDOC
JAMES ELLIOTT
Programiści Java znają już Javadoc. Ci, którzy są w temacie już od dłuższego czasu, pamiętają, jaka była to istotna zmiana, ponieważ Java stała się pierwszym popularnym językiem, który zintegrował generator dokumentacji bezpośrednio z kompilatorem i standardowym zestawem narzędzi. Wynikająca z tego eksplozja dokumentacji API (nawet jeśli nie zawsze była ona świetna lub dopracowana) przyniosła nam wszystkim ogromne korzyści, a trend rozprzestrzenił się na wiele innych języków. Jak donosił James Gosling, Javadoc był początkowo kontrowersyjny, ponieważ „dobry redaktor dokumentacji technicznej mógłby zrobić to o wiele lepiej” – natomiast istnieje znacznie więcej API niż redaktorów dokumentacji technicznej, a zaleta posiadania czegoś powszechnie dostępnego została dobrze sprawdzona.
Czasami jednak potrzebujesz czegoś więcej niż tylko dokumentacji API – znacznie więcej, niż możesz zmieścić na stronach opisu pakietów i projektów, które oferuje Javadoc. Skupione na końcowym użytkowniku przewodniki i instrukcje, teoria i szczegółowe informacje o architekturze, wyjaśnienia dotyczące łączenia ze sobą wielu komponentów... żadne z nich nie pasuje do Javadoc.
Czego możemy zatem użyć, aby zaspokoić te potrzeby? Odpowiedzi zmieniały się na przestrzeni czasu. W latach 80. ubiegłego wieku przełomowym, wieloplatformowym narzędziem do tworzenia dokumentów technicznych, wyposażonym w GUI był FrameMaker. Do generowania atrakcyjnej drukowanej dokumentacji API za pomocą FrameMakera, Javadoc zawierał nawet MIF Doclet – z czego pozostała tylko szczątkowa wersja dla systemu Windows. DocBook XML oferuje podobne możliwości związane ze strukturą i łączeniem, wraz z otwartą specyfikacją i wieloplatformowym zestawem narzędzi, ale bezpośrednia praca z jego surowym formatem XML jest niepraktyczna. Nadążanie za jego narzędziami do edycji stało się kosztowne i uciążliwe, a nawet te dobre spośród nich stały się toporne i utrudniały pisanie.
Jestem podekscytowany, gdyż znalazłem lepszą odpowiedź: AsciiDoc – oferujący całą siłę DocBooka w łatwym do napisania (i odczytania) formacie tekstowym, w którym robienie prostych rzeczy jest trywialne, a wykonywanie złożonych jest możliwe. Większość konstrukcji AsciiDoc jest tak samo czytelna i dostępna jak w innych, lekkich formatach znacznikowych, takich jak Markdown, które stały się popularne na internetowych forach dyskusyjnych. A kiedy potrzebujesz czegoś bardziej wyrafinowanego, możesz dołączyć złożone równania przy użyciu formatów MathML lub LaTeX, sformatowane listingi kodów źródłowych z ponumerowanymi i dołączonymi objaśnieniami w akapitach tekstu, różnego rodzaju specjalne bloki i nie tylko.
AsciiDoc został wprowadzony wraz z implementacją Pythona w 2002 roku. Obecną oficjalną implementacją (i najważniejszą w zakresie tego języka) jest Asciidoctor, wydany w 2013 roku. Jego kod, napisany w Ruby można również uruchomić w JVM przez AsciidoctorJ (z wykorzystaniem wtyczek do Mavena, czy Gradle’a) lub transponować do JavaScriptu – wszystko to działa dobrze w środowiskach bazujących na ciągłej integracji. Gdy na bazie powiązanej dokumentacji (nawet z różnych repozytoriów) musisz zbudować całą witrynę, narzędzia takie jak Antora sprawiają, że jest to szokująco łatwe. Społeczność jest przyjazna i wspierająca, a obserwowanie jej rozwoju i postępów w ciągu ostatniego roku było inspirujące. A jeśli ma to dla ciebie znaczenie, to proces formalnej standaryzacji specyfikacji AsciiDoc jest w toku.
Lubię tworzyć bogatą, atrakcyjną dokumentację do projektów, które udostępniam. AsciiDoc znacznie mi to ułatwiło i wprowadziło tak szybkie cykle pracy, że szlifowanie i doskonalenie dokumentacji stało się zabawą. Mam nadzieję, że w twoim przypadku będzie tak samo. I, zataczając koło, jeśli zdecydujesz się postawić wszystko na AsciiDoc, przekonasz się, że istnieje nawet Doclet o nazwie Asciidoclet, który pozwala pisać Javadoc za pomocą AsciiDoc!UWAŻAJ NA OTOCZENIE SWOJEGO KONTENERA
DAVID DELABASSEE
Istnieje niebezpieczeństwo związane z konteneryzacją starszych aplikacji Java w takiej postaci, w jakiej są, wraz z ich starszymi wersjami wirtualnych maszyn Javy (JVM), ponieważ ergonomia tych starszych maszyn będzie zaburzana podczas uruchamiania w kontenerach Dockera.
Kontenery stały się de facto mechanizmem opakowania środowiska uruchomieniowego. Zapewniają wiele korzyści: pewien poziom izolacji, lepsze wykorzystanie zasobów, możliwość wdrażania aplikacji w różnych środowiskach i nie tylko. Kontenery pomagają również zmniejszyć powiązania między aplikacją a platformą znajdującą się poniżej, ponieważ dana aplikacja może być spakowana w przenośny kontener. Ta technika jest czasami używana do modernizacji starszych aplikacji. W przypadku języka Java taki kontener zawiera starszą aplikację Javy wraz z jej zależnościami, w tym starszą wersję maszyny JVM używanej przez tę aplikację.
Praktyka polegająca na konteneryzowaniu starszych aplikacji Javy wraz z ich środowiskami z pewnością może pomóc w utrzymaniu starszych aplikacji działających w nowoczesnej infrastrukturze, przez oddzielenie jej od starszej, nieobsługiwanej infrastruktury. Jednak potencjalne korzyści płynące z takiej praktyki wiążą się z nowym zestawem zagrożeń wynikających z mechanizmów ergonomii JVM.
Mechanizmy ergonomii JVM umożliwiają wirtualnym maszynom Javy dostrojenie się na podstawie dwóch kluczowych wskaźników środowiskowych: liczby procesorów i dostępnej pamięci. Dzięki tym metrykom JVM określa ważne parametry, takie jak używana metoda odśmiecania pamięci, sposób jej konfiguracji, rozmiar sterty, rozmiar puli wątków i tak dalej.
Obsługa kontenerów Dockera z systemem Linux, dodana w 191 aktualizacji JDK 8, umożliwia maszynie JVM wykorzystywanie funkcji cgroups Linuksa w celu uzyskania metryk zasobów przydzielonych do kontenera, w którym działa. Każda maszyna JVM starsza niż ta nie będzie świadoma, że działa w kontenerze i uzyska dostęp do metryk z systemu operacyjnego hosta, a nie z samego kontenera. W większości przypadków kontener jest skonfigurowany tak, aby używał tylko podzbioru zasobów hosta, dlatego w celu dostrojenia się, maszyna JVM będzie bazować na nieprawidłowych metrykach. To szybko doprowadzi do niestabilnej sytuacji, w której kontener prawdopodobnie zostanie zabity przez hosta, gdy spróbuje zużyć więcej zasobów, niż jest dostępnych.
Poniższe polecenie pokazuje, które parametry maszyny JVM są konfigurowane przez mechanizmy ergonomii JVM:
java -XX:+PrintFlagsFinal -version | grep ergonomic
Obsługa kontenerów JVM jest domyślnie włączona, ale można ją wyłączyć za pomocą flagi JVM -XX: -UseContainerSupport. Użycie tej flagi w kontenerze z ograniczonymi zasobami (procesor i pamięć) pozwala obserwować i badać wpływ mechanizmów ergonomii JVM z włączoną lub wyłączoną obsługą kontenerów.
Uruchamianie starszych JVM w kontenerach Dockera nie jest zalecane. Ale jeśli jest to jedyna możliwość, taka wersja JVM powinna być przynajmniej skonfigurowana tak, aby nie przekraczała zasobów przydzielonych do kontenera, w którym działa. Idealnym, oczywistym rozwiązaniem jest użycie nowoczesnej, wspieranej wersji JVM (na przykład JDK 11 lub nowszej), która nie tylko domyślnie obsługuje kontenery, ale także zapewnia aktualne i bezpieczne środowisko uruchomieniowe.ZACHOWANIE JEST „ŁATWE”, A STAN JEST TRUDNY
EDSON YANAGA
Gdy poznawałem programowanie obiektowe, jednymi z pierwszych nauczanych pojęć była trójka: polimorfizm, dziedziczenie i hermetyzacja. Szczerze mówiąc, spędziliśmy trochę czasu, próbując je zrozumieć i wykorzystać w kodzie. Ale, przynajmniej w moim przypadku, zbyt duży nacisk położono na pierwsze dwa pojęcia, a bardzo mało na trzecie i najważniejsze: hermetyzację.
Hermetyzacja pozwala nam okiełznać rozrastający się stan i złożoność, które są czymś typowym w dziedzinie tworzenia oprogramowania. Pomysł, że możemy ukrywać stan m.in. przed innymi komponentami i opakować jego dowolne zmiany starannie zaprojektowanym API, jest podstawą projektowania i kodowania złożonych systemów informatycznych.
Jednak, przynajmniej w świecie Javy, nie udało się rozpowszechnić niektórych dobrych praktyk dotyczących budowy dobrze hermetyzowanych systemów. Właściwości JavaBean w anemicznych klasach, które po prostu ujawniają stan wewnętrzny przez gettery i settery, są powszechne, a dzięki architekturom Java Enterprise została spopularyzowana koncepcja, że większość – jeśli nie całość – logiki biznesowej powinna być zaimplementowana w serwisach. W ich wnętrzu używamy getterów do pobrania informacji, przetworzenia ich w celu uzyskania wyniku, a następnie jego wstawienia z powrotem do naszych obiektów za pomocą setterów.
A kiedy pojawiają się błędy, przekopujemy się przez logi, używamy debugerów i próbujemy dowiedzieć się, co dzieje się z naszym kodem w środowisku produkcyjnym. Dość „łatwo” jest wykryć błędy spowodowane problemami z zachowaniem: fragmenty kodu robią coś, czego nie powinny. Z drugiej strony, gdy wydaje się, że nasz kod działa właściwie, a my nadal mamy błędy, sprawa staje się znacznie bardziej skomplikowana. Z mojego doświadczenia wynika, że najtrudniejsze do rozwiązania są błędy spowodowane niespójnym stanem. Pewnie osiągnąłeś w swoim systemie stan, który nie powinien się wydarzyć, ale jednak wystąpił – wyjątek NullPointerException dla właściwości, która nigdy nie miała być null, czy wartość ujemną, która powinna być tylko dodatnia i tak dalej.
Szanse na znalezienie kroków, które doprowadziły do takiego niespójnego stanu, są nikłe. Nasze klasy mają otoczki, które zbyt łatwo zmienić i są zbyt łatwo dostępne: każdy fragment kodu, w dowolnym miejscu w systemie, może zmienić nasz stan bez żadnego sprawdzenia czy wyważenia.
Możemy oczyścić dane wejściowe wprowadzone przez użytkownika za pomocą mechanizmów walidacji, ale taki „niewinny” setter nadal istnieje i pozwala każdemu fragmentowi kodu go wywołać. Nie będę nawet omawiał prawdopodobieństwa, że ktoś użyje instrukcji UPDATE bezpośrednio w bazie danych, aby zmienić niektóre kolumny, które są mapowane w encjach na podstawie bazy danych.
Jak możemy rozwiązać ten problem? Jedną z możliwych odpowiedzi jest niezmienność (niemutowalność). Jeśli możemy zagwarantować, że nasze obiekty są niezmienne, a spójność stanu jest sprawdzana podczas tworzenia obiektów, nigdy nie będziemy mieć niespójnego stanu w naszym systemie. Ale musimy wziąć pod uwagę, że większość frameworków Javy nie radzi sobie zbyt dobrze z niezmiennością, zatem powinniśmy przynajmniej dążyć do zminimalizowania zmienności. Posiadanie odpowiednio zakodowanych metod wytwórczych i konstruktorów może również pomóc nam osiągnąć ten stan minimalnej zmienności.
Dlatego nie generuj setterów automatycznie. Zastanów się nad tym. Czy naprawdę potrzebujesz tego settera w swoim kodzie? A jeśli zdecydujesz, że tak, być może z powodu pewnych wymagań frameworka, rozważ użycie warstwy antydegradacyjnej (anti-corruption layer) w celu ochrony i weryfikacji stanu wewnętrznego po interakcjach spowodowanych setterami.