30. 12. 2013

Hledání nejkratší cesty s omezeními – technická case study

Hledání nejkratší cesty s omezeními – technická case study


Hlavní náplň mé práce tvoří vývoj aplikace Registr telekomunikační sítě pro Telefóniku O2. Navzdory svému názvu aplikace neslouží pouze pro statickou evidenci hardwaru a síťových prvků, ale evidovaná data využívá pro potřeby procesů konstrukce sítě u zákazníka. V první polovině letošního roku jsem pracoval na zakázce, jejíž výstupem byl uživatelský formulář pro plánování tras optických kabelů. Z hlediska funkcionality se jednalo o grafovou úlohu. Byl to poměrně atypický problém, o to raději se chci podělit o popis řešení. Přijde mi totiž zajímavé především tím, že po stránce teoretické není úplně triviální (musel jsem oprášit skripta Teoretická informatika a jim podobné), ale současně si zachovává napojení na praxi a mohu tak na vlastní oči vidět jeho reálnou hodnotu pro uživatele (proto o něm bloguji až s časovým odstupem).

Je zřejmé, že dnes už nikdo aspoň trochu soudný nepíše vlastní algoritmus na řazení nebo binární půlení. Základní algoritmy se dostaly do standardní knihovny Javy i ostatních jazyků. Čím dále půjdeme od základních algoritmů ke specifickým, tím více se budou standardní knihovny vzdalovat, ale při troše štěstí lze pořád nalézt dobrou externí knihovnu a znovu neobjevovat kolo. Příkladem je např. JUNG pro grafové algoritmy, hashovací funkce a BloomFilter v Guavě nebo Levenshteinova vzdálenost v Apache commons-lang. Jsem však přesvědčen, že problém, o kterém budeme hovořit v tomto článku, má řešení za hranicemi běžného portfolia nabízeného světem opensource. Budu se snažit, aby čtenář neměl dojem, že "zas ten internet vyprodukoval bůhvíkolikátý tutoriál Dijkstrova algoritmu" (DA) a zaměřím se nikoli na DA, ale spíše na cestu od učebnicového DA k finálnímu řešení.

Zadání

(Ve skutečnosti zadání v takto ucelené podobě na začátku neexistovalo, jde spíš o souhrn všech znalostí o problému):
Je dán neorientovaný graf, kde uzly představují body telekomunikační sítě a hrany dílčí úseky optických kabelů, které body sítě propojují.
  • U každého úseku je evidována kladná délka v metrech. 
  • U většiny bodů sítě jsou evidovány GPS souřadnice budov, v nichž je umístěno zařízení. 
  • Úseky jsou uloženy v relační databázi jako řádky tabulky obsahující mj. id krajních bodů (z historických důvodů je tato tabulka snapshot ostrých dat aktualizovaný jobem 1x/24 hod).
  • Graf nemusí být souvislý (např. v praxi je již mezi dvěma body optika, ale na zbytek optické sítě ještě není kabel napojen, spojení je např. metalické a z hlediska databáze optických kabelů je to nespojitost).
  • Každý úsek patří k právě jednomu kabelu, kabel se skládá z mnoha úseků. (Kabel reprezentuje svazek optických vláken, jak je fyzicky položen mezi několika uzly sítě; výsledná cesta ale může jít přes úseky více kabelů, které jsou v uzlech spojeny provařením.)
Úkolem je nalézt pro dané body A, B nejkratší cestu z bodu A do bodu B.
  • Kritériem je minimální délka v metrech; při shodě délek vítězí cesta s menším počtem hran.
  • Jisté uzly, úseky nebo kabely mohou být vyloučeny z hledání (blacklist).
  • Jisté uzly, úseky nebo kabely mohou být naopak požadovány v nalezené cestě (whitelist). Není tím ale určeno jejich pořadí. Počet omezení na požadované části je omezen na max. 4. Požadavky na vyloučení nebo zahrnutí vycházejí z konstruktérské praxe a jsou důsledkem snahy o využití volných kapacit.
To už se trochu vzdálilo od ideálního pseudokódu z wikipedie, že? Pojďme si požadavky probrat podrobněji, od jednoduchých po nejsložitější:

Reprezentace grafu

Lze vytušit, že ať bude algoritmus jakýkoli, bude potřeba hodně dotazů na sousedící uzly či hrany a s SQL dotazem přímo do tabulky bychom asi po výkonové stránce uživatele moc neoslnili. Je tedy nutné pracovat s grafem v paměti. Naštěstí graf není tak rozsáhlý, aby se na slušném stroji do paměti nevešel – počet hran je cca 13000. Je zřejmé, že prostým načtením tabulky do seznamu POJO objektů bychom si oproti SQL dotazu taky moc nepomohli, navíc většina sloupců db tabulky vůbec není potřeba (případně je potřeba až nakonec a to řeší dodatečný hromadný dotaz).

Graf je proto za účelem efektivního hledání předzpracován, základní strukturu tvoří guavácká Multimap<Node,Edge>. Samotné třídy Node, Edge mají rovněž velmi skromný tvar – obsahují pouze id uzlu, resp. id úseku, délku úseku a id kabelu. Serializovaná podoba má cca 7MB, struktura je cachována na úrovni aplikačního serveru a dle timestampu obnovována ze snapshot tabulky v databázi (ze zadání plyne, že tabulka se mění pouze jednou denně).

Uvedené řešení je jednoduché, kompaktní, má dobrou časovou složitost všech potřebných lookupů a v případě potřeby může být kdykoli upgradováno na in-memory nebo NoSQL databázi.

Nesouvislost grafu

Pokud mezi dvěma uzly neexistuje cesta z důvodu nesouvislosti grafu, klasický DA nedokáže zabránit projití celé komponenty souvislosti, v níž leží výchozí uzel. Abychom se těmto zbytečným výpočtům vyhnuli, provedeme během předzpracování grafu detekci souvislých komponent algoritmem "šíření vlny": vybere se náhodný uzel, přiřadí se mu číslo komponenty 1, pomocí fronty se prohledá graf na všechny uzly z něj dosažitelné a tyto uzly obdrží shodné číslo komponenty. Ze zbylých uzlů se vybere další náhodný uzel a vše se opakuje pro komponentu číslo 2. To se celé opakuje, dokud zbývají uzly s nepřiřazeným číslem komponenty.

Při požadavku na nalezení nejkratší cesty lze ihned prohlásit, že uzly jsou nedosažitelné tehdy a jen tehdy, když se nerovnají odpovídající čísla komponent. Tato kontrola se použije i pro uzly na whitelistu.

Složené kritérium pro ohodnocení cesty

Ohodnocení kvality nalezených cest je určeno jak délkou, tak počtem hran. Původní algoritmus vyjadřující délku jako číslo by bylo nutné v místech, kde se porovnávají délky, ještě doplnit o porovnání na počet hran. Zesložiťovat algoritmus se mi ale nechtělo, neboť jsem intuitivně očekával, že jiné požadavky ho budou zesložiťovat mnohem neúprosněji. Proto jsem dal přednost izolaci této funkcionality do samostatné třídy:
class Score implements Comparable<Score> {
    private final int length;
    private final int numberOfEdges;
    ... 
    public int compareTo(Score that) {
        int result = ComparisonChain.start()
                .compare(this.length,that.length)
                .compare(this.numberOfEdges, that.numberOfEdges)
                .result();
        return result;
    }
} 
Tato třída se pak může použít všude tam, kde se v původním algoritmu pracuje s délkou. Vhodně zvolené názvy jako např. operace Score.plus(Score), statická factory metoda Score.fromLength(int), konstanta Score.ZERO a immutabilita zajišťují téměř neznatelný rozdíl oproti původní podobě algoritmu s ohodnocením typu číslo.

GPS souřadnice – pomoc nebo past?

Zdánlivě jasná odpověď – když hledám nejkratší cestu z Písku do Brna, je zřejmé, že si vyberu spíš Tábor než Plzeň. Praktická úvaha nás ale vrací k teorii: GPS je typickým příkladem heuristiky snažící se omezit stavový prostor hledání, naproti tomu DA patří mezi neinformované metody hledání a žádnou heuristiku nepotřebuje. Vypadá to, že bude potřeba zrevidovat, zda vůbec úlohu řešit Dijkstrovým algoritmem (kdy bychom GPS nevyužili) nebo zda použít alternativní možnost – algoritmus A*, který se dá považovat za zobecnění DA.

Díky kompaktní reprezentaci není problém s větším množstvím prohledávaných uzlů způsobeném absencí heuristiky palčivý. Nicméně jestliže tento argument pouze oslabuje důvod pro použití A*, následující zcela vyšachovává A* ze hry.

