Nešťastná designová rozhodnutí Javy

Java letos oslaví 20 let, a to mi dává dobrou příležitost bilancovat.

Java je ohromně úspěšný jazyk, který svého času způsobil revoluci v programování, ale s pokračujícími léty nejenže ztratil dech, ale zakomponoval do sebe i řadu nešťastných až vyloženě špatných rozhodnutí. Mezi takové nejvíce kritizované vlastnosti patří:

  • genericita implementovaná jako type erasue
  • absence přetěžování operátorů (alespoň ==)
  • chybí property

O nich bylo ale napsáno až dost. Já bych chtěl zmínit některé (zdaleka ne všechny) méně známé, méně zřejmé, ale stejně nepříjemné aspekty návrhu Javy. Budu se snažit vysvětlit, proč je považuji za chyby, ačkoliv stále se jedná jen o můj osobní názor.1

1. Checked exceptions

Kromě toho že se jedná o limitující koncept, kvůli kterému se musí psát spousta skutečně zbytečného error-check kódu2, tak především rozbíjí dědičnost a nutí programátora dělat error handling na obvykle nevhodném místě.3 Navíc obtěžuje při ladění, jelikož Java generuje chybu místo varování, když se chytá checked exception tam, kde nemůže nastat.
Příklad: Dědičnost rozbíjí v případě že chci implementovat metodu, která může vyhazovat i jinou výjimku, než jakou uvádí interface.

interface MyInterface {
    void MyMethod() throws MyException;
}
class MyImplementation implements MyInterface {
    public void MyMethod() throws MyException, MyOtherException //Error
    { ... }
}

2. Inner classes a static nested classes

Upřímně, kdo opravdu znáte rozdíly mezi nimi? A ti z vás co ano, kdo jich prakticky využíváte? A kdo i na to odpověděl kladně – doufám, že se taky upřímně stydí vždy, když napíše inner classu, protože je to z OOP hlediska katastrofa.4

3. Unsigned types

Neexistují. Protože „Unsigned aritmetika je příliš složitá“5. Jako vážně?
To že byte je signed typ způsobuje haldu problémů při interoperabilitě s databázemi, webovými službami a vůbec všemožnými standardy.

4. Tuple a Func classes

Ve standardní knihovně nejsou generické třídy pro různě dlouhé tuply ani lambda funkce.
Takže je potřeba buď natahat závislosti na Apachí/Googlí balíčky, nebo na každou hloupost psát vlastní přepravky a funkční třídy.

5. Balíčkové a file naming konvence

Bohužel nejsou jen konvence, ale součástí specifikace jazyka. Přitom neexistuje důvod, proč by měl jazyk vynucovat uspořádání podle své bytecode formy – o to se má starat kompilátor. Důsledkem je spousta hloupých omezení:

  • Nelze vytvořit stejnojmennou generickou třídu s různě dlouhými sadami typových parametrů. Například Tuple<T1,T2> a Tuple<T1,T2,T3>. (Ačkoliv za to může i type erasure.)
  • Dalším důsledkem je directory clutter – kde by víc souvisejících malých tříd šlo dát do jednoho souboru (například Exceptiony) a nebo nested class overuse (které jsou problém samy o sobě, viz výše).

6. Programování proti rozhraní a interfaces v Javě vůbec

Osobně zastávám názor, že metody by měly přijímat co nejobecnější typy a vracet co nejkonkrétnější. Ale tím to končí.
Nemá žádný smysl vyrábět ke každé třídě rozhraní, když vím že 90% z nich (a vím i jakých 90%) nebude nikdy mít jinou implementaci.
Ještě absurdnější je situace u lokálních proměnných. Kde je kód List<String> myList = new ArrayList<String>(); běžný a kdybychom se ho chtěli křečovitě držet tak Javě zamyká cestu k typové inferenci.

S interfacy se pojí řada dalších nepříjemností:

  • Java nemá zvláštní konvence pro pojmenovávání rozhraní a tříd, takže na první pohled nelze poznat, co je co. Dobrým zvykem v jiných jazycích bývá prefixovat íčkem, např. IMojeRozhranní.
  • Java má různá klíčová slova pro implementaci rozhraní (implements) a tříd (extends). To je problematické při refactoringu mezi rozhraními a abstraktními třídami.
  • Java se neumí vypořádat s „nekompatibilními rozhraními“

