Přejít k navigační liště

Zdroják » Různé » Remcání proti Javě

Remcání proti Javě

Články Různé

Po 3⁄4 roce práce v Golangu jsem se vrátil na chvíli k Javě. Doufám, že jen dočasně. Protože, když člověk na dostatečně dlouhý čas vypadne z prostředí, kde dlouhodobě pobýval, a vrátí se zpět, najednou mu dojde, jak jsou některé věci obskurní. A jak některé věci, které byly vymyšleny s nejlepšími úmysly pomáhat, ve skutečnosti práci zatemňují a ztěžují. Je to, jako když žijete v prasečáku a už si pak neuvědomujete, že prasata smrdí.

Nálepky:

Text vyšel původně na autorově webu.

Nekonečné přelévání dat mezi vrstvami

Určitě to znáte — pokud jste v posledních 10 letech dělali nějakou “klasickou” webovou aplikaci v Javě, je všechno rozděleno do vrstev a mezi těmi vrstvami si předáváte data. Data, která přijdou z webové služby nebo z GUI, následně přechroustáte v business logice a uložíte.

Na každou z těch vrtev nejspíš používáte nějaký framework a ten framework… očekává data v určitém formátu/struktuře (typicky POJO, protože OOP, že jo), nebo s určitými metadaty (anotační rakovina).

Takže ty pořád stejný hodnoty (nejspíš nějaký String, nebo int) posíláte kaskádou vrstev tam a zpátky. K tomu je nejspíš(?) pořád(?) validujete a ošetřujete chyby. (Nebo na to kašlete?). A to všechno jen proto, že vám to diktuje nějaký framework nebo design pattern.

Mimochodem, pokud jste se nad tím někdy zamýšleli, dobrá polovina návrhových vzorů existuje jenom proto, aby řešila problémy objektového programování. Existují jazyky (třeba Ruby  nebo Clojure), kde většina návrhových vzorů nedává smysl.

Proliferace modelových tříd

S výše zmíněným problémem souvisí, že všechno to přelévání dat mezi vrstvami musíte realizovat pomocí modelových tříd, které… nejen že na sebe “nesedí” (tuhle jeden field chybí, támhle druhý přebývá), ale jsou inherentně jiného typu. Takže vezmete do ruky lopatu a pěkně ručně, nebo nějakým mapovacím frameworkem ty data přehážete z jednoho modelu do druhého. A to nemluvím o tom, že často ty třídy vracíte v nějaké kolekci.

Proč je problém, že jsou modelové třídy různého typu? Občas se to tak pěkně sejde, že máte dvě takovéhle třídy:

public class HistoryRecord  {
    @JsonProperty("opcRequestID")
    private String id;
    @JsonProperty("namespaceName")
    private String namespaceName;
    @JsonProperty("bucketName")
    private String bucketName;
    // Rest of the class is ommitted.
}

@KievEntity
public class ObjectSelectHistoryEntity {
    @HashKey
    @Column(type = ColumnType.STRING, length = 255)
    private String id;
    @Column(type = ColumnType.STRING, length = 255)
    private String namespaceName;
    @Column(type = ColumnType.STRING, length = 255)
    private String bucketName;
    // Rest of the class is ommitted.
}

Všimněte si, že obě třídy obsahují naprosto identické členy — jak názvem, tak typem. Je vám to k něčemu? Ne. Prostě vezmete do ruky lopatu… Smutné je, že většinou nad těmi hodnotami nepotřebujete dělat žádné transformace, chcete jenom dostat data od uživatele do databáze.

Proč k tomu dochází? Na jedné straně máte dejme tomu SwaggerJAX-RS apod. a na druhém konci je nějaká (No)SQL perzistence, často zastřešená ORM abstrakcí. Takže HibernateSpring Data atd.

Všechny tyhle věci vám generujou, nebo očekávají… hádejte co? Ano, modelové třídy. A do těch modelových tříd generujou, nebo v nich očekávají… hádejte co? Ano, metadata (tj. anotace). A od vás, jako programátora, očekávají… hádejte co? Ano, že vezmete do ruky lopatu a…

Starý dobrý Maven

