Devoxx 2018

November 17th, 2018

Po letech jsem se byl podívat na Devoxxu, takže je na čase oprášit na blogu pavučiny a zapsat si, co jsem se naučil.

Reactive vs. Loom

Nejvíc mě dostala prezentace o projektu Loom. Na současném přístupu ke škálování mi cosi nesedělo, ale nedokázal jsem pojmenovat co. Pár firem potřebuje opravdu škálovat, a proto všichni musí začít připisovat knihovny na to, aby podporovaly RxJavu, Reactor a podobné. Musíme přepsat databázové ovladače, HTTP knihovny, přístup k souborovému systému tak, aby byl reaktivní. Za to se nám dostane té odměny, že už nebudeme muset psát v Javě, ale v jakémsi DSL, kterým budeme krásně skládat asynchronní potrubí.

Když se nad tím zamyslíte, tak je to neuvěřitelně prosakující abstrakce. Abych ji mohl použít, musím moji asynchronní knihovnu zadrátovat do API. Takže metody už nevrací T ale Mono<T>. To není prosakující abstrakce, ale cedník.

V projektu Loom si chlapci v Oraclu řekli, že to jde možná dělat lépe. Co nám vadí na vláknech? Jsou drahá, žerou paměť a jsou zbytečně blokována při čekání na sít, souborový systém nebo zámek. Co kdybychom použili jinou abstrakci, říkejme jí třeba Fiber (nitka?). Nitka se veze na vlákně, ale když narazí na blokující operaci, tak se vlákna pustí a to může jít vozit jiné nitky. Když blokující operace skončí, blokovaná nitka naskočí na jiné vlákno a provádí další můj kód. Jelikož se všechny blokující operace obvykle provádí skrz standardní Java knihovnu, kód který řeší opuštění vlákna a opětovné naskočení zpět je schovaný v JVM a jeho knihovnách. Já nemusím nic řešit. (Kdo můj poetický popis nepochopil, nechť se stydí a pustí si video).

To znamená, že jako programátor dostanu škálování v podstatě zadarmo. Můžu dál psát svůj staromódní imperativní kód a JVM zajistí, že to poběží efektivně. Samozřejmě nedostanu všechny výhody reaktivního přístupu jako backpressure (protitlak), ale možná mi to za čistší API, čitelné stack tracy a jiný konfort, na který jsem zvyklý, stojí.

Na Dovoxxu ukazovali zajímavé demo, kde vzali Jetty, místo ThreadPoolu podvrhli něco co používá Fibers a když to zrovna nespadlo, tak klasická aplikace v JAX-RS škálovala jako divá. Bohužel bude ještě pár let trvat, než dořeší všechny detaily jako ThreadLocals a podobně, ale vypadá to nadějně.

Trochu s tím rezonovala přednáška o Briana Goetze o Objektovém a funkcionálním programování. Síla objektového programování je v místech, které překračují hranice (boundaries). Tam potřebuji mít silný kontrakt v čemž OOP exceluje. Uvnitř hranic dává občas smysl zahodit ceremonii s objekty spojenou a využít sílu FP.

Nejlepší přednášky:

Co říci závěrem? Devoxx je super konference, člověk si rozšíří obzory, uvědomí si, jak moc ho programování baví a jak živý je Java ekosystém. Až na pár ojedinělých výjimek byly všechny přednášky skvělé, takže několikrát litoval, že se nemůžu rozkrájet a jít do víc sálů najednou. Nevadí, 10 Mistakes Hackers Want You to Make si pustím z YouTube.

Jak zrychlit vývoj

November 6th, 2016

Představte si, že jste antropolog, který byl vyslán ke kmeni programátorů, aby vyzkoumal, jak zrychlit jejich práci.

Nejjednodušší je, začít daný kmen pozorovat. Máme štěstí, o programování žvaní téměř bez přestání. Vypadá to, že nejdůležitější je, jestli se používají mezery nebo něco, čemu říkají tabelátory. Tak ne, nejdůležitější je psát funkcionálně, stav by měl být prohlášen za tabu. Aha to asi taky ne. Že by řešením bylo zbavit se typů? Nebo je naopak zavést? Co domorodec, to názor.

