Najwyższa pora dodać do mojej konkursowejaplikacji jakąś bazę danych. Od początku zakładałam, że z powodów edukacyjnych użyję bazy nierelacyjnej. Zdecydowałam się w końcu na MongoDB. Jest to jedna z popularnych baz nierelacyjnych obsługiwanych przez mojego dostawcę PaaS (Platform as a Service), tj. Pivotal Web Services.
Źródła
Swoją działalność w tym obszarze opieram na następujących źródłach:
Strona projektu Hibernate dla baz nierelacyjnych: http://hibernate.org/ogm/ Jeszcze nie wiem, czy Hibernate się przyda. Twórcy MongoDB twierdzą, że ich produkt likwiduje potrzebę korzystania z ORM, ale, z drugiej strony, Spring ma też swoje wymagania.
Pierwsze kroki
Po instalacji serwera bazodanowego wykonuję następujące kroki:
Uruchamiam proces serwera MongoDB:
1
mongod--dbpath istniejaca/sciezka
Przygotowuję rekord reprezentujący użytkownika mojego systemu (plik user.json). Rekordy w MongoDB mają postać dokumentów w standardzie JSON (w przybliżeniu). Wartościami pól mogą być również inne dokumenty oraz tablice (w tym tablice dokumentów). Dokumenty są przechowywane w ramach kolekcji. Kolekcja przypomina tablicę w relacyjnej bazie danych, tyle że zawarte w niej dokumenty wcale nie muszą mieć tej samej struktury. Dokumenty w kolekcji muszą mieć klucz główny o nazwie _id:
W metodzie configureAuth uzyskuję dostęp do obiektu AuthenticationManagerBuilder i definiuję parę użytkowników i nadajemy im role (użytkownika lub administratora).
W metodzie configure podaję, kto ma mieć dostęp do której części aplikacji. I tak:
Do adresów wymienionych w linii 17 dostęp mają wszyscy, a więc nie tylko zalogowani użytkownicy.
(18) Do stron pod adresem /admin i poniżej dostęp ma tylko użytkownik o prawach administratora.
(19) Do stron pod adresem /user i poniżej dostęp ma tylko użytkownik w roli USER.
(20) Do reszty stron dostęp mają tylko zalogowani użytkownicy, niezależnie od roli.
Włączam dodatkowo standardowy formularz logowania (21) i ustawiam adres / jako domyślne miejsce lądowania po udanym zalogowaniu bądź wylogowaniu.
Dodam jeszcze następujący fragment kodu do mojego szablonu strony:
Uwaga! Żeby silnik szablonów Thymeleaf odpowiednio przetworzył ten fragment, w pom.xml muszę dodać zależność do artefaktu thymeleaf-extras-springsecurity4.
Podobny obrazek widział chyba każdy programista aplikacji webowej. Od czasu do czasu serwer Tomcat postanawia poinformować użytkownika aplikacji, że coś poszło nie tak. Informuje dość brutalnie, przynajmniej od strony stylistycznej. Co gorsza, przy okazji potrafi wyjawić przypadkowemu użytkownikowi dość dużo informacji na temat wnętrza programu.
Informacja o błędzie wygenerowana przez serwer Tomcat. W tym wypadku żądana strona nie istnieje. Czasami można w tym miejscu zobaczyć ciekawszy materiał, czyli stack trace.
Tego typu informacje przydają się do debugowania aplikacji na etapie jej powstawania, ale użytkownik końcowy nie powinien ich oglądać (chociażby z powodów estetycznych).
Jak sobie z tym poradzić? Framework Spring Boot automatycznie ustawia dla nas nieco tylko ładniejszą stronę błędu:
Whitelabel Error Page, czyli strona błędu generowana przez Spring Boot
Mechanizm ten można wyłączyć we właściwościach aplikacji (application.properties):
1
server.error.whitelabel.enabled=false
Także zmiana domyślnego ViewResolver może usunąć tę wersję informacji o błędzie i przywrócić wersję tomcatową.
Jeśli zamiast strony prezentowanej przez Springa chcesz ustawić swoją własną, musisz zerknąć do dokumentacji wybranego przez Ciebie silnika szablonów. W przypadku Thymeleaf wystarczy dodać szablon o nazwie error.html:
Szablon błędu dla silnika Thymeleaf
Dzięki temu strona błędu może wyglądać na przykład tak:
Czy jest ładniejsza… Pozostawiam ocenie Czytelnika 😀
Pivotal to firma z, oczywiście, San Francisco. Jeden z jej produktów to chmura i oprogramowanie Pivotal Cloud Foundry . Pivotal Web Services to ich instancja dostępna publicznie. Umożliwia łatwe wdrażanie aplikacji napisanych w następujących językach i frameworkach: Java, Grails, Play, Spring, Node.js, Ruby on Rails, Sinatra, Go.
Moja aplikacja jest napisana w Spring Boot. A więc do dzieła!
Jak wdrożyć aplikację?
Wchodzimy na stronę http://run.pivotal.io/. Wita nas przyjazny komunikat o 87 dolarach w prezencie od firmy na cele testowania.
Bierzemy!
Rejestrujemy się w serwisie. Musimy podać numer telefonu, żeby przepisać otrzymany kod.
Firma uprzejmie prowadzi nas za rękę. Teraz pora na instalację PWS CLI, czyli konsoli do zarządzania aplikacją. Po instalacji logujemy się w sposób pokazany na stronie.
----->Downloading Spring Auto Reconfiguration1.10.0_RELEASEfrom https://download.run.pivotal.io/auto-reconfiguration/auto-reconfiguration-1.10.0_RELEASE.jar
(found incache)
...
0of1instances running,1starting
1of1instances running
App started
OK
...
state since cpu memory disk details
#0 running 2016-04-19 12:42:35 PM 0.0% 880K of 512M 66.3M of 1G
Podglądamy efekt na stronie.
Wdrożone!
Uczymy się korzystać z konsoli tekstowej oraz z widoku www.
Moja konkursowa aplikacja właściwie nic jeszcze nie robi – ot, wyświetla kilka widoków. Dzisiaj przetestowałam nowy drobiażdżek: wyświetlanie innej wersji strony na urządzeniach mobilnych.
Jak to zrobić? W Spring Boot to naprawdę prosta sprawa. Wystarczy:
Dopisać poniższą linię w pliku resources/application.properties (utworzyć plik, jeśli nie istnieje), nadpisując tym samym domyślnie ustawienie właściwości.
Zapisać mobilne wersje widoków w katalogu mobile (lub innym, jeśli zmienisz domyślną wartość właściwości spring.mobile.devicedelegatingviewresolver.mobilePrefix)
Mobilne wersje widoków
Po takim przygotowaniu możemy już podziwiać stronę w wersji normalnej i mobilnej. W swojej wersji mobilnej na teraz dodałam tylko słówko “MOBILE”. Wygląda to tak:
Wersja mobilna strony
Urządzenie mobilne udałam przy użyciu dość badziewiastego dodatku do Firefoksa o nazwie User Agent Switcher.
UPDATE: Marcin S. podpowiedział mi, że w Chrome można udawać urządzenie mobilne bez potrzeby instalowania żadnych dodatków:
Przyznaję, że ten wpis jest nieco wymuszony. Zasady Daj się poznać nakładają na mnie obowiązek wrzucania dwóch postów konkursowych tygodniowo. Tymczasem, choć informatycznie działo się u mnie sporo, projekt konkursowy w tym tygodniu leżał odłogiem. Dlaczego? Pomijając już przygody z nadgarstkiem – rozpoczęłam studia podyplomowe (przetwarzanie danych – big data)! Wczoraj, na moim dawnym wydziale, bawiłam się SQL-em, dzisiaj R. O R mam nawet anegdotę, ale na razie nie wymyśliłam pretekstu, żeby podciągnąć ją pod temat konkursowy.
Jeśli chodzi o moją aplikację Szafbook: nadal gnębi mnie kwestia dat. Pisałam już o tym, że HTML5 udostępnia pole wejściowe input typu date. Niektóre przeglądarki (Chrome!) wyświetlają przy nim elegancki widżet do wyboru daty z kalendarza. Inne nadal pozwalają/nakazują użytkownikowi samodzielne wpisanie wartości.
Nie byłam pewna, co z tym zrobić. Nie chcę rozbijać daty urodzenia na trzy osobne pola – za bardzo podoba mi się rozwiązanie z Chrome. Nie chcę wykrywać przeglądarki i podejmować decyzji w zależności od niej. Tak długo, jak będzie to możliwe, chcę się trzymać z dala od JavaScriptu.
Co tymczasowo wymyśliłam? Własny sposób tworzenia obiektu reprezentującego datę na podstawie danych wprowadzonych przez użytkownika, w którym pozwalam użytkownikowi na nieco większą elastyczność niż trzymanie się jednego sztywnego formatu daty.
Kod i uwagi do niego wklejam poniżej. Przy okazji zauważyłam kilka nowych (dla mnie) rzeczy w Javie. Najbardziej spodobały mi się grupy nazwane w wyrażeniach regularnych, wprowadzone w Javie 7. Dzięki ich istnieniu mogę wyciągnąć rok, miesiąc i dzień z tekstu dopasowanego do jednego z dwóch różnych wyrażeń regularnych. Bez nazw miałabym z tym większy problem, ponieważ numery grup (których trzeba było używać wcześniej) nie zgadzałyby się ze sobą (rok to grupa 1 w pierwszym wzorcu i grupa 3 w drugim).
W ogóle lubię wyrażenia regularne! 🙂 Na poprzednim blogu pojawił się kiedyś wpis o pułapkach regeksowych w Javie. Chyba przypomnę go w przyszłym tygodniu.
"Could not parse date: "+text+". Please use "+DATE_FORMAT.toPattern());
}
}
}
...
}
8. Użycie mojego własnego kodu do zamiany wartości tekstowej na typ Calendar.
17 i 18: Wyrażenia regularne mają umożliwić akceptację dat takich jak: 2016-04-10, 2016.04.10, 04/10/2014. Każde z wyrażeń definiuje 3 grupy nazwane: year, month oraz day.
39-41: Odwołuję się do grup, które są obecne niezależnie od tego, który z dwóch wzorców zadziałał.
43: Dość lekkie testy poprawności daty dopasowanej do wzorca. Zajrzałam do implementacji kalendarza. Podanie nieistniejącego dnia miesiąca nie powinno spowodować rzucenia wyjątku. Jeśli użytkownik będzie uparcie twierdził, że urodził się 30 lutego… Mało mnie interesuje, jaką datę ustawi mu system.
45: Ustawiam odpowiednią datę w moim kalendarzu. Początkowo chciałam mieć w tym miejscu obiekt typu Date, ale zorientowałam się, że tworzenie instancji Date w oparciu o rok, miesiąc i dzień od dawna nie jest zalecane.
Na koniec pozdrawiam z Warszawy! Przyjechałam z całą rodziną na konferencję 4Developers.
Mój zalążek projektu kompiluję i buduję przy użyciu Mavena. Maven (ang. “spec”) to narzędzie do budowania projektów napisanych w Javie. Sam Maven także został napisany w tym języku. Maven pozwala w uporządkowany sposób zarządzać zagadnieniami takimi jak: kompilacja, testowanie, budowanie, wynajdowanie i pobieranie zależności, generowanie dokumentacji.
Po co mi Maven, skoro mogę zbudować projekt w moim IDE? Po pierwsze, IDE nie zawsze jest pod ręką i może nagle okazać się, że na komputerze kolegi nie da się zbudować pilnie poprawionej wersji kodu, gdyż brakuje pięciu bibliotek (a po ich pobraniu – pięciu kolejnych, wymaganych przez te pierwsze). Po drugie, jeśli kod ma być testowany, a nawet wdrażany automatycznie, to przecież potrzebna jest możliwość uruchomienia go z zewnątrz. Po trzecie wreszcie – Maven to cała masa udogodnień i gotowych rozwiązań.
Konfigurację projektu mavenowego umieszcza się w pliku pom.xml. Plik ten można napisać samemu, ale najczęściej jest on generowany przez jakiś inicjalizator. W moim wypadku był to wspomniany już wcześniej Spring Initializr, ale najczęściej szablon generuje sam Maven w oparciu o żądany “archetyp”. Poniższe wywołanie stworzy szkielet aplikacji webowej z odpowiednio zainicjalizowanym plikiem pom.xml:
modelVersion: informacja o tym, z jakiego modelu DOM (Document Object Model) korzysta dany pom.xml. DOM w Mavenie zmienia się bardzo rzadko.
groupId: unikatowy identyfikator organizacji bądź grupy, która stworzyła projekt; najlepiej oparty o URL.
artifactId: nazwa głównego artefaktu generowanego przez projekt.
packaging: rodzaj wynikowego archiwum (e.g. JAR, WAR, EAR, etc.).
version: wersja powstałego artefaktu. SNAPSHOT oznacza wersję roboczą, do której się nie przywiązujemy.
name: wyświetlana (np. w dokumentacji) nazwa projektu.
description: krótki opis projektu
parent: (nieobowiązkowy) nadrzędny pom.xml, w którym zawarte są różne domyślne ustawienia projektu (np. kodowanie, konfiguracja pluginów).
properties: właściwości projektu. Ja podaję kodowanie źródeł oraz wersję Javy.
dependencies: zależności, czyli biblioteki potrzebne do zbudowania projektu. Jeśli nie ma ich na dysku, zostaną pobrane z repozytorium Mavena. Oprócz id grupy i artefaktu najczęściej trzeba podać także wersję, ale w tym wypadku wersjami podstawowych bibliotek zarządza wspomniany przed chwilą rodzic.
build: tu można zdefiniować podstawowe informacje na temat procesu budowania, np. lokalizację zasobów, profile budowania, niezbędne pluginy i ich konfiguracja. Pluginy w Mavenie mogą wszystko: potrafią przeprowadzić statyczną analizę kodu, przeformatować pliki itp. Moja aplikacja korzysta w tej chwili z pluginu Spring Boot Maven plugin, który umożliwia tworzenie uruchamialnych plików z aplikacją webową
Gdzie Maven przechowuje biblioteki?
Jeśli w systemie brakuje wymaganych bibliotek, Maven pobiera je ze zdalnego repozytorium http://mvnrepository.com lub innych wskazanych repozytoriówi umieszcza w repozytorium lokalnym.
Domyślna lokalizacja lokalnego repozytorium Mavena w systemie Windows
Oto najważniejsze standardowe polecenia Mavena (kolejność nie jest przypadkowa):
validate: sprawdź, czy projekt jest skonfigurowany w sposób umożliwiający jego zbudowanie.
compile: skompiluj kod, jeśli zmienił się od poprzedniego razu
test: przetestuj kod (testy jednostkowe, niewymagające wdrożenia).
package: w oparciu o skompilowany kod utwórz wynikowe archiwum (np. JAR czy WAR).
install: zainstaluj archiwum w lokalnym repozytorium Mavena. Projekt stanie się wówczas dostępny jako zależność dla innych projektów.
deploy: wdróż!
clean: usuń wszystkie artefakty wygenerowane przez poprzednie buildy.
site: wygeneruj dokumentację (HTML w oparciu o Javadoc).
Jak to wygląda w moim projekcie? W oparciu o pom.xml powstanie plik szafbook-0.0.1-SNAPSHOT.jar. Mogę go uruchomić w konsoli – zawiera wbudowany serwer Tomcata, na którym odpali się moja aplikacja.
1
2
mvn package
java-jar target/szafbook-0.0.1-SNAPSHOT.jar
Maven w konsoli
Mavena można podpiąć pod wszystkie najważniejsze IDE. W Eclipse będzie to wyglądało tak:
Maven zintegrowany z Eclipse
W kolejnym wpisie postaram się odpowiedzieć (sobie) na pytanie, czy w przypadku mojego projektu warto porzucić Mavena na rzecz coraz popularniejszego systemu budowania Gradle.
Bonus: troubleshooting. Gdzie mój pom.xml?! Eclipse domyślnie wyświetla pom.xml w postaci formularza. Poniższa ilustracja pokazuje, gdzie kliknąć, żeby dostać się do wersji tekstowej.
Dopiero teraz, przy piątym wpisie w ramach konkursu Daj się poznać, osiągnęłam etap aplikacji “Witaj Świecie”. No cóż. W tym artykule opowiem krótko co przy tej okazji zrobiłam i co mnie zaskoczyło. Kod jest, oczywiście, dostępny w GitHubie.
Najważniejsze elementy w strukturze aplikacji to (tradycyjnie zresztą):
katalog src/main/java z kodem w języku Java (domyślnie wypełniany przez Spring Initializr jedną klasą o nazwie Application),
katalog src/test/java z testami jednostkowymi,
src/main/resources to miejsce na zasoby; podkatalog static ma zawierać pliki przetwarzane po stronie klienta (obrazki, javascript), a podkatalog templates szablony przetwarzane po stronie serwera,
plik pom.xml to informacje dla Mavena, którego wybrałam jako narzędzie do budowania mojego projektu; zawiera przede wszystkim listę potrzebnych mi bibliotek.
Hello World!
Aplikacja kompilowała się już w poprzedniej odsłonie, ale teraz zależało mi na jej uruchomieniu i wyświetleniu czegoś w przeglądarce. W oparciu o tutorial na stronie Springa powstał ten oto skomplikowany kontroler:
1
2
3
4
5
6
7
8
9
@RestController
publicclassHelloController{
@RequestMapping("/")
publicStringindex(){
return"Szafbook: work in progress!";
}
}
@RestController to adnotacja, która sprawi, że klasa będzie mogła obsługiwać żądania REST. Jej użycie jest równoważne zastosowaniu dwóch innych adnotacji: @Controller and @ResponseBody. W ten sposób tworzę kontroler, który zwraca dane tekstowe (a nie kod HTML).
@RequestMapping pozwala określić adres, pod jakim ma być dostępny kontroler. W naszym wypadku będzie to adres najwyższego rzędu.
Klasa uruchamiająca aplikację wygląda w najprostszej wersji tak:
Adnotacja @SpringBootApplication również grupuje kilka innych adnotacji. Być może najistotniejsza z nich to @ComponentScan. Dzięki niej pakiet, w którym umieściłam kod zostanie przeszukany pod kątem usług sieciowych – w ten sposób znaleziony zostanie HelloController.
Za radą tutoriala urozmaiciłam sobie efekt w konsoli dodając kod przeglądający ziarna (beans) dostępne w tzw. kontekście aplikacji.
System.out.println("Inspect the beans provided by Spring Boot:");
String[]beanNames=ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for(StringbeanName:beanNames){
System.out.println(beanName);
}
}
}
Jak to wygląda w przeglądarce? Zerknij na nagłówek tego wpisu.
Poniżej przedstawiam dane wypisane w konsoli. Zwróć uwagę, że aplikacja webowa jest uruchamiania w wierszu poleceń z pliku JAR, a nie z pliku WAR wgranego na serwer. Jak to możliwe? Otóż przy aktualnej konfiguracji, wynikowy plik JAR zawiera wbudowaną wersję serwera Tomcat. Wielkość szafbook-0.0.1-SNAPSHOT.jar to 33 MB…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java-jar szafbook-0.0.1-SNAPSHOT.jar
(...)
Inspect the beans provided by Spring Boot:
_halObjectMapper
_linkDiscovererRegistry
_relProvider
actuatorMetricReader
alpsController
alpsConverter
alpsJsonHttpMessageConverter
annotatedEventHandlerInvoker
annotationRelProvider
applicationContextIdFilter
auditEventRepository
auditListener
auditableBeanWrapperFactory
autoConfigurationReportEndpoint
backendIdConverterRegistry
backendIdHandlerMethodArgumentResolver
baseUri
basicErrorController
beanNameHandlerMapping
beanNameViewResolver
(...)
Głupie wtopy: co zrobiłam nie tak przy pierwszym podejściu
Problem 1: Aplikacja buduje się (przy użyciu Mavena) w Eclipse i nie buduje się na zewnątrz. Rozwiązanie: Instalacja w systemie Mavena 3 i przestawienie Eclipse na tę samą instalację (wcześniej Eclipse korzystał z instalacji wbudowanej, a w systemie miałam Mavena 2).
Problem 2: java.lang.UnsupportedClassVersionError Rozwiązanie: Zmiana wartości zmiennej JAVA_HOME w systemie na wersję Javy używaną przez projekt w Eclipse.
Problem 3: Aplikacja działa w przeglądarce, ale w konsoli mam “java.lang.IllegalStateException: Tomcat connector in failed state“. Rozwiązanie: Usunąć zduplikowane wywołanie SpringApplication.run 🙂 Sukcesem może zakończyć się tylko jedno z nich, potem port będzie już zajęty.
Co dalej?
Jeśli chodzi o rozwijanie projektu, to najbliższe prace poświęcone będą:
Ciągłej Integracji,
“M” w MVC, czyli modelowi danych.
Jeśli chodzi o blogowanie, to planuję wpisy bezpośrednio związane z rozwojem kodu, a dodatkowo:
git rebase, którego znaczenie wreszcie zrozumiałam,
Maven.
PS. Java Beans – ciekawostka lingwistyczna
W języku programowania Java istotną rolę pełnią tzw. “ziarenka”: JavaBeans oraz Enterprise JavaBeans. Dlaczego ziarenka? Otóż “Java” to w Stanach synonim słowa “kawa” (bardzo dużo kawy uprawiano na wyspie Jawie). Stąd pomysł, żeby najmniejsze samodzielne składowe aplikacji określić mianem “ziaren kawy”.
Oficjalne logo języka Java – parujący kubek z kawą
Tekst jest oparty na mojej prezentacji z konferencji Kariera IT. Pierwsza część wpisu jest dostępna tutaj.
Świat staje się coraz mniejszy. Wszyscy uczymy się angielskiego, a lokalne dialekty odchodzą w zapomnienie. Dlaczego wciąż upieramy się, by z komputerem rozmawiać na tyle różnych sposobów? Ile języków powinien znać programista? Od którego zacząć?
To druga część wpisu na temat współczesnych języków programowania i różnic pomiędzy nimi (pierwsza jest dostępna tutaj). W tej części mówię o różnicy pomiędzy językami kompilowanymi i interpretowanymi i tłumaczę, czym są maszyny wirtualne. Na końcu pokazuję, że języki programowania – tak samo jak języki, za pomocą których ludzie komunikują się między sobą – żyją i zmieniają się z czasem.
Kompilować czy interpretować?
Kolejny podział języków programowania opiera się na sposobie przetwarzania napisanego w nich kodu. Języki bywają dzielone na:
kompilowane, czyli przed uruchomieniem tłumaczone na kod maszynowy właściwy danej architekturze, zapisywany w osobnym pliku, np. C++, Pascal,
interpretowane, czyli uruchamiane bezpośrednio w oparciu o kod źródłowy, często wyposażone w interaktywne konsole, np. Perl, Ruby,
interpretowane przez maszynę wirtualną, tj. kompilowane do kodu bajtowego, jak Java, C#.
Warto wiedzieć:
Ten sam język, w zależności od środowiska, może być kompilowany lub uruchamiany.
Potrzeba kompilacji jest nie tyle cechą języka, co środowiska, z którego korzystamy, choć w praktyce konkretne języki są z reguły konsekwentnie przetwarzane w ten sam sposób.
Oba podejścia mają swoje zalety. Kod skompilowany do postaci maszynowej będzie działał optymalnie w architekturze, dla której jest przeznaczony. Kompilator bywa sprzymierzeńcem programisty – potrafi wytknąć błędy (czasami dość złożone), zanim jeszcze uruchomisz program. Z kolei w środowisku interpretowanym (w którym de facto kolejne instrukcje są kompilowane „w locie”) możesz pozwolić sobie na więcej improwizacji i beztroskich prób bez konieczności tworzenia kompletnego, zamkniętego programu.
Skompilowana wersja tego samego programu będzie wyglądała inaczej w systemie o innej architekturze. Program interpretowany wygląda tak samo niezależnie od tego, gdzie chcemy go uruchomić (o ile celowo nie odwołujemy się elementów istniejących jedynie w konkretnym systemie operacyjnym) – to środowisko (interpreter) musi być w stanie porozumieć się z procesorem.
Jeśli popełnisz poważny błąd w linii numer 100, kompilator w ogóle nie utworzy wykonywalnej wersji programu. Środowisko interpretowane prawdopodobnie bez szemrania wykona linie 1-99 i elegancko wyłoży się na linii 100. Zdecyduj sam, która opcja jest lepsza?
Kompromis pomiędzy wspomnianymi dwoma podejściami stanowią maszyny wirtualne.
Maszyny wirtualne
Poniższy rysunek przedstawia schemat działania maszyny wirtualnej (ang. Virtual Machine, VM).
Zasada działania maszyny wirtualnej (na przykładzie JVM)
W przypadku języków z maszyną wirtualną (jak Java, C#) kod jest co prawda kompilowany, ale nie do postaci kodu maszynowego, tylko tzw. kodu bajtowego (ang. bytecode), przeznaczonego dla VM. Dzięki temu skompilowany kod zawsze wygląda tak samo i może być wdrażany w różnych systemach. Maszyna wirtualna właściwa danej architekturze (czyli np. działająca w systemie Windows) jest w stanie w locie tłumaczyć kod bajtowy do postaci kodu maszynowego – instrukcji dla procesora.
Warto wspomnieć, że niektóre maszyny wirtualne (np. JVM, czyli Maszyna Wirtualna Javy) są obecnie tak dojrzałe i wydajne, że twórcy nowych języków (jak w tym wypadku Scala, Clojure) decydują się na kompilowanie tych języków do kodu bajtowego, by skorzystać z optymalizacji wprowadzanych przez daną maszynę wirtualną.
Wydajność języka a wydajność programisty
Być może zwróciłeś uwagę na to, że kilka razy w tym wpisie pojawiły się słowa „wydajny” lub „optymalny”. Języki programowania można porównywać i w ten sposób. Ruby (czytaj: kod napisany w Ruby, uruchomiony w odpowiednim środowisku) działa wolniej niż Java, Java jest wolniejsza od C.
Skoro tak, to dlaczego więc wszyscy nie piszemy w C? Nie wolno zapomnieć o tym, że wydajności języka i wydajność programisty to dwie różne rzeczy.
Program powinien działać szybko, to jasne. Programy są jednak pisane i poprawiane przez ludzi. Ogromne znaczenie mają więc także:
czas potrzebny do stworzenia działającego programu,
czas potrzebny do naprawienia błędu przez autora kodu,
czas potrzebny do naprawienia błędu przez innego programistę.
Jeśli chodzi o szybkość tworzenia programu od zera (1) – w ekstremalnej sytuacji prototyp tworzy się w jednym języku, a – jeśli pomysł chwyci – później w tle przepisuje się kod na inny, wydajniejszy język.
W kwestii naprawiania błędów (2), ale też szybkości kodowania, rozważ proszę następujące dwa fragmenty kodu.
C++
C++
1
2
3
4
5
int*x=newint[10];
// tablica 10 elementów typu int
// ... (operacje)
// pora zwolnić pamięć
delete[]x;
Java
Java
Java
1
2
3
4
5
int[]x=newint[10];
// tablica 10 elementów typu int
// ... (operacje)
// nie musimy zwalniać pamięci, zajmie się tym Odśmiecacz (Garbage Collector)
// ... ale nie wiadomo kiedy
Oba przykłady robią to samo: tworzą tablicę przechowującą 10 elementów typu całkowitego. W przypadku C++ musisz ręcznie zwolnić przydzieloną pamięć. Można uznać to za zaletę – masz pełną kontrolę nad pamięcią. Jeśli wiesz, że obiekt nie będzie już potrzebny, możesz się go natychmiast pozbyć. W Javie pamięć zwolni Odśmiecacz (ang. Garbage Collector) po wykryciu, że do zmiennej nie ma już odwołań. Tyle tylko, że zrobi to, kiedy będzie mu wygodnie… Może wcale.
Z ogromną dozą pewności pozwolę sobie napisać to:
Każdemu programiście języka C++ zdarzyło się co najmniej raz zapomnieć operatora delete.
Przykłady kodu są zabawkowe. Gdyby jednak taki kod znajdował się w pętli, gdyby tablica zawierała obiekty zamiast wartości int – mielibyśmy poważny problem.
Co do poprawiania błędów przez innych developerów (3): znana maksyma głosi, że kod jest czytany dużo częściej niż pisany. Warto zadbać o jego przejrzystość i strukturę. To, czy kod jest czytelny czy nie, nie zależy jedynie od wyboru języka, ale także od stosowania konwencji (nazw, formatu kodu) i abstrakcji (jak wzorce projektowe).
Języki programowania ewoluują!
Wiesz już (niekoniecznie ode mnie), że to, czy język się kompiluje czy interpretuje nie jest rdzenną cechą samego języka, ale środowiska, w którym ma działać Twój kod.
Wiesz, co jeszcze może ulegać zmianom? Sama składnia!
Język naturalny, czyli język, którym ludzie porozumiewają się między sobą, ewoluuje. Pojawiają się nowe słowa, stare zyskują nowe znaczenia. Uparcie powtarzane błędy w końcu wchodzą do kanonu (czy zastanawiałeś się kiedyś, dlaczego w wielu językach najczęściej używane czasowniki odmieniają się nieregularnie?)
Nie inaczej jest w przypadku języków programowania. W nowych wydaniach pojawiają się konstrukcje pozwalające w bardziej zwięzły sposób wyrazić te same treści. Dopuszczane są elementy innych paradygmatów (np. popularne ostatnio wyrażenia lambda, związane z programowaniem funkcyjnym).
Obie wersje tworzą mapę (tablicę asocjacyjną), w której kluczami są łańcuchy znaków, a wartościami – listy zawierające łańcuchy znaków. Czyli, na przykład, na podstawie nazwiska mogę pobrać listę numerów telefonu przypisanych do danej osoby.
Druga, nowsza wersja jest wyraźnie krótsza. Dlaczego? Otóż dlatego, że programiści Javy od lat dostawali białej gorączki przez ostrzeżenia kompilatora na temat nieznanego typu podczas tworzenia kolekcji, mimo że typ można było jednoznacznie wywnioskować na podstawie deklaracji zmiennej. Ich skargi wreszcie dotarły gdzie trzeba, i oto efekt.
Dlaczego podałam numery wersji od 5 w górę? Ponieważ w wersji 4 nie można jeszcze było stosować uogólnień (ang. generics) – w efekcie nie mogłabym w ten sposób określić typu danych przechowywanych w kolekcji.