Jedním z podmínek nalezení optimálního řešení je totiž přípustnost heuristiky. Jak tento pojem pochopit selským rozumem? Představme si, že algoritmus je nevidomý cestovatel a heuristika jeho průvodkyně, která ho po cestě doprovází a je pro cestovatele jediným zdrojem informací. Představme si dva cestovatele na cestě z Prahy do Českých Budějovic, jeden si najme průvodkyni Hedviku, druhý průvodkyni Helenu. Hedvika je drzá, neustále balancuje na hranici, jak velkou lež může na cestovatele zkusit. Proto mu u Příbrami řekne, že už jsou v Písku, v Písku řekne, že už jsou před Českými Budějovicemi, a od Vodňan už jen opakuje netrpělivému cestovateli, že už tam každou chvíli budou. Ten je sice nervózní, ale po celou dobu žije v jakési iluzi lepšího a nic jiného mu v podstatě nezbývá. Naproti tomu Helena je hodná, snaživá a chce cestovatele v cíli "mile překvapit". Proto v Písku ohlásí teprve Příbram a těsně před Budějovicemi Písek. Cestovatel sice v lepším případě bude mile překvapen, v horším případě ho ale dlouhá cesta přestane bavit, takže v domnělém Písku nařídí změnit směr a v realitě skončí někde na Hluboké nebo na Temelíně.

Nevím, kterou z těchto průvodkyň byste si vybrali vy, ale algoritmus A* má určitě radši prolhanou Hedviku. Hedvika totiž představuje přípustnou heuristiku. Algoritmus je postaven tak, že s přehnaným optimismem si poradí, ale s pesimismem ne. Jak to souvisí se zadáním naší úlohy? GPS souřadnice jsou souřadnice budov, v nichž se někde uvnitř nacházejí konce jednotlivých kabelů. GPS je mnohem  přesnější než rozloha půdorysu jednotlivých budov, pro evidenci se musí zvolit zástupný bod, obvykle souřadnice hlavního vchodu budovy, závory areálu apod. Může pak nastat situace, kdy dva areály mají hlavní vchody daleko od sebe, ale mnohem blíž jsou k sobě optické rozvaděče, na kterých končí kabel. Je tedy zřejmé, že v situaci, kdy vzdušná vzdálenost GPS souřadnic zástupných bodů je větší než reálná délka kabelového úseku, heuristická funkce založená na GPS "přehání" a není tudíž přípustná a tím použitelná pro naše potřeby.

Uvedená situace byla prokázána analýzou dat a stejně jako případ, kdy GPS souřadnice nejsou evidovány, vedla k zamítnutí algoritmu A* pro řešení úlohy.

Ověření bezespornosti omezení

Design by contract je samozřejmá praxe, která se v našem případě prakticky projeví především v kontrole omezení na požadované nebo vyloučené prvky grafu. Musí být splněny tyto podmínky:
  • výchozí ani cílový uzel nesmí být na blacklistu
  • žádný prvek nesmí být současně na whitelistu i blacklistu
  • úsek nesmí být na whitelistu a současně jeho kabel na blacklistu (pozn. obráceně je to však legitimní – můžeme chtít jít přes kabel, ale ne přes nějaký jeho konkrétní úsek)
  • úsek nesmí být na whitelistu a současně nějaký jeho krajní bod na blacklistu
  • cílový uzel i prvky na whitelistu musí být z výchozího uzlu dosažitelné
  • na whitelistu nesmí být dva paralelní úseky (mající shodné krajní body), neboť dle definice cesty by nebylo možné se do krajního bodu vrátit

Detekce těchto situací je jednoduchá a rychlá – pár množinových operací. Provést ji před zahájením hledání považuji za lepší alternativu, než doplňovat kontroly do vyhledávacího algoritmu (které by odváděly pozornost od podstaty) nebo ji zcela vynechat (a získat výsledek, popř. informaci o neexistenci výsledku, po delší době a větším prozkoumaném prostoru).

Blacklist

Vyloučení bodů nebo úseků z hledání se uskutečňuje v místě, kde se zkoumají sousedé uzlu vyjmutého z fronty. Do pseudokódu DA z wikipedie by se přidaly řádky
 8         u := extract_min(N)
 9         for each neighbor v of u:
               if blacklist.rejectsEdge(u,v) continue;  
               if blacklist.rejectsNode(v) continue; 
10             alt = d[u] + l(u, v)

Whitelist

Nalezení nejkratší cesty s omezující podmínkou na požadované prvky je nejobtížnější částí celého úkolu. Obtížnost je způsobena faktem, že není určeno pořadí požadovaných prvků. Pokud na mapy.cz zadáte trasu Aš-Ostrava přes Brno a Prahu (v tomto pořadí!), zajistíte si pěknou trojnásobnou projížďku po D1, protože služba požadavek převede na posloupnost nejkratších cest mezi po sobě jdoucími body. V případě bodů sítě ale uživatel toto pořadí nezná a ani ho určovat nechce – nemá to vůbec smysl.

Přímočaré řešení založené na prozkoumání všech kombinací je nereálné, nicméně pomůže k lepší představě o obtížnosti úlohy: Uvažujme nyní na chvíli jiný graf, který je úplný, jeho množinu uzlů tvoří pouze uzel A, B a uzly na whitelistu původního grafu a hrany jsou ohodnoceny délkou nejkratší cesty mezi uzly v původním grafu. Může se zdát, že potřebujeme nalézt nejkratší Hamiltonovu cestu mezi uzly A a B a její jednotlivé hrany pak zpátky "rozbalit" na nejkratší cesty v původním grafu. Proč je to nesmysl? Z mnoha důvodů: Především tento algoritmus je špatně – mohou existovat dvě dílčí nejkratší cesty sdílející tutéž hranu, převodem na náhradní graf se tato nežádoucí situace zamaskuje. Nicméně i kdyby se tento problém překonal, algoritmus bude pomalý (nejen kvadratický počet volání DA, ale především hledání Hamiltonovy cesty, což je známý NP-těžký problém). A nakonec: algoritmus uvažuje pouze uzly, podle zadání ale mohou být na whitelistu i úseky (to by šlo ještě vyřešit půlením a zařazením vzniklého pomocného uzlu na whitelist) nebo kabely (zde mne žádný jednoduchý způsob transformace nenapadá).

Poslední důvod naráží na fakt, že v zadání jsou omezení stanovena velmi volně. Mohou znít nejen "jdi přes uzel 123", ale i "jdi přes úsek 456" nebo dokonce "jdi přes nějaký úsek, který patří do kabelu 789". Je tedy přirozené, aby algoritmus pracoval nikoli s kombinacemi projitých uzlů, ale s kombinacemi již splněných omezení. Kombinace jsou vyjádřeny jako prvky potenční množiny. Zahájení algoritmu hledání je spjato s prázdnou množinou splněných omezení, dosažení cíle s množinou všech splněných omezení.

Zbývá vyřešit problém nalezení cesty a k tomu mne inspirovalo řešení problému obchodního cestujícího pomocí dynamického programování. Řešení sice neodstraňuje exponenciální složitost, ale snižuje její řád a jak to u dynamického programování bývá, kompenzuje čas pamětí. Funguje na principu funkce g(u,set), která udává, jaká je nejkratší Hamiltonova cesta mezi počátečním uzlem a uzlem u, která jde přes uzly množiny set. Se zvětšující se množinou set se dosahuje znovupoužití dřívějších hodnot funkce. Poměrně srozumitelný příklad (vejde se na jedinou stránku a není v něm ani jedno řecké písmenko) je uveden zde (PDF). V našem algoritmu bude množinou set nikoli množina prošlých uzlů, ale množina splněných omezení. Při maximálně 4 splněných omezeních (viz zadání) je horní odhad počtu stavů 24=16.

Algoritmus

Výsledný algoritmus je tedy Dijkstrův algoritmus upravený v následujících rysech:
klasický DA modifikovaný algoritmus
prvek prioritní fronty uzel grafu dvojice (uzel,množina_splněných_omezení), která představuje nejlepší dosaženou cestu z výchozího uzlu do uzlu uzel, která splňuje příslušná omezení.
rekonstrukce nalezené cesty pro každý uzel se eviduje pouze předcházející uzel resp. vstupující hrana eviduje se uspořádaná množina všech uzlů na dosud nalezené cestě (tuto informaci potřebuje pro kontrolu, že se cesta prodlužuje o nový uzel)
inicializace vložení počátečního uzlu do fronty vložení dvojice (počáteční uzel,prázdná množina omezení) do fronty
dosažení cíle vyjmutí cílového uzlu z fronty vyjmutí dvojice (cílový uzel,množina všech omezení) z fronty
tvorba prvků fronty ze sousedů právě vyjmutého uzlu ze sousedů uzlu z právě vyjmuté dvojice; k původní množině splněných omezení se přidají ta omezení, která se splní průchodem příslušnou hranou resp. dosažením příslušného souseda
zacházení s prvkem fronty relaxace hrany může zlepšit dosavadní nejlepší cestu do uzlu, není-li již uzavřen kontrola, že cesta se prodlužuje o nový uzel, jinak totéž jako u DA
kritérium řazení prvků prioritní fronty podle délky dosud známé nejlepší nejkratší cesty podle skóre (délka, pak počet hran), při shodném skóre navíc sestupně podle počtu splněných omezení (pouze heuristika bez vlivu na správnost)

Pseudokód