Také jim můžeme položit následující dvě otázky

  1. Kdybyste si mohli volně vybrat technologie pro příští projekt, o kolik byste byli rychlejší?
  2. Pokud byste psali váš současný projekt se současnou technologií znovu od začátku, o kolik byste ho udělali rychleji?

Nevím jak váš, kmen, ale u toho mého by výběr správné technologie moc velké zrychlení nepřinesl. To se samozřejmě může lišit, pokud děláte v korporaci, která vás nutí používat nepoužitelné nástroje, tak přechod na něco příčeňejšího může přinést docela dost. Něco jako když vás nutí hrát basketbal ve svěrací kazajce a pak vám uvolní jednu ruku. Ale u nás ostatních, technologie a programovací finty už tak zásadní přínos obvykle nemají. Něco jako, když jsme doteď hráli bosí a dostaneme super boty. Pomůže to, ale pokud nedokážeme trefit koš, tak zas ne o tolik.

Co odpověď na druhou otázku? To je jiná. Kdybychom současný projekt dělali znovu, tak bychom se vykašlali na tuhle funkcionalitu, protože jsme ji nakonec stejně vymazali, zeptali bychom se finančního ředitele na jeho názor mnohem dřív, takže bychom nemuseli půlku aplikace úplně předělávat. Možná bychom se do toho projektu vůbec nepustili, protože bychom věděli, jak neuvěřitelně nákladné to nakonec bude.

Proč bychom ten samý projekt psali po druhé mnohem rychleji? V čem je ten rozdíl? V tom, že se při implementaci projektu hlavně učíme. Ano, navenek to vypadá, že hlavně píšeme kód, ale největší fuška je v učení se. Učíme se co zákazník chce. Učíme se jak to sakra zaintegrovat do té hromady čehosi, co už ve firmě máme. Učíme se, jak komunikovat se svými kolegy. Učíme se jak ten systém provozovat. Učíme se jak to efektivně nasazovat. Pořád se prostě jen něco učíme.

Ano, psaní kódu je důležité, je to náš hlavní užitečný výstup, ale není to to, co nás brzdí. Sebelepší programovací prostředí, které mi bude číst myšlenky a generovat podle nich kód mi nepomůže, pokud nevím co mám dělat. Možná vás to překvapí, ale nepomůžou mi dokonce ani mikroservices.

Proč to píšu? Přijde mi, že si to lidé neuvědomují. Jeden můj výše postavený kolega se nám snaží pomoci tím, že chce některou naší méně důležitou programovací práci hodit na někoho mimo tým. Prostě někoho najmeme a o nám to naprogramuje. Jednoduché jako facka. Není. Psaní kódu je bohužel ta nejméně problematická část. Nejtěžší je vymyslet co to má dělat, jak se to má napojit, jak to provozovat a udržovat. To se outsourcovat nedá. Psaní kódu je navíc to co nás na tom baví, takže by nám zůstaly ty věci kolem. Programování by si slízl někdo jiný. Odporná představa.

Důležitost učení si ale neuvědomují ani programátoři. Ti stále vedou své žabomyší války o tom, jestli je lepší Emacs nebo Vi, jestli mají ukládat data do SQL nebo NoSQL, jestli mají používat Docker nebo nevím co. Něco z toho jsou užitečné debaty, ale dokud se ve stejné míře nevěnují i tomu, jak se lépe učit i ty nezáživné neprogramovací věci, tak jsou to debaty bezpředmětné. Lepší technologie mi může pomoci vyndat ze svěrací kazajky i tu druhou ruku, ale když nebudu mít zájem učit se pravidla hry a nepochopím, že tu skákavou kulatou věc mám dostat do té obroučky co visí nesmyslně vysoko nad hřištěm, tak jen budu pokračovat ve zmateném pobíhání po hřišti. Nejspíš mě to bude víc bavit, ale výsledek nebude o moc lepší.

Mistakes I made designing JsonUnit fluent API