Řeknu vám, že jestli jsem na něčem poslední roky tvrdě pracoval, tak na tom, aby bylo jasné, že jsem Maven kverulant. Já si prostě nemůžu pomoct — vždycky, když vidím nějakého spokojeného uživatele Mavenu, tak si musím (přátelsky) rýpnout.

Protože, upřímně soudruzi, který buildovací, nebo automatizační nástroj umožňuje, ba pobízí uživatele k takovémuto projektovému designu:

Je to tak: na 99 Java souborů (z nichž tak třetina až polovina je výše zmíněný OOP balast) je potřeba 23 Maven konfiguráků! Či jinými slovy, 17 Maven modulů produkujících JAR. Ani vám nebudu ukazovat projektovou hierarchii, protože kdybych si vyjel:

tree -P pom.xml -I 'src|target'

tak se mi to nevejde na obrazovku notebooku. Jen vám řeknu, že ta hierarchie je tříúrovňová.

No, a kdybych se ještě pustil do toho, jak skvěle se tuní Maven pluginy, tak bychom tady byli ještě zítra. Jenže to už by nebylo remcání… to by byla svatá válka!

Kam ten svět spěje?

Řekl jsem dneska něco pozitivního? Ne, neřekl. Ale to neznamená, že bych Javu neměl celkem rád — za těch 12 let, co jsme spolu zatím strávili, by bylo divné, kdybychom se čas od času nepoškorpili. Když pominu svoji ženu, tak Java je vlastně jeden z nejdelších vztahů, který jsem kdy měl. 😈 Za tu dobu, už člověk ztratil iluze a ví, jak to v životě chodí. A naučí se být trochu tolerantní.

Komentáře

Subscribe
Upozornit na
guest
24 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Vít Heřman

Tak jen tiše závidím možnost pracovat v Golang. Jako hračku jsem si ho již před časem také naučil a teď jsem přidal F#. A najednou člověk posuzuje všechny ty OOP patterny a frameworky úplně z jiné perspektivy. Jakoby jejich zásadním účelem bylo zamotat lidem hlavy a spoustu věcí zamlžit a zesložitit. Takže jsem rád, že neremcám sám :-)

tmysik

…autor nepřidal aspoň minimální ochutnávku toho, jak je to v Go jinak/lepší. Jasně, můžu si to zkusit najít, ale tohle mi v článku tak nějak chybí (spolu s případným komentářem autora).

Tobiáš Potoček

Se zájmem jsem si přečetl tento článek, i související článek na autorově blogu. Přijde mi, že výtky se netýkají jazyka takového, ale spíše prostředí, nástrojů a návyků, které časem vznikly kolem. Tedy toho, jak tak nějak realisticky vypadá vývoj v Javě.

Musí ale tak vypadat? Nikdo nás přeci nenutí nadužívat objektovost Javy a vytvářet spoustu vrstev, když to nemá praktický význam. Není možné v Javě psát prakticky ten samý kód jako v Golang? Chybějící funkcionalita se dá dodat malými knihovnami a co zbude je syntaktický cukřík. Je v tom ten rozdíl, které vede k dlouhodobě kvalitnějšímu kódu a souvisejícímu ekosystému?

Mám např. dojem (nicméně přiznávám, že konkrétní zkušenosti s Golang nemám), že oba jazyky řeší error handling koncepčně velmi podobně. Golangovské panic odpovídá unchecked výjimkám, druhá vracená hodnota obsahující chybový status zase chedked výjimkám (jsou i součástí method signature). Rozdíl je skutečně pouze v syntaxi, jakou jazyk daný problém řeší. Dovedu si představit, že golangovský přístup je explicitnější (panika…) a tedy spíše povede programátory k dobrým návykům, na tu druhou stranu, javovský způsobem mi osobně přijde pohodlnější.

Vít Heřman

Autoři Golang by se asi bránili přirovnání výjimkám v Javě. Myslím, že hlavním jejich argumentem proti výjimkám je jejich podstata nestrukturovaného skoku (podobně jako goto). Error handling je proto realizován pomocí obyčejných návratových hodnot, což se liší od checked výjimek ve dvou věcech: a) nedochází ke skoku na stacku b) ošetření není vynucováno. S tím přirovnáním panic bych pak asi souhlasil.