List modifiedDijkstra(A, B, blacklist, whitelist) {
    queue = new Queue;
    queue.offer(A, {},                                        // vlozi do fronty dvojici (A,{})
        Score.ZERO, null, {});                                // u dvojice se eviduje skore, predchudce a cesta
    while (!queue.isEmpty()) {
        min,constraints = queue.poll();
        queue.close(min, constraints);
        if (min == B && constraints == whitelist.allConstraints) break; // jsme v cili
        for (neighbor,edge : min.neighbors) {                 // soused a hrana, ktera k nemu vede
            if (blacklist.rejectsEdge(edge)) continue;
            if (blacklist.rejectsNode(neighbor)) continue;
            if (min.getPath().contains(neighbor)) continue;
            neighborConstraints = constraints ∪ whitelist.getConstraints(neighbor,edge);
            if (queue.isClosed(neighbor,neighborConstraints)) continue;
            Score score = min.getScore() + edge.getLength();
            if (queue.isNew(neighbor,neighborConstraints) || score < neighbor.getScore()) {
                queue.offer(neighbor, neighborSet, score, min, min.getPath() ∪ min);
            }
        }
    }
    path = new List;                                          // rekonstrukce stejná jako u DA
    for (node = B; node != null; node = node.getPredecessor()) {
        path.insertAtBeginning(node);       
    }
    return path;
}

Ve skutečném kódu je několik rozdílů na úrovni implementace, které jsou způsobeny použitím objektového paradigmatu. Samostatné třídy, které jsou immutable a mají přesně vymezenou zodpovědnost, existují pro samotný uzel grafu (Node), dvojici (uzel,omezení) (ConstrainedNode) a stav dvojice ve frontě (TrackedNode). Díky tomu je např. skóre nebo předchůdce uzlu vlastností objektu TrackedNode, což je v pseudokódu trochu zamaskováno. Podobně i obohacení množiny omezení je implementováno pomocí bitových operací a guavácké Sets.powerSet. Prioritní frontu bylo výhodné implementovat pomocí TreeSet, ve které uzavřené uzly stále zůstávají, ale jsou posunuty na konec. Značná část logiky mohla být díky tomuto přístupu přesunuta do pomocných tříd.

Příklad

Činnost algoritmu si ukážeme na následujícím grafu, kde úkolem je najít nejkratší cestu mezi body A a B, která musí jít přes uzel F a hranu DE:
Výpis je vytvořen na základě reálného běhu testu, pouze komentáře jsou doplněny ručně. V každém kroku cyklu je fronta popsána pomocí tabulky, kde řádky představují uzly grafu a sloupce kombinace splněných omezení. Každý prvek fronty je vyjádřen zápisem v příslušném řádku a sloupci. Tento způsob se mi osvědčil při sestavování algoritmu, protože oproti prostému seznamu prvků fronty lépe vizualizuje, jak je algoritmus s plněním jednotlivých omezení daleko.

Zápis prvku má tvar $<délka>,<počet_hran> <path>, kde <délka> a <počet_hran> představují dosažené skóre a <path> je uspořádaná množina uzlů tvořících cestu z uzlu A do příslušného uzlu (nevčetně). Pro názornost je modře zvýrazněn nejmenší prvek, který bude v příští iteraci vybrán z fronty, tučně zvýrazněny prvky, které od minulé iterace nově vznikly resp. byly aktualizovány relaxací hrany, a naopak šedě ztlumeny prvky, které už jsou uzavřené.

ITERACE 1:
+---+-------+
|   | {}    |
+---+-------+
| A | $0,0  | inicializace s výchozím uzlem
+---+-------+
ITERACE 2:
+---+--------+--------+
|   | {}     | {F}    |
+---+--------+--------+
| A | $0,0   |        | výchozí uzel uzavřen
| F |        | $1,1 A | uzel F je dosažen a už je tím splněno jedno omezení
| C | $5,1 A |        |
| D | $6,1 A |        |
+---+--------+--------+
ITERACE 3:
+---+--------+----------+
|   | {}     | {F}      |
+---+--------+----------+
| A | $0,0   |          |
| F |        | $1,1 A   |
| C | $5,1 A | $5,2 A,F | dosažitelnost C přímo z A a nepřímo přes F jsou dvě různé věci
| D | $6,1 A |          |
| E |        | $6,2 A,F | nový uzel dosažitelný přes F
| B |        | $2,2 A,F | nový uzel dosažitelný přes F
+---+--------+----------+
ITERACE 4:
+---+--------+-------------+
|   | {}     | {F}         |
+---+--------+-------------+
| A | $0,0   |             |
| F |        | $1,1 A      |
| C | $5,1 A | $5,2 A,F    |
| D | $6,1 A | $11,3 A,F,B | D nově dosažen pro omezení {F}
| E |        | $6,2 A,F    | toto dosažení se nezlepšilo, přes A,F,B by mělo skóre $7,3
| B |        | $2,2 A,F    |
+---+--------+-------------+
ITERACE 5:
+---+-----------+-------------+
|   | {}        | {F}         |
+---+-----------+-------------+
| A | $0,0      |             |
| F |           | $1,1 A      |
| C | $5,1 A    | $5,2 A,F    |
| D | $6,1 A    | $11,3 A,F,B |
| E | $11,2 A,C | $6,2 A,F    | E nově dosažen také bez splněných omezení
| B |           | $2,2 A,F    |
+---+-----------+-------------+
ITERACE 6:
+---+-----------+------------+
|   | {}        | {F}        |
+---+-----------+------------+
| A | $0,0      |            |
| F |           | $1,1 A     |
| C | $5,1 A    | $5,2 A,F   |
| D | $6,1 A    | $7,3 A,F,C | relaxace hrany CD - hrana zlepšila cestu do D pro omezení {F}
| E | $11,2 A,C | $6,2 A,F   |
| B |           | $2,2 A,F   |
+---+-----------+------------+
ITERACE 7:
+---+-----------+------------+-----------+
|   | {}        | {F}        | {DE}      |
+---+-----------+------------+-----------+
| A | $0,0      |            |           |
| F |           | $1,1 A     |           |
| C | $5,1 A    | $5,2 A,F   |           |
| D | $6,1 A    | $7,3 A,F,C |           |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D | E nově dosažen pouze přes hranu DE
| B | $15,2 A,D | $2,2 A,F   |           |
+---+-----------+------------+-----------+
ITERACE 8:
+---+-----------+------------+-----------+-------------+
|   | {}        | {F}        | {DE}      | {F,DE}      |
+---+-----------+------------+-----------+-------------+
| A | $0,0      |            |           |             |
| F |           | $1,1 A     |           |             |
| C | $5,1 A    | $5,2 A,F   |           |             |
| D | $6,1 A    | $7,3 A,F,C |           | $10,3 A,F,E | D dosažen z E již se všemi omezeními
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D |             |
| B | $15,2 A,D | $2,2 A,F   |           |             |
+---+-----------+------------+-----------+-------------+

Situace všech "načatých" cest nyní vypadá podle následujícího obrázku. Poprvé bylo dosaženo splnění všech omezení (fialová cesta), ale na nalezení nejkratší cesty si budeme muset ještě počkat.

ITERACE 9:
+---+-----------+------------+-----------+---------------+
|   | {}        | {F}        | {DE}      | {F,DE}        |
+---+-----------+------------+-----------+---------------+
| A | $0,0      |            |           |               |
| F |           | $1,1 A     |           |               |
| C | $5,1 A    | $5,2 A,F   |           |               |
| D | $6,1 A    | $7,3 A,F,C |           | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D | $11,4 A,F,C,D | další dosažení všech omezení
| B | $15,2 A,D | $2,2 A,F   |           |               |
+---+-----------+------------+-----------+---------------+
ITERACE 10:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   | další dosažení všech omezení
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E |               | nově dosažen C pouze přes DE
| D | $6,1 A    | $7,3 A,F,C |             | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E |               | cíl dosažen, ale bohužel bez [F]
+---+-----------+------------+-------------+---------------+
ITERACE 11:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D |
| D | $6,1 A    | $7,3 A,F,C |             | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $19,4 A,F,E,D | nález prvního řešení
+---+-----------+------------+-------------+---------------+

Po 11. iteraci byl konečně dosažen cílový uzel za současného splnění všech omezení. Máme tedy první řešení délky 19,

ale zatím nemáme jistotu, že nalezená cesta je nejkratší. Musíme ještě dokončit dílčí načaté cesty, které jsou zatím kratší než nalezená cesta.

ITERACE 12:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $19,4 A,F,E,D |
+---+-----------+------------+-------------+---------------+
ITERACE 13:
+---+-----------+------------+-------------+-----------------+
|   | {}        | {F}        | {DE}        | {F,DE}          |
+---+-----------+------------+-------------+-----------------+
| A | $0,0      |            |             |                 |
| F |           | $1,1 A     |             | $15,3 A,D,E     |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D   |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E     |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D   |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,5 A,F,C,D,E | zlepšení řešení přes hranu EB
+---+-----------+------------+-------------+-----------------+

Další zlepšení vypadá takto:

ITERACE 14: nezajímavá - A,F,E,D,C je slepá cesta, žádné zlepšení ani nový nález se nekoná
+---+-----------+------------+-------------+-----------------+
|   | {}        | {F}        | {DE}        | {F,DE}          |
+---+-----------+------------+-------------+-----------------+
| A | $0,0      |            |             |                 |
| F |           | $1,1 A     |             | $15,3 A,D,E     |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D   |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E     |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D   |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,5 A,F,C,D,E |
+---+-----------+------------+-------------+-----------------+
ITERACE 15: nezajímavá - A,D,B je zkratka obcházející omezení, navíc drahá
+---+-----------+------------+-------------+-----------------+
|   | {}        | {F}        | {DE}        | {F,DE}          |
+---+-----------+------------+-------------+-----------------+
| A | $0,0      |            |             |                 |
| F |           | $1,1 A     |             | $15,3 A,D,E     | minimální prvek fronty díky více splněným omezením
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D   |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E     |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D   |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,5 A,F,C,D,E |
+---+-----------+------------+-------------+-----------------+
ITERACE 16:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E   | minimální prvek fronty libovolný z D{DE} a B{DE}
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,4 A,D,E,F | zlepšení řešení
+---+-----------+------------+-------------+---------------+

Další zlepšení, tentokrát se sice nesnižuje délka, ale počet hran cesty:
Zbylé iterace provádějí už jen pozavírání zbylých uzlů...

ITERACE 17:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,4 A,D,E,F |
+---+-----------+------------+-------------+---------------+
ITERACE 18:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,4 A,D,E,F |
+---+-----------+------------+-------------+---------------+
ITERACE 19:
+---+-----------+------------+-------------+---------------+
|   | {}        | {F}        | {DE}        | {F,DE}        |
+---+-----------+------------+-------------+---------------+
| A | $0,0      |            |             |               |
| F |           | $1,1 A     |             | $15,3 A,D,E   |
| C | $5,1 A    | $5,2 A,F   | $16,3 A,D,E | $12,4 A,F,E,D |
| D | $6,1 A    | $7,3 A,F,C | $15,3 A,C,E | $10,3 A,F,E   |
| E | $11,2 A,C | $6,2 A,F   | $10,2 A,D   | $11,4 A,F,C,D |
| B | $15,2 A,D | $2,2 A,F   | $15,3 A,D,E | $16,4 A,D,E,F |
+---+-----------+------------+-------------+---------------+


takže po vybrání B{F,DE} z fronty je již garantováno, že řešení je optimální.

Závěr

Popisovaný problém byl řešen se současným důrazem jak na efektivitu, tak na srozumitelnost při maintainování kódu a z toho plynoucí zastupitelnost v konkrétních podmínkách zákazníka. I když nebylo možné se vyhnout tomu, aby se někde projevila exponenciální složitost, popisovaná praktická opatření předcházejí všem negativním mimořádným situacím a řešení se za cca 9 měsíců provozu osvědčilo.

V reálné aplikaci jsou uzly tvořeny hierarchií různých typů uzlu a složitější je i způsob aktualizace grafu přes db job, ale tyto fenomény nemají vliv na hlavní algoritmus.




31. 10. 2013

Když Java nevěří svým vlastním jarům

Když Java nevěří svým vlastním jarům

Náš projekt je vnitropodniková aplikace, jejíž klientskou část tvoří tenký Swing klient - hlavní jar a 29 jarů knihoven třetích stran. Protože uživatelé si průběžně instalují aktualizace Javy, nainstalovali si i release JDK 7u45. U této verze ale dostali nepříjemné varování:
případně:

Všechny jary jsou samozřejmě podepsány certifikační autoritou (nikoli self-signed). Kde je problém?

Diagnostika

Aplikace toto varování nehlásila hned na začátku, ale až po přihlášení. Bylo tedy zřejmé, že problém nastane někdy za běhu, pravděpodobně při pokusu o classloading nějaké třídy. Nejrychlejší cesta k nalezení postiženého místa byla spustit aplikaci a poté, co vyskočí okno s varováním, se na ni napíchnout pomocí jconsole.
Mimochodem jsem při tom zjistil, že v případě, kdy jconsole nenalezne běžící lokální javovskou aplikaci (typicky vidí jen sama sebe), není nutné si hrát s remote připojením přes -Dcom.sun.management.jmxremote, ale stačí zavolat jconsole <PID> a po odklepnutí bezpečnostního dotazu to funguje taky.
V threaddumpech pak najdeme místo, kde aplikace čeká:

Stack trace:
com.sun.deploy.uitoolkit.ui.NativeMixedCodeDialog._show(Native Method)
com.sun.deploy.uitoolkit.ui.NativeMixedCodeDialog.access$000(Unknown Source)
com.sun.deploy.uitoolkit.ui.NativeMixedCodeDialog$2.run(Unknown Source)
java.security.AccessController.doPrivileged(Native Method)
com.sun.deploy.uitoolkit.ui.NativeMixedCodeDialog.showImmediately(Unknown Source)
com.sun.deploy.uitoolkit.ui.NativeMixedCodeDialog.show(Unknown Source)
com.sun.deploy.security.CPCallbackHandler.showMixedTrustDialog(Unknown Source)
com.sun.deploy.security.CPCallbackHandler.access$1100(Unknown Source)
com.sun.deploy.security.CPCallbackHandler$ParentCallback.checkAllowed(Unknown Source)
com.sun.deploy.security.CPCallbackHandler$ParentCallback.check(Unknown Source)
   - locked com.sun.deploy.security.CPCallbackHandler$ParentCallback@abcfd2e
com.sun.deploy.security.CPCallbackHandler$ParentCallback.access$1700(Unknown Source)
com.sun.deploy.security.CPCallbackHandler$ChildElement.checkResource(Unknown Source)
com.sun.deploy.security.DeployURLClassPath$JarLoader.checkResource(Unknown Source)
com.sun.deploy.security.DeployURLClassPath$JarLoader.getResource(Unknown Source)
com.sun.deploy.security.DeployURLClassPath$JarLoader.findResource(Unknown Source)
com.sun.deploy.security.DeployURLClassPath$1.next(Unknown Source)
com.sun.deploy.security.DeployURLClassPath$1.hasMoreElements(Unknown Source)
java.net.URLClassLoader$3$1.run(Unknown Source)
java.net.URLClassLoader$3$1.run(Unknown Source)
java.security.AccessController.doPrivileged(Native Method)
java.net.URLClassLoader$3.next(Unknown Source)
java.net.URLClassLoader$3.hasMoreElements(Unknown Source)
sun.misc.CompoundEnumeration.next(Unknown Source)
sun.misc.CompoundEnumeration.hasMoreElements(Unknown Source)
java.util.Collections.list(Unknown Source)
com.o2bs.rts.core.MainApp.getApplicationBuildVersion(MainApp.java:200)

Inkriminovaným místem je tedy zjišťování čísla buildu, které se zobrazuje na stavovém řádku aplikace! Jde konkrétně o kód:

List<URL> resources = Collections.list(Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF"));

Výpisem resourců zjistíme, že seznam obsahuje kromě aplikačního jaru a jeho závislostí ještě systémové jary:

[jar:file:/C:/Program%20Files/Java/jre7/lib/javaws.jar!/META-INF/MANIFEST.MF,
jar:file:/C:/Program%20Files/Java/jre7/lib/deploy.jar!/META-INF/MANIFEST.MF,
jar:file:/C:/Program%20Files/Java/jre7/lib/plugin.jar!/META-INF/MANIFEST.MF,
jar:file:/C:/Program%20Files/Java/jre7/lib/deploy.jar!/META-INF/MANIFEST.MF,
...]

Ty samozřejmě nejsou podepsány certifikátem zákazníka. Očekával bych, že je bude Java považovat za důvěryhodné, zatímco zrovna tyto jary způsobují, že Java aplikaci považuje za tzv. mixed code. Zřejmě ale nejsem sám, koho to překvapuje. Poslední tři odkazy pěkně popisují i varianty řešení:

Řešení - zmírnit kontrolu v Javě

Java Control Panel -> Advanced -> Mixed code... -> Enable - hide warning and run with protections

Zaškrtnutí uvedené možnosti způsobí, že Java se bude chovat, jako by uživatel stiskl No resp. Don't block. Výhodou této možnosti je jednoduchost, nevýhodou je ovšem nutnost zásahu na straně uživatele. Vzhledem k většímu počtu a různé technické zdatnosti uživatelů se snažíme je obtěžovat technickými zásahy co nejméně a jen tehdy, není-li alternativa.

Řešení - označit jary jako Trusted-Library

Přidáním atributu Trusted-Library:true do manifestu každého jaru (našeho a závislostí, nikoli systémových) se docílí toho, že jar bude nahrán classloaderem, který je tolerantní k popisované situaci. Podle dokumentace by měl být tento class loader rodičem class loaderu z Java Web Start, ačkoli inspekcí výrazu Thread.currentThread().getContextClassLoader() a následných .getParent() jsem žádný rozdíl nezpozoroval a v obou případech (s vylepšením manifestu i bez něj) byl řetěz classloaderů tvořen objekty tříd com.sun.jnlp.JNLPClassLoader, com.sun.jnlp.JNLPClassLoader, com.sun.jnlp.JNLPPreverifyClassLoader, sun.misc.Launcher$AppClassLoader, sun.misc.Launcher$ExtClassLoader.
Výhodou této možnosti je oprava bez zásahu do kódu i do uživatelova nastavení. Nevýhodou je pouze subjektivní pocit, že atribut, jehož dokumentace jasně uvádí "The Trusted-Library attribute is used for applications and applets that are designed to allow untrusted components.", je zneužit pro workaround zcela odlišného problému.

Modifikace build skriptu

V případě volby tohoto řešení je také třeba počítat s tím, že se ten atribut musí do jarů nějak dostat. V našem případě se jedná o zásah do Maven buildu. Pro build hlavního jaru je řešení jednoduché: pro maven-jar-plugin přidat do configuration/archive následující XML:
<manifestEntries>
    <Trusted-Library>true</Trusted-Library>
</manifestEntries>
Složitější je přidání atributu do jarů závislostí. Neznám žádný specializovaný Maven plugin, který by uměl pracovat s existujícím jarem, proto jsem se se skřípěním zubů uchýlil k Antu přes maven-antrun-plugin. Ani s Antem ale není ještě vyhráno - problém vykonání nějaké operace na každém .jar souboru z dané množiny není řešitelný pomocí samotného tasku <jar update="true"> (nested element <fileset> totiž specifikuje soubory určené k zabalení do jaru, nikoli množinu jarů!). Většina rad na webu byla založena na použití elementu <foreach> z knihovny ant-contrib, to by ale vyžadovalo další závislosti a především by nás to ještě víc zabetonovalo v antovském antipatternu programování v XML. Naštěstí mezi nimi existovala i výjimka, na jejímž základě se nakonec podařilo implementovat funkcionalitu přes task <scriptdef>. Vzhledem k tomu, že rozšíření ant-nodeps už na projektu stejně je a build běží na JDK6, která obsahuje Javascript, nevyžaduje toto řešení žádný dodatečný artefakt a vypadá i relativně čitelně:
<scriptdef name="addTrustedLibraryIntoManifests" language="javascript">
<![CDATA[
    importClass(java.io.File);
    importClass(org.apache.tools.ant.taskdefs.Manifest);
    var baseDir = new File(self.getProject().getProperty("project.build.directory") + "\\dependencies");
    var files = baseDir.listFiles();
    //self.log("baseDir " + baseDir + ", length " + files.length);
    var jarCount = 0;
    for (j = 0; j < files.length; j++) {
        var filename = files[j];
        if (/.*\.jar$/.test(filename)) {
            var jarFile = new File(filename);
            var manifest = new Manifest();
            var mainSection = manifest.getMainSection();
            mainSection.addConfiguredAttribute(new Manifest.Attribute("Trusted-Library","true"));
            var jarTask = self.project.createTask("jar");       
            jarTask.setDestFile(jarFile);
            jarTask.setUpdate(true);
            jarTask.addConfiguredManifest(manifest);
            jarTask.execute();
            jarCount++;
        }
    }
    self.log("enhanced " + jarCount + " jars");
]]>
</scriptdef>
<addTrustedLibraryIntoManifests />

Tato zkušenost mne utvrdila v názoru, že Maven se svým deklarativním frameworkovým přístupem je slabá zbraň proti partyzánskému imperativnímu programování v XML, které tu bylo v době dominance Antu. O důvod víc přejít na Gradle, který jsem během rozhodování pro toto řešení měl také na paměti.

Řešení - přistupovat k jaru přímo

Místo volání getResources na classloaderu by bylo také možné získat požadované informace přímo:

import com.sun.jnlp.JNLPClassLoader;
import com.sun.javaws.jnl.JARDesc; 
JNLPClassLoader cl = (JNLPClassLoader)Thread.currentThread().getContextClassLoader();
JARDesc desc = cl.getLaunchDesc().getResources().getEagerOrAllJarDescs(true)[0];
URL url = desc.getLocation(); 
try (JarInputStream is = new JarInputStream(url.openStream(),false)) {
    System.out.println(is.getManifest().getMainAttributes().getValue("Implementation-Build"));
}

Tuto možnost uvádím jen pro úplnost, program jsem zkoušel pouze na úrovni inspekce běžící aplikace. Pokud by měl být v reálném kódu, pravděpodobně by se musela vyřešit viditelnost tříd z JWS a dále ošetřit možnost, že aplikace je spouštěná mimo JWS (typicky z IDE), aby nedošlo ke ClassCastException při castu na JNLPClassLoader. Celkově vidím užitečnost tohoto kódu jen pro troubleshooting a ladění, ne pro produkční nasazení.

Shrnutí

Nepochybuji, že Oracle klade důraz na bezpečnost a obzvlášť u posledních upgradů Javy to lze pocítit, viz zdůvodňování posledního odložení JDK8. Nicméně změny v minor updatech jsou nepříjemné a navíc se mi nechce jen tak přijmout, že tak elementární případ, jakým je aplikace se všemi podepsanými jary, se musí zprovoznit takovýmto workaroundem.
Nicméně problém se podařilo odstranit, s přihlédnutím k výhodám a nevýhodám jednotlivých možností řešení. Zajímalo by mne, jak se bude toto chování vyvíjet v dalších verzích. Pokud máte zkušenost s jednodušším postupem u některého kroku, budu rád, když se o něj podělíte v komentářích.

23. 5. 2013

GeeCON 2013 - 3. den

Zápisky z třetího dne konference GeeCON 2013

První den zde. Druhý den zde.

Bodil Stokke: What Every Hipster Should Know About Functional Programming

Konečně přednáška o funkcionálním programování, u které jsem rozuměl použitému jazyku. Typescript vůbec není špatná věc. (Podle toho, jak jsem se o něm bavil s jinými programátory, zaznamenávám nejen poměrně pozitivní ohlas na samotný jazyk, ale i jakýsi druh příjemného překvapení související s tím, že autorem je Microsoft.)
Před přednáškou jsem se ještě díval na biografii přednášející, a protože mi nebyla jasná poslední věta, potřeboval jsem si doplnit vzdělání. To se nakonec ukázalo jako osudně rozhodující krok k pochopení přednášky, protože veškeré příklady pak obsahovaly postavy z tohoto seriálu.
Po stručném přehledu základních paradigmat a pár teoretických slajdech přednášející ukázala na jednoduchých příkladech základní pojmy:
  • first class functions = functions are values, can be passed or returned  as argument
    • priklad: hello() je funkce x hello je proměnná (v níž je přiřazená funkce)
  • functor is collection of X that can apply function f: X->Y over itself to create collection of Y
    function map(func: (a:any)=>any, list: any[]) : any[] {
       for (i...) newlist.push(func(list[i]));
    }
  • rozdíl ponies x map(CAPS,ponies) (ponies je seznam, CAPS je funkce pro převod na uppercase) 
  • funktor = např. filter nebo redukce:
    function reduce(func:(a,b)=>any, list:any[], initial) {...}
    reduce(add,[1,2,3,4,5],0);
  • funkce vyšších řádů jsou vlastně abstraktní továrny na funkce
    mapReducer(func:(a:any) => any) {
        return function(a,b) {
            return a.concat(func(b));
        }
    }
    reduce(mapReducer(CAPS),ponies,[]) 
  • combinators
    function square(x:number):number {return x*x;}
    function addThree(func) {return function(x) {return func(x)+3}}
    addThree(square)(5) ... 28
    • praktičtější aplikace: nullSafe - ošetří volání funkce na null a tváří se jako funkce
  • kompozice funkcí
    function CAPS ... převede na uppercase
    function hi ... vypíše hello arg
    func compose(f1,f2) {return function(x:any) {return f2(f1(x));}}
    compose(hi,CAPS)("everypony") ... totéž co CAPS(hi("everypony"))
  • applicative functors, příklad s kombinací dvou listů - kartézský součin, aplikuje mapu na 2 listy
  • currying (schonfinkeling) - transformuje funkci o mnoha argumentech na řetěz funkcí o 1 argumentu tak, že posloupnost volání dává identický výsledek (tady už to začínalo houstnout a nešlo opisovat, nicméně lepší než na fotky je podívat se přímo na prezentaci)
  • partial application
  • monads 
Celkově oceňuji názornost příkladů a motivaci podívat se na Typescript. Příklady ke konci si ještě budu muset projet, protože pak už jsem nestíhal.

Ken Sipe: Making Java Unit Test Groovy with Spock

Přednáška o Groovy testovacím frameworku Spock. Opět jedna z kvalitnějších přednášek s mnoha praktickými ukázkami. Spock je framework založený na JUnit umožňující psát testy formou specifikace předpokladů a následných očekávání přímo do testovací metody. Pomáhá řešit také další související situace při psaní testů, např. zastínění jednoho předpokladu druhým (neotestování dalšího při selhání prvního) nebo testy s parametry. Využívá k tomu sílu jazyka Groovy umožňující definovat podmínky a očekávané chování pomocí srozumitelného DSL.
  • anotace umožňují vynechat test při splnění/nesplnění určitých podmínek
    @IgnoreIf({!jvm.java8})
    def java8feature() { ... expect: friends.stream().getFirst() != null;}
    
    nebo naopak
    @Requires({jvm.java8})
    
    Zde se uplatňují výhody dynamického jazyka, ve statickém by se to díky .stream() pro Javu nižší než 8 nezkompilovalo.
  • názvy metod v groovy mohou být řetězce s mezerou. Oproti Javě, kde se u názvu metod musíme přizpůsobit syntaxi identifikátoru a řešit to různými náhražkami v podobě podtržítek a velbloudí konvence, tohle je další výhoda Groovy - čitelnější popis, dokumentace je součástí názvu metody.
  • zápis testu - given: ... when: ... then: ...
    • given odpovídá setUp resp. @Before
    • when je jako obsah testu před asserty - vlastní stimul
    • then odpovídá assertům - testuje odpověď
  • lze testovat i mocky
    def subscriber1 = Mock(Subscriber)
    Subscriber subscriber1 = Mock()  - ekvivalentní zápis
  • flexibilní zápis testu pomocí přetížených operátorů, lambd nebo wildcard podtržítek:
    • 1 * subscriber1.receive("event") ... na instanci subscriber1 byla jednou volána metoda receive s uvedeným parametrem
    • receive(!null) ... voláno receive s čímkoli různým od null
    • receive({String s -> s.contains("event"}) ... vlastní ověřovací lambda výraz
    • subscriber1./rec.*/(!null)  ... volána metoda, jejíž název splňuje regex
    • subscriber.receive(_ as String) ... nebo jejíž parametr je cokoli určitého typu
    • 2 * _.receive ... ale nerozlišuje, zda byla na jednom mocku zavolána metoda dvakrát nebo na dvou mockách po jednom
    • _ * _._(_)  ... validní zápis - libovolný počet volání libovolné metody s libovolnými argumenty na libovolném objektu
  • expect: je when a then zároveň, je to šablona pro nějaký testovací příklad
  • where: je pro doplnění parametrů do šablony, při syntaxi parametr << [list] to doplňuje parametry ze seznamu. Pokud je více parametrů, berou se pro příslušný běh testu stejnolehlé hodnoty ze všech seznamů.
  • cool feature - pokud nastane chyba, vymaluje to jednoduchý ascii art vyznačující volání a pomocí svislítek popis argumentu metody a místa chyby (mám rád ascii malůvky)
  • parametry pro expect/where lze zapsat i ve formě tabulky á la wiki, v buňce tabulky může být i volání metody
  • @Shared - použít pro resource náročný na tvorbu, existuje na úrovni třídy a mohou přes něj spolu komunikovat jednotlivé testovací metody, podobné použití static fieldů u obyčejného JUnitu.
  • old() closure - přístup na původní hodnoty před spuštěním testu
    then: map.example != old(map.example)
  • násobná klauzule then definuje, že očekáváme testované interakce v daném pořadí (naproti tomu více podmínek v jedné klauzuli then představuje podmínky testované jako celek)
  • možnost vlastních anotací na testech a další řízení testů podle anotací. Příklad:
    definice vlastních anotací:
    @Fast, @Slow
    použití:
    runner {
        exclude {
            annotation Slow
        }
    }
    Na našem Java projektu něco podobného máme a oproti Spocku je to celkem pracné.
  • extension annotation - umožňují nadefinovat si vlastní anotace, pověsit je na proces testování (specifikaci) a udělat kód onSuccess nebo onFailure (ukázka kódu, který na chybu reagoval promluvením "Failure is not an option"). Podobnou věc jsem si jednou udělal i v Javě (úspěch zahrál fanfáru, neúspěch zvuk havárie), ale bylo nutné se pořádně vlomit do tříd JUnitu.
  • další menší poznámky: anotace @Stepwise, Hamcrest expect: ... that ..., JUnit Rules už přednášející nestihl, protože značně přetáhl, ale mně osobně to vůbec nevadilo
Shrnutí: přednáška zvýšila nejen můj zájem o Spock, ale i o samotné Groovy. Není sice u nás reálné přejít s projektem na Groovy a statické jazyky kvůli typové kontrole stále preferuji. Nicméně v případě některých testů si dovedu představit úsporu práce, pokud by byly založeny na tandemu Groovy+Spock.
Za povšimnutí stojí i jednotlivé konstrukce a DSL (wiki tabulka, přetížení operátorů, klauzule). Tyto zápisy jsou sice elegantní, ale domnívám se, že je potřeba dobře znát, na jakém jazykovém principu jsou postaveny. Přistupovat k nim bez znalosti Groovy jako k magii, která funguje, se může vymstít, (resp. to člověka stejně přinutí pochopit, až s tím bude experimentovat).

Piotr Gabryanczyk: Typeclasses, Monads - Functional and simple!


Přednáška orientovaná na Scalu a NoSQL databázi Cassandra, aspekty funkcionálního programování tam byly zmíněny tak, že už to teď nedám dohromady. Poslouchal jsem jen napůl a stejně jsem v polovině odešel. Ostatní témata ve slotu mne nelákala, proto jsem využil čas alespoň pro vyzkoušení obou mindballů (ilustrační fota) na stáncích YSoftu, kde bylo jinak o přestávkách narváno. A musím uznat, že to fakt funguje :-).

Bert Ertman, Paul Bakker: Building Modular Cloud Applications in Java - Lessons Learned

Architektonická přednáška předvádějící cloudovou aplikaci postavenou na OSGi pro podporu učení pro děti. Cloudové aplikace jsou pro mě jako programátora nevyzkoušená věc, přednášku jsem si proto vybral záměrně. Architektura založená na HTML5 + javascriptu, RESTful ws, OSGi servisách, OSGi frameworku Amdatu a Eclipse pluginu BndTools, které napomáhají modularitě, a MongoDB. Střípky:
  • důraz na modularitu - schopnost vyměnit část za běhu
  • autoscaling - během školního vyučování se očekává větší zátěž -> potřebují kapacitu, ale neplatit za idle provoz serverů v noci -> Amazon autoscaling
  • bezestavová architektura - dobrá škálovatelnost (HTML5 umožňuje více bezestavové klienty, zmínka o Angular.js)
  • tip na knihu: Building modular applications (O'Reilly)

Discussion panel: Functional Programming - radical thinking shift and step towards clearer and reliable software

Formát diskusního panelu jsem na GeeCONu ještě nezažil a toto znělo podle názvu slibně. Diskuse se účastnilo 7 hostů + moderátor. Byla to zčásti teoretická, zčásti praktická diskuse, asi podle povahy jednotlivých hostů. První půl hodiny však nebyl prostor pro otázky publika, a i potom se ke každé otázce vyjadřovala většina hostů, takže na konci bylo hodně tazatelů, na které se nedostalo. Zajímavosti:
  • citát Martina Oderskeho (přibližně): je výzvou pro dnešní dobu, jak provést přechod z imperativního na paralelní distribuované programování při zohlednění omezených schopností lidské mysli, které jsou třeba k jeho zvládnutí
  • vylepšení paralelismu je řízeno modernizací hardwaru, hardware už je dnes lepší a může podporovat náročnější paradigma
  • funkcionální programování je jen prostředek k dosažení paralelismu
  • neexistuje čistě funkcionální jazyk (M. Odersky)
  • nejde říct o jazyku, že je funkcionální, ale že některé jeho rysy jsou funkcionální
  • změna paradigmatu je větší problém než změna jazyka
  • otázka, jak dlouho bude trvat adopce paradigmatu v business světě

Sven Peters: How To Do Kick-Ass Software Development (závěrečná keynote)

Odlehčená přednáška na závěr, nicméně i tak s hodnotnými informacemi. Pojem vypůjčen z filmu Kick-ass (český překlad Nářez), který jsem neviděl, ale z kontextu (tím, jak tento pojem přednášející používal v každé druhé větě) se tomu dá rozumět jako "pořádný", "úžasný", dobrý postřeh měli taky kluci, že by to šlo nahradit za "agilní". Přednášející ze společnosti Atlassian (JIRA).
  • diabolical developer: compiles? == ship it!
  • team which works with legacy code != legacy team == hard to refactor if member leaves, adding processes helps, old decisions still apply, changing stuff is too complicated
  • deliver kickass software
    • kickass feedback - musí být jednoduchý, ne: vidět chybu, mít odkaz úplně jinde, odkaz zavede do JIRA, projdu wizardem, registrací a pak musím vyplnit x polí formuláře
    • easy to find, fast to submit
    • protect your developers
    • google sh't umbrealla: 425mil. users, 100 developers
    • týmy dělají daily meeting 1/2 hod. jen nad feedbackem
  • have kickass teams
    • Developer On Test - dejte testování developerům
    • QA = Quality Assistance (ne Assurance)
    • 6 tips for kickass DoTing
      • training - týdenní školení, jak psát testy, pro všechny developery
      • pairing
      • blitz test - všichni se podílí na testování důležité funkcionality, odhalí hodně chyb
      • test recipe
      • split sessions - opposite of pairing, dev a tester testují jiným způsobem
      • bug hunter - člen týmu, který se snaží to shodit
    • quality is everybody's responsibility
    • developers are doing design
    • "jednou jsem dělal GUI a dopadlo to takhle: (plný panel fieldů a checkboxů)", "pak jsem k tomu přidal barvy (strašně nabarvená složitá aplikace)"
    • design workshop for developers
    • designers are also developers - umi zaklady gitu, html atd.
  • kickass collaboration
    • lone cowboy x trouble starts with team
    • development rules protecting from making mistakes as traffic rules from accidents
    • fast,simple workflow for parallel coding
    • pull requests - what do you think?
    • atlassians prefer collocated teams - everybody in one office
    • vyhýbejte se emailům (alternativa: chatroom v ramci firmy, twitterový formát zmínky o jiném člověku: @someone)
    • v Atlassian chatují i lidi ve stejné místnosti - sluchátka na uších udržují v zóně soustředění
  • kickass automation
    • kolik času strávíte týdně automatizací vašeho softwarového vývoje?
    • buildy - dlouhé, složité, nestabilní, bez koncepce
    • 4 rady, jak se vypořádat s monster buildy
      • vynechat součást buildu, která to zpomaluje
      • paralelizovat testy
      • mít strategii buildů, např. zbuildování projektu a unit testy pokaždé, integrační testy 1x/hod., výkonové 1x/noc
      • sledovat statistiky
    • rychlé buildy = snížené přepínání vývojářů mezi úkoly
    • viditelné výsledky testů: wallboards, televize s dashboardem
    • freud bot - statická analýza kódu v pull requestu
    • release button = single push deployment
  • další kickass věci mimo vývoj softwaru: rychlost vývoje, kvalita, škálovatelnost, tým, zákazník - vše kickass
  • hledejte cesty, jak to zlepšit - ohnout pro váš tým, ne jak poslouchat nějakého gurua
  • manažeři jsou lidi, někteří se nechají přesvědčit snadněji, někteří obtížněji (u každého ilustrativní foto, u "obtížněji" to bylo foto Larryho Ellisona :-)
  • share success and failures
  • kickass firemní kultura
  • můžu si na konci dne říct: did I kickass today?

Shrnutí

Automatizace, funkcionální programování, cloudové aplikace a testování patřily mezi nejčastější témata konference. Nejvíce mne obohatily přednášky s praktickými kusy kódu, obzvlášť pokud si přednášející troufnul na live demo.
Na závěr bych také rád poděkoval společnosti YSoft, díky které jsem měl na konferenci volnou vstupenku. Budeme o GeeCON také mít lightning talk na některém setkání CZJUG. Tyto články i lightning talk jsou výsledkem snahy předat získaný užitek zas dál.

21. 5. 2013

GeeCON 2013 - 2. den

Zápisky z druhého dne konference GeeCON 2013

První den.

Oliver Gierke: Data Access 2.0? Please welcome - Spring Data!

Spring Data je knihovna Springu pro přístup k NoSQL databázím. Klasický springový přístup využívající návrhový vzor Template Method, podobně jako u relačních databází je JdbcTemplate nebo u JMS JmsTemplate.
  • zdůrazňoval, že Spring Data se snaží zachovat specifické vlastnosti konkrétních NoSQL databází (u NoSQL databází je vzájemná odlišnost přece jen větší než u relačních), např. podpora zeměpisných souřadnic u Monga
  • ukázky pro Mongo a Neo4J
  • Spring Data definuje vlastní anotace mající zhruba ekvivalent v JPA. Právě proto, že Spring Data je jen projekt zastřešující menší podprojekty pro konkrétní NoSQL databáze, má každý podprojekt na míru ušitou sadu anotací. Např. @Entity z JPA odpovídá u Monga @Document, u Neo4J @NodeEntity
  • MongoTemplate - obdoba JdbcTemplate
  • repositories - obdoba DAO u JDBC
  • možnost definovat dotaz anotací @Query v signatuře DAO metody a anotacemi parametrů metody vyznačit navázání parametrů dotazu
  • plugin do IDE dokáže zpracovat anotované třídy a vylepšit podle nich nápovědu jmen. Např. za findBy nabídne Name, pokud má entita takovou property
  • QueryDslPredicateExecutor - vyjádření dotazu pomocí predikátu, metoda findAll
  • integrace s CDI
  • DZone refcard pro projekt Spring Data (přednášející je leader projektu a zároveň autor taháku)

Jessica Kerr: Object-Oriented Design in the Wild

Poměrně informativní přednáška především v mluveném slovu, slajdy hrály spíš roli vyvolání emocí, bavení a dotvoření celkového efektu. Oceňuji především rozvedení základních principů OOD: SOLID, DRY, YAGNI a pár dalších. Po zhlédnutí přednášky stojí za to si je znovu projít na stránce Principy OOD nebo aspoň na wikipedii.
  • úvod o typech, třídách atd., porovnání Java/Haskell
  • rajský obrázek s podmanivou hudbou a zasněným prohlášením "Ruby is my developer joy" (u Haskellu protikladný projev) - docela vtipné
  • Single responsibility principle (S v SOLID)
    • třída by měla mít pouze jeden důvod ke změně
    • příklad se zbožím, které je na skladu, lze ho koupit a prodat:
    • Java: class Good implements InvItem,Purchase,Sellable
    • Ruby: class Good include InvItem include Purchase...
  • Interface segregation principle (I v SOLID)
    • deklarujte nejmenší možný interface
  • Liskov substitution principle (L v SOLID)
    • podtřída má properties nadtřídy, ale nemění jejich význam
    • LSP=PLS=Principle of Least Surprise
    • překvapením může být: přístup na globální stav, změna vstupního argumentu, změna okolí třídy, porušení očekávání
  • Dependency inversion principle (D v SOLID)
    • části by neměly o sobě vědět, přirovnání k linuxu: cat data.csv | grep 42 | cut -f2 | sort
  • Open-closed principle (O v SOLID)
    • knihovna je "closed for modification", ale "open for extension" aplikací, která ji používá
  • cohesion x coupling
  • Reuse-Release equivalency principle - znovupoužití kódu by mělo být umožněno ne kopírováním, ale pouze v releasech, u nichž je uživatel odstíněn od vnitřní implementace a jejichž vydání je patřičně komunikováno. Uvedla Guavu jako ukázku dodržování tohoto principu.
  • napsat komponentu s tím, že je předem plánována pro znovupoužití, je předčasná optimalizace (kořen všeho zla - Donald Knuth). Podepisuji to z vlastní zkušenosti: jakákoli i třeba dobře myšlená aktivita (nejen kus softwaru, ale třeba zavedení používání nějakého nástroje nebo firemního procesu), která vznikne rozhodnutím někoho od stolu, je odsouzena k tomu, že ji nikdo nepoužívá, nezvykne si na ni a nepřijme za vlastní. Musí vzniknout reaktivně, ne proaktivně.
  • Acyclic dependencies principle
  • Stable dependency principle
    • komponenta, na které někdo závisí, by měla být stabilnější než závisející komponenta
    • Příklad: stabilita roste zleva doprava: javascript na stránce -> JQuery -> browser
  • Stable abstraction principle
    • když mám modelovat tygra, nemodeluju ho jako něco, co implementuje rozhraní kočka a rozhraní zvíře v džungli, ale prostě jako tygra, jinak je to přeabstrahováno
  • nalézt zlatou střední cestu mezi znovupoužitelným a abstraktním
  • princip DRY
    • nepřehánět to a moc nevysušovat, nebo vznikne poušť
    • příklad: na řadě míst se vyskytuje formátování datumu, někdo z toho v dobré víře udělá společnou komponentu, ale to může být špatně, protože každý výskyt může formátovat podle jiných národních zvyklostí
    • někdy je copypaste lepší alternativa než zavlečení další závislosti na společné logice
  • princip YAGNI - kill zombie code, případně zakomentovat
  • každé pravidlo pomáhá jen do té míry, do jaké nasměruje správným směrem
  • starý kód je často lepší, protože již prokázal svůj přínos
  • svoboda porušit pravidla, pokud to dává smysl - používat selský rozum

Tom Bujok: SOAP sucks, doesn't it?

Pro mě nejlepší přednáška druhého dne. Uklidnila mě, že nejsem sám, komu připadá práce se SOAP webservisami přes nízkoúrovňové XML jednodušší a intuitivnější než přes heavyweight stacky jako CXF, Axis nebo Metro. Když jsme měli na projektu implementovat webservisy, rozhodl jsem se použít Spring Web Services (tehdy ale ještě ve verzi 1.5.9, podpora testů ještě nebyla součástí a musela se použít úžasná knihovna Lukáše Křečana) a nad nimi udělat jen tenkou vrstvu pro potřeby projektu. Tady zašli ještě dál a udělali nad Spring WS poměrně flexibilní, intuitivní a stručné API - knihovnu Reficio SOAP-WS.
Můj pohled na webservisy hodně utvářel článek Arjena Poutsmy (project leadera Spring WS) a bylo vidět, že prezentace byla v podobném duchu (rozdíl code-first x contract-first). To už sliboval i abstrakt přednášky. Poznámky:
  • pojem "buzzword driven architecture"
  • odstrašující příklad složitého psaní ws: hodně factory, builderů, práce s DOM, metoda normalize s nejasným významem - v Javě na jedné stránce
  • v protikladu Groovy příkad s XmlSlurper na 2 řádky
  • odstrašující příklad, co se musí vše definovat v CXF: wsdl, binding.xml, cxf.xml, 2x spring applicationContext, cxf-servlet.xml, web.xml, ant/maven pluginy: cxf-codegen, builderhelper
  • API postavené na builder patternu, který je použit pro vše: tvorbu message, WSDL i konfiguraci. Ukázky:
    • String message = Wsdl.parse(URL).binding().localPart(...)
    • WsdlParser
    • SoapContext().builder().alwaysBuildHeaders(true)...
    • SoapClient.builder().endpointUrl(...).basicAuthentication(...).connectTimeout(...).readTimeout(...).build().post(soapAction,message);
    • SoapServer.builder().httpPort(...).maxThreads(...).start() ... .stop()
    • RequestResponder, Source respond(request) - čistý OO návrh API pro práci se zprávou, které umožňuje ji v případě potřeby změnit, ale zůstává jednoduché v defaultním nastavení.
  • následovaly příklady a praktická ukázka, groovy demo

Trisha Gee: What do you mean, backwards compatibility?

Přednášející z vývojového týmu MongoDB, předváděla praktické zkušenosti s vývojem nového driveru pro Mongo. Vzhledem k povaze projektu, na kterém dělám (účelově zaměřený produkt pro jednoho klienta), neřešíme v oblasti kompatibility tak závažné problémy jako u softwaru typu Mongo, nicméně byla to zajímavá přehledová přednáška, i když slajdy opět spíš jen pro efekt.
  • Design is process not document
  • everybody is designer - jako by opsala některé myšlenky z prezentace Martina Čackého 
  • sepsali dvoustránkový dokument s cíli nového návrhu -> důležité ujasnit si, jaké podmínky má nové řešení splňovat
  • obecné podmínky:
    • konzistence (pořadí parametrů, zda jsou fieldy na začátku/na konci třídy, ale zas tyto maličkosti moc neřešit, důležitá je architektura)
    • cleaner design
    • intuitivní API ("users are our friends"), např. přirozené pořadí volání metod: collection.find(query).sort(sortCriteria).count()
    • sane exception handling
    • test friendly
    • backward compatible
  • žádné chyby ze statické analýzy, projití testů - nutná podmínka správně vyřešené zpětné kompatibility
  • identifikujte své uživatele - kdo to bude používat? (java developer, developer knihovny třetí strany, contributor)
  • přizpůsobení starého API, něco na způsob toho, co jednou psal Dagi 
  • souvislost se SpringData a předchozí přednáškou Olivera Gierkeho
Dotaz přednášející na to, kdo to používá, nakonec trochu zkazil jinak dobrý dojem - evidentně se zvedlo méně rukou, než čekala, důvodem byl zřejmě fakt, že lidé používají API na vyšší úrovni (viz právě předchozí zápisek o Spring Data).
Na přednášce byly použity UML diagramy nakreslené v online utilitě Gliffy. Vypadaly pěkně, až budu potřebovat zas někdy namalovat UML, tento nástroj rozhodně zvážím. Už jsem takto přešel od Photoshopu a Gimpu ve prospěch Pixlr. Myslím, že kvalita online nástrojů neustále roste. Chápu, že pro fulltime grafika nebo analytika by to asi cesta nebyla, ale pro nárazovou práci to splňuje úkol lépe než těžkotonážní systémy typu Enterprise Architect.

Slawek Sobótka: Model is all you need

Člověk zaměřený na DDD. Znalost business problematiky je u nás dost důležitá a okolo modelování se točí práce mnoha programátorů a analytiků. Kromě samotného modelování domény na úrovni entit řešíme i to, jak se do modelu promítá UI a také toho se prezentace dotkla. Prezentace byla jinak zajímavá počtem názorných přirovnání a parabol a v neposlední řadě i použitým prezentačním nástrojem prezi.com (prezentace).
  • složitost (complexity) systému
    • essential - složitost samotné povahy problému, nevyhnutelná (představuje hranici, za kterou není možné problém zjednodušit, jinak už by se řešil jiný problém)
    • accidental - složitost vyvolaná implementací konkrétního řešení problému, tu se snažíme minimalizovat
  • doménový expert a autor modelu spolupracují nad modelem
    • doménový expert předává znalost autorovi modelu
    • autor modelu tvoří mode
    • doménový expert ověřuje, že model odpovídá realitě
  • iterace: udělat nejdřív model z user story, zdokumentovat a pak konfrontovat s novým scénářem (a až pak ho teprve nakódovat). Např. objednávka má více položek, pak přijde scénář, kdy je nutno vydat jen některé položky z objednávky a doménový model se změní tak, že z entity Objednávka se stane entita Rezervace).
  • "compiler is your friend unless you have a dynamic language and you have to run it, static lanuage is not bad idea" - kolikrát už jsme se spolehli na překladač, když se při změně modelu něco rozbilo...
  • vztah mezi modelem a UI: z UI flow se musí rekonstruovat proces a zákonitosti business logiky, z nich API a z API teprve domain model
  • user story se zaměřuje na mentální model uživatele, což je ok, ale pro naprogramování to není dost
  • skvělé přirovnání, co odlišuje user story od znalosti business logiky: když hraju kulečník, můžu napsat 100000 user stories, které budou znít "když držím tágo tak, půjdou koule tam". Ale podstatné je znát to základní: zákon odrazu (business rules).
  • "objednávka má více řádků - tak si to namalujeme jako tabulku s řádky, ne jako UML s propojením ♦—, za UML vás lidi z businessu vyhodí. Řada lidí má vizuální inteligenci a potřebuje to vidět, nestyďte se za to." Trefné a svým způsobem osvobozující.
  • dobrý model sděluje informace o realitě implicitně svým návrhem. Např. má-li entita metodu Invoice issuance(Order order), říká tím model, že můžeme vydat a vyfakturovat právě jednu objednávku.
  • symetricky: Dont hack your model. Ústupky a špinavé triky v oblasti modelování core entit jsou větší zlo než např. hackování implementace kódu, který je jinak postaven nad dobrým modelem. Známe z praxe: např. špatná rozhodnutí v návrhu db tabulek nebo volba nesprávných datových struktur.
  • problém: overgeneralization (přehnané zobecnění) - např. různé nesouvisející objekty dědí z Document jenom proto, že implementuje Printable "to look more professionally"
  • klientská abstrakce modeluje koncepty nějak a v modelu můžou být úplně jiné
  • metoda approveOrder, kterou přednášející zakončil příklad, má v sobě tolik ifů, že by mohla sloužit jako startovní bod pro včerejší přednášku o refactoringu. Ale tady představuje cílový stav :-).
  • modelování peněz (vtip: kolik je 2 EUR krát 3 EUR? odpověď: 6 EUR čtverečních)
  • využití open-closed principu a návrhového vzoru Strategie - např. pro výpočet daně: rozdělit na pevnou logiku (BookKeeper), měnitelnou strategii (TaxPolicy) a logiku, která strategii nastaví do pevné logiky (Factory)
  • featura patří k doméně nebo k aplikační logice? Jak to poznat? Přirovnání: Kam patří nákupní košík?
    • když to je nějaký obchod, kde nemají košíky, tak jen k aplikační logice
    • když je košík např. součástí psychologického šetření chováni zákazniků, se kterým má aplikace počítat, pak je to součást domény
  • další přirovnání: pozoruji z okna, že Slunce obíhá Zemi, založím na tom nový programovací jazyk, bez getterů a setterů s jednořádkovými cykly a dám ho do cloudu (schválně tam nacpal co nejvíc cool věcí). Ale model mám špatně.
  • není důležité, aby model modeloval realitu, důležité je, aby modeloval situaci, na které funguje ten byznys. Pokud je byznys založen na tom, že Slunce obíhá Zemi a funguje, pak ho musím takhle namodelovat.
  • doménový expert musí mít znalost ne širokou, ale hlubokou. Kdo ví všechno o všem, není doménový expert.