April 11th, 2016

Several years ago, I have decided that it would be nice to add fluent API to JsonUnit library. If you are not familiar with fluent assertion APIs it looks like this.

assertThatJson("{\"test\":1}").node("test").isEqualTo(1);

assertThatJson("[1 ,2]").when(IGNORING_ARRAY_ORDER).isEqualTo("[2, 1]");

I really like fluent assertions. First of all, they are really easy to use, you get help from your IDE unlike when using Hamcrest static methods. Moreover, they are quite easy to implement. This is how the original implementation looked like.

public class JsonFluentAssert {

    protected JsonFluentAssert(...) {
    }

    public static JsonFluentAssert assertThatJson(Object json) {
        return new JsonFluentAssert(...);
    }

    public JsonFluentAssert isEqualTo(Object expected) {
        ... 
    }


    public JsonFluentAssert node(String path) {
        ...
    }


    public JsonFluentAssert when(Option firstOption, Option... otherOptions) {
    ...
    }

    ...
}

We have static factory method assertThatJson() that creates JsonFluentAssert. And all other methods return the same class so you can chain them together. Nice and simple. Unfortunately, there are three mistakes in this API. Do you see them? Congratulations if you do. If not, do not be sad, it took me several year to see them

Unnecessarily complicated

The biggest mistake is that the API supports chaining even after the assertion method isEqualTo() is called. It seems I designed this way on purpose since there is a test like this

assertThatJson("{\"test1\":2, \"test2\":1}")
    .node("test1").isEqualTo(2)
    .node("test2").isEqualTo(1);

The problem is that to support such rare use-case, now the API is more error prone. I got several error reports complaining that this does not work

assertThatJson("[1 ,2]").isEqualTo("[2, 1]").when(IGNORING_ARRAY_ORDER);

It looks reasonable, but it can not work. The comparison has to be done in isEqualTo and if it fails, it throws an assertion error so when() method is not called at all. In current design you can not postpone the comparison since we do not know which method is the last one in the invocation chain. OK, how to fix it? Ideally, isEqualTo() should have returned void or maybe some simple type. But I can not change the contract, it would break the API.

If I can not change the API, I can do the second best thing – mark it as deprecated. This way the user would at least get a compile time warning. It seems simple, I just need to return a new type from isEqualTo() with when() method marked as deprecated.

Methods return class not an interface

Here comes second mistake – isEqualTo() and other methods return a class not an interface. An interface would have given me more space to maneuver. And again, I can not introduce an interface without potentially breaking backwards compatibility. Some clients might have stored the expression result in an variable like this

JsonFluentAssert node1Assert = 
assertThatJson("{\"test1\":2, \"test2\":1}").node("test1");

If I want to mark method when() as deprecated if called after assertion, isEqualTo() has to return a subclass of JsonFluentAssert like this

public class JsonFluentAssert {

    private JsonFluentAssert(...) {
    }

    public static JsonFluentAssert assertThatJson(Object json) {
        return new JsonFluentAssert(...);
    }

    public JsonFluentAssertAfterAssertion isEqualTo(Object expected) {
        ... 
    }


    public JsonFluentAssert node(String path) {
        ...
    }


    public JsonFluentAssert when(Option firstOption, Option... otherOptions) {
        ...
    }
    ...
    public static class JsonFluentAssertAfterAssertion extends JsonFluentAssert {

        @Override
        @Deprecated
        public JsonFluentAssert when(Option firstOption, Option... otherOptions) {
            return super.when(firstOption, otherOptions);
        }
   }
}

Extensibility

Third mistake was to make the class extensible by making the constructor protected and not private. I doubt anyone actually extends the class but I can not know for sure. And I can not change signature of isEqualTo() without breaking subclasses.

Solution

It's pretty tricky situation. I can either keep broken API or break backward compatibility, tough choice. At the end I have decided to bet on the fact that no one is extending JsonFluentAssert and I did the change depicted above. I may be wrong but there is nothing else I could do, I do not want to live with bad API design forever. But if you have a better solution, please let me know. Full source code is available here.