Jinak mám zkušenost, že podoba jazyka předurčuje, jak vypadají frameworky a ekosystém. Ale když píšete, že Vás nikdo nenutí, tak je to spíše o tom, kde pracujete a zda máte štěstí na projekty, kde Vás nikdo nenutí. Protože až příliš často nutí. Třeba přebíráte práci po někom jiném, firemní standardy, atd. Já mám pocit, že mne všude nutí přizpůsobovat se velkým frameworkům a patternům, přestože vím, že sám dokážu dosáhnout výsledku efektivnějším způsobem a dodat řešení lepší kvality

balki

jinak mám zkušenost, že podoba jazyka předurčuje, jak vypadají frameworky a ekosystém

Nemal by mat scheme (lisp) potom najlepsi ekosystem na svete? Bude este programovanie v golang take sexi, ked sa nan nalepia 3 dekady balastu? C je tiez este stale tak sexi, ako bolo na zaciatku?

To boli recnicke otazky, prosim nedpovedajte.

Vít Heřman

Přečetl jsem pozorně, důvody vysvětluje skvěle. Teoretická námitka by mohla spočívat jen v tom, že při neošetření návratové hodnoty může program pokračovat dále, avšak s chybnými daty. Avšak ošetření chyb je tak geniálně jemným způsobem vynuceno tím, že kompilátor a) vynutí přiřadit si obě návratové hodnoty b) nenechat ani jednu z nich nevyužitou. Obojí založeno na principu, že deklarovaná a nepoužitá hodnota je chyba. Lze jemně potlačit pomocí _. Tomu říkám elegance.

Tobiáš Potoček

Hmmm články podobného typu nemám moc rád, protože už ten samotný název, Why Go gets exceptions right, se snaží tak trochu navodit dojem, že tady existuje jakýsi jednoznačně nadřazený způsob a že s ním přišlo Go. Že realita je trochu někde jinde dokazuje už ten samotný článek svým průřezem historií. Jazyk C (kterým článek začíná) a jazyk Go (kterým článek končí) řeší signalizování chyby velmi podobně, tedy skrz více návratových hodnot, jen jeden k tomu „zneužívá“ pointery a druhý má pro to nativní podporu. Jinými slovy, skončili jsme tam, kde jsme začali, jen s jemným iterativním zlepšením. Nedivil bych se, kdybychom za rok četli článek Why XYZ gets exceptions right, a základní premisou bude jemná iterace nad javovskými výjimkami :)

Když už mluvíme o těch javovských výjimkách, tak článek zmiňuje dva základní problémy, které je nutno napravit:

  1. Výjimky už nejsou používány výjimečně
  2. Výjimky potenciálně vedou k zhůvěřilostem typu catch (e Exception) { // ignore }

U toho prvního bodu mi není jasné, zda je to myšleno ryze akademicky / principiálně, či jsou tam nějaké skutečně praktické důsledky, např. ve výkonu či v dlouhodobé (ne)kvalitě produkovaného kódu. Signalizovat chybu nicméně není nijak výjimečná situace a výjimky jsou z mnoha pohledů elegantní způsob, tak proč bychom ho neměli používat? Obecně mám pocit (což se týká i toho druhého bodu), že ač dobrý návrh jazyka by samozřejmě měl sám o sobě vést ke kvalitnímu kódu a správným návykům, tak by to neměl dělat příliš na úkor pohodlí. Máme přeci k dispozici i jiné nástroje (best practices, conventions, code review…), jak se vyhnout zhůvěřilostem.

V jednom komentáři výše je zmíněno, že ošetření výjimek není vynucováno. To u checked výjimek (které koncepčně odpovídají vraceným chybám v Go) není tak úplně pravda. Kompilátor vás donutí daný kód buď obalit try-catch blokem (pokud chybu můžete ošetřit na daném místě) či aspoň deklarovat výjimku pomocí throws v signatuře metody („probublání“ chyby). Ono „probublání“ ukazuje ten rozdíl mezi Go a Javou. Občas prostě „probublání“ je jediná věc, co se v daném místě kódu dá dělat, a Go vás v takovém případě donutí napsat 3 řádky boilerplate kódu. Java ne (to je to pohodlí). Neodvážím se tvrdit, který přístup je lepší, ale osobně preferuji Javu.

Tobiáš Potoček

Vyjdu-li z Vašeho language ➡️ frameworks ➡️ platform ➡️ landscape, tak Vy jste v podstatě celý řetězec vyresetoval a nahradil už jeho první článek, tj. jazyk. Nicméně Váš článek se vyhrazuje pouze vůči těm následujícím tří článkům. Takže svým způsobem je takový krok nesmyslný :) Nebylo by logičtější ponechat si to, co funguje a znám (jazyk), a nahradit ten pouze ten zbytek? Nebo to prakticky není možné, tj. všechny existující frameworks v Javě jsou poznamenané? A vyplývá tato poznamemanost z podstaty jazyka? Nebo by bylo možné si sednout a napsat nějakou Go-like knihovnu pro Javu?