Sam Newman: Surfing The Event Stream

Tato přednáška mne zajímala především proto, že často hledám ve velkém množství logů. Myšlenkou přednášky bylo zacházet se zprávami logu jako s eventy a tyto eventy přeposílat a rozesílat (do jiných systémů nebo knihoven pro zobrazení a zpracování statistik), filtrovat atd. I když řešení, které předváděl, bylo určené pro zpracování mnohem rozsáhlejších dat než naše logy, byla přednáška užitečná kvůli zmínkám o mnoha stránkách, odkazech a dalších nástrojích:
Celkový dojem: téma je mi v tomto podání vzdáleno, ale přednáška nabídla hodně odkazů ke studiu a prozkoumání. Myšlenka zpracovávat logy online, generovat události a rovnou je třídit a posílat monitorovacím systémům, je fajn.

Joel Spolsky: The Cultural Anthropology of Stack Exchange (keynote)

Autor StackOverflow, odlehčující keynote na konec, nicméně se zajímavými informacemi spíš k poslouchání než pro nějaké dlouhé poznámky.

  • yahoo answers jsou jako otázky školáků, když se vrátí ze školy - otázky do diskuse, ne do Q&A fóra
  • proč reputation? jako sociální jev, který je všude (srov. s armádou)
  • meta stackoverflow - SO o SO
  • "we hate fun" - zavírají se otázky, jaká je nejvtipnějši hláška v kódu/komentářích (neřeší problém, site je zaměřena na řešení problémů)
Třetí den.