7. Stream API (Java 8)

Nedávno vyšla Java 8 , která obsahuje dlouho očekávané API pro oblíbené higher-order metody nad kolekcemi. Problém je že působí jako early beta.

  • Metody nejsou přístupné přímo přes List / Array objekty ale je nutné nejprve zavolat myList.toStream() respektive Arrays.stream(myArray).
  • Nad streamem nejde for-eachovat: for (Object item : myStream) (Použití .forEach() metody je suboptimální.)
  • Tak typický use-case jako převedení Streamu na List se provádí odpornou syntaxí .collect(Collectors.toList())

8. Default methods (Java 8)

Defaultní metody jsou jen slabou náhražkou opravdových extension metod. Limitují jejich flexibilitu a přinášejí nové potíže:

  • Přidání defaultní metody na rozhraní může ošklivým způsobem rozbít její implementace.
  • Po přetížení defaultní metody v implementaci už neexistuje cesta jak původní verzi zavolat.

9. Javadoc

Jako bonus na závěr mám ještě připravenou kritiku Javadocu. Zdálo by se že v dokumentaci nejde udělat chybu, že se jedná pouze o otázku vkusu. Bohužel to fundamentální rozhodnutí zda ji skládat z jednořádkových, nebo blokových komentářů má víc důsledků, než si tvůrci Javy zřejmě uvědomili.
Příklad: Představte si že mám třídu s řadou metod opatřených Javadocem. A teď bych rád několik z nich zakomentoval, protože chci vyzkoušet, jak se chovají, kdybych zachoval implementaci z předka. A hle – jelikož Java má na Javadoc blokové komentáře, ale neumožňuje jejich vnořování, musím každou z metod zakomentovat zvlášť.

Závěr

Závěrem, abych nebyl tak negativní, musím přiznat že Java má i některé dobře vymyšlené vlastnosti. Chválu zaslouží minimálně kompaktní a vcelku výkonný bytecode. Takže přinejhorším můžeme všichni přejít na Scalu a užívat si moderní programovací jazyk beze změny běhového prostředí. 🙂

A ať už se mnou souhlasíte nebo ne, budu rád za připomínky v komentářích.


1 A vzhledem k tomu, že se najdou i programátoři, kteří považují přetěžování operátorů za matoucí a kód znepřehledňující, tak jedna pravda ani neexistuje.

2 Připomíná mi to situaci, v jaké se kdysi nacházel Multics a dočtete se o ní v článku Unix and Multix: I remarked to Dennis [Dennis Ritchie, tvůrce C a Unixu, pozn. autora] that easily half the code I was writing in Multics was error recovery code. He said, „We left all that stuff out. If there’s an error, we have this routine called panic, and when it is called, the machine crashes, and you holler down the hall, ‚Hey, reboot it.'“ Tak schválně, jak velká část vašeho Java kódu je error handling?

3 Checked exceptions mají také poměrně silný tábor odpůrců. Srozumitelné a stručné vysvětlení, proč nejsou v C# jsem našel v rozhovoru s Andersem Hejlsbergem, podle kterého způsobují problémy se škálovatelností a verzováním.

4 Zkuste na chvíli zapomenout vše, co víte o vnořených třídách v jakémkoliv jazyce, a zamyslete se, jak by skutečně měly fungovat. Zjistíte, že vám stačí jeden typ – takový, který stejně jako inner class vidí na všechny membery třídy, do které je vnořený, ale zároveň podobně jako static nested class není navázán na instanci – ta se mu v případě potřeby může předat jako parametr konstruktoru (což je i velký plus pro srozumitelnost kódu).

5 Volně cituji Jamese Goslinga, tvůrce Javy, z velmi zajímavého trojinterview o C, C++ a Javě.

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Google photo

Komentujete pomocí vašeho Google účtu. Odhlásit /  Změnit )

Twitter picture

Komentujete pomocí vašeho Twitter účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s