Omlouvám se, že Vás tlačím tímto směrem, ale tyto otázky mi přijdou skutečně zajímavé :)

Tobiáš Potoček

Tak i čistý jazyk má svá specifika, ať už jde o samotný syntax, související naming conventions, sada základních struktur a funkcí či signalizování chyb, viz diskuze výše o výjimkách :) Já osobně mám třeba Javu velmi rád (jednoduše mi sedne) a to i z důvodů, které jsou často předmětem kritiky (třeba ukecanost Javy je něco, co mi snadno umožňuje číst cizí kód). Takže v praxi budu mít vždy tendenci spíše napravovat chyby v těch nadstavbových slupkách, než vyresetovat celý stack :)

Nicméně toto je možná dáno i tím, že v mém současném zaměstnání (korporát) se na serveru používá z velké částí custom Java stack (proto ty slupky vnímám jako oddělitelné), který v kombinaci s ustáleným (a rozsáhlým) seznamem konvencí a best practices prakticky eliminuje většinu zmiňovaných problémů. V jiném prostředí bych možná „trpěl“ podobně a radoval se z přechodu na Go, protože by to byla cesta nejmenšího odporu, jak z toho ven :)

atamiri

Stejný kód asi ne, v Go se píše jinak kvůli odlišnému runtimu, zejména gorutinám, například veškerá síťová komunikace je asynchronní (NIO), což se zrcadlí i ve standardní knihovně. Dalším problémem je pak nutnost mít JRE, kdežto Go produkuje vesměs statické binárky.

Oldisy3

To je tak když se jazyk používá k něčemu k čemu se nehodí. Stejný dojem sem získal když sem v c++, vlastně borland c++. taky sme v tom dělali něco na co se to absolutně nehodilo, ono to šlo ale to jen proto že to bylo dohnané 100MB knihoven částečně v delphi.

Richard Šindler

Problém javy je jak se se s ní pracuje. Zeptejte se javistu, jakou zvolí architekturu pro projekt Baf. Ještě dřív než se bude zajímat o to, co vlastně má Baf dělat, oznámí, že tam bude Spring a Hibernate. Možná, kdyby do javy přidali funkce dřív, než anotace, mohlo být všechno jinak.

Balki

To skor nadriadeny javistu (obvykle zaprdeny fortranista, ktory si precital aka je java skvela) prijde ze ma skvely projekt Baf a bude to webservis, ktory bezi nad vsetkymi databazami, na vsetkych prostrediach, musi mat embednute jetty a uvari to kavu. Developer potom zisti, ze by im stacila command lineova appka, alebo nieco hotove a je z toho smutny.

oldjay

ten první příklad – opravdu nevím co autora nutí dělat dvě prakticky identické třídy. hádám, že ty rozdílné anotace.
To ale v normálních frameworks není potřeba. Klidně používám anotace pro eclipselink (ORM) a jersey (REST) společně v jedné třídě.

Mlocik97

Skvelý článok, plne súhlasím… jedine co mi přijde na golangu horší než v jave je praca s enum.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.