Ostatnio zdarzyło mi się wdać w jałową dyskusję na temat wyższości C# nad Javą. Padł tam koronny argument fanboya Javy, pt. “nie masz pojęcia o czym mówisz, nie używasz Javy”. Otóż, prawie. Od kilku miesięcy pracuję jako programista tego pokracznego języka. Postanowiłem wreszcie wylać swoje żale na wasze ekrany.

Gettery i settery

Java, WTF? Rozumiem konwencję utrzymywania enkapsulacji pól i udostępniania/modyfikacji danych przez funkcje. Moje pytanie brzmi: skoro jest to praktycznie obowiązkowa część korzystania z języka i każdy framework wymaga stosowania się do tej zasady, to dlaczego nie można było tego zmieścić do jednej linii? Zarządzanie kodem udekorowanym getterami i setterami to piekło. Brak sensownego rozwiązania problemu getterów i setterów to tylko wierzchołek góry lodowej, jakim jest…

Brak lukrów składniowych

Operujesz na kolekcji. Chcesz dostać jej pierwszy element. Co robisz? Oczywiście, wywołujesz metodę, zamiast zastosować stosowany w tablicach operator []. Przyczyna jest prosta: nie można przeciążać operatorów. Właściwie wszystko co robisz musi być opisane słowem, nazwą metody.

Takich przykładów jest mnóstwo. Aż dziw bierze, że ciągi znaków konkatenujemy operatorem +, zamiast .append().

Deklarowanie rzucanych wyjątków

Java wymaga zadeklarowania jakie wyjątki mogą zostać rzucone przez metodę. Wydaje się przydatne, prawda? Korzystając z danej metody, wiesz czego się spodziewać. Problem polega na tym, że kod jest organizmem żywym i zdarza się, że metoda, której używasz, zaczyna rzucać więcej wyjątków. Co się wtedy stanie? Ano dupa, Twój kod przestał się właśnie kompilować. Musisz obsłużyć sytuację albo dodać wyjątek do listy throws.

Ok, załóżmy nawet, że API z którego korzystasz się nie zmienia. W Javie istnieje coś takiego, jak RuntimeException. Wyjątek ten, jak i jego potomstwo nie muszą być zdefiniowane jako rzucane przez metodę. W ten sposób otrzymano konstrukcję językową, która nie daje żadnej pewnej informacji. Przynosi w zamian za to mnóstwo problemów z utrzymaniem kodu.

Zarządzanie zależnościami

Napisałeś własną bibliotekę. Brawo! Korzystasz w niej z innej biblioteki. Dla uproszczenia, nazwijmy Twoją bibliotekę BadassLib, a bibliotekę od której zależysz - HaxxorLib. Pisząc swój kod korzystałeś z HaxxorLib w wersji 2.1.0. W wersji 2.2.0 usunięta została jedna z metod, na której polegałeś. Przygotowałeś konfigurację Mavena (taki javowy NuGet + msbuild), w której jasno określiłeś, że BadassLib wymaga HaxxorLib w wersji dokładnie 2.1.0. I co? Panu Ryszardowi spodobała się Twoja biblioteka i postanowił jej użyć. Niestety, w swoim projekcie nie korzysta z Mavena. Dlaczego nie korzysta z Mavena? Powodów może być wiele, nie chcę się nad nimi rozwodzić. Istotne jest to, w jaki sposób Pan Ryszard rozwiąże problem zależności BadassLib. Otóż doda on do classpatha ręcznie pobrany HaxxorLib. Zgadnij w jakiej wersji HaxxorLib była do pobrania ze strony biblioteki :-) Miłego debugowania, Panie Ryszardzie.

Źródłem tego problemu jest beznadziejny system zarządzania zależnościami w środowisku Java. Decyzja o wykorzystaniu zależności podejmowana jest na podstawie classpatha, a nie wymagań konkretnego modułu. Z tego powodu otrzymujemy piękne zjawisko, jakim jest Jar Hell. Problem ma zostać rozwiązany przez projekt Jigsaw, jednak na to przyjdzie nam jeszcze długo poczekać.

Podsumowanie

Nie chodzi o to, że język Java do niczego się nie nadaje. Po prostu jego składnia jest toporna i nie sprzyja czytaniu kodu, a zarządzanie zależnościami pozwala na nadużycia i prowadzi do trudnych do wykrycia błędów. Do tego dochodzi problem z niespójnością koncepcji. Możesz używać +operatora +_ na obiektach String, ale nie możesz ich wykorzystać w swojej klasie. Musisz stosować gettery i settery, ale nie możesz mieć ich skróconych do jednej linii. Same sprzeczności