Co mi dalo pět let v Seznamu

Už na pohovoru mi Petr, (v té době) budoucí nadřízený, říkal, že se nejedná o veřejný projekt. Abych nebyl zklamán. Zřejmě byl překvapen, když mne to naopak potěšilo a bral jsem všemi deseti. :-) Utíkal jsem totiž od klientů/uživatelů, s kterými jsem si užil svoje. Chtěl jsem přestat řešit věci, které mne nebaví, a zaměřit na věci, které mne naopak baví. A to vývoj.

Když jsem nastoupil, moc jsem toho neuměl. Nevěděl jsem ani, že je rozdíl, zda definuji proměnou v initu nebo na třídě. Ani verze Pythonu mi moc neříkaly, resp. bylo mi úplně jedno, jaká kde je. Verzování pro mne bylo pouze commit. Testy jsem věděl, že jsou dobré a chtěl je psát, ale zkušenost žádná.

Přišel jsem jako jeden z pomocníků k projektu, který už nějaký rok běžel se spoustou úkolů a žádný čas na řešení technického dluhu. Seznam má naštěstí výhodu v podporování teambuildingů a tak jednou v hospodě jsme se s kolegou dohodli, že zavedeme code review. A bylo. Když to ulehčím. Samozřejmě jsme si to dlouho ladili a ladíme neustále.

To vyvolalo diskuze o tom, co vlastně používáme. Framework uplácaný na koleni z dob, kdy frameworky ještě pořádně nebyly. Což o to, na dobu vzniku dobré, ale nijak dál se nevyvíjelo. Takže čas migrovat. Měl jsem v té době hodně rád Django, takže jsem přispěl s refaktoringem modelů na něco jako modely v Djangu. Aby se to mohlo jednou na Django převést. Kéž bych v té době viděl do budoucnosti…

…a věděl, že bez testů je refaktoring blbost. Potřebovali jsme testy. Měl jsem o testování velký zájem, takže jsem byl pověřen nastudováním, vyslán na školení, zavedení na projektu a zaškolení kolegů. Zpětně se těm úplně původním testům doslova řechtám. Ale bylo potřeba nějak začít a úkol to splnilo.

Jedna z vtipných věcí byl pětiřádkový SVN hook, který testy pustil a poslal mail. Ještě že Petr viděl za včas kam to směřuje a poukázal na sluhu Jenkinse. Slouží nám dobře dodnes. Jen jsme ho postupně zahltili více a více úkoly. O tom za chvíli.

…a taky, že když na měsíc odjedu, nakonec se rozhodne pro Flask a SQL Alchemy. Při prototypování vyšel jako vítěz co se týče možností poskládat a postupně rozšířit do celé aplikace bez nutnosti zastavit vývoj nových featur. Takže rozhodně nelitujeme.

Což jsem už v době, která byla pro mne i tým zlomová. Dostal jsem celý tým na starost. Kluk, který rád programuje a nemá rád lidi, se má najednou postarat o celý tým. Mám však rád výzvy, tak jsem do toho šel. Spoiler alert: dnes už vím, že mne to děsně baví!

A proč to bylo zlomové pro tým? Protože mám asi tak trochu úchylku na automatizaci. Ne že bych si automatizoval úplně vše, ale mezi slepými jednookým králem, že. Začalo to už chvíli po nástupu do Seznamu, kdy jsem si udělal několik utilitek. Jmenovitě například pro balení deb balíků a jejich instalaci na testovací servery či pro práci s logy.

Což bylo fajn, ale chtěl jsem je dotáhnout mnohem dál. Takže jsem postupně projekt vedl k bodu, kdy nasadit na test neznamená sedět u počítače půl hodiny a bouchat něco do konzole. Nemluvě o nasazení do provozu. Ale nedělat nic, naopak si vychovat sluhu. Jenkinse. Což teda znamenalo spoustu práce na pozadí, která není ani vidět (a produkťáci se pro jistotu ani nesmí dozvědět :-)), ale přijít do práce a vidět v e-mailu, které tickety byly samy přes noc nasazeny, je slast.

A ještě větší radost je, když už od vývoje do provozu nejsou průměrně dva měsíce, ale týden. Pro to jsme museli změnit styl verzování a zlepšit testy a code review. Se vším jsme se poprali. Dnes už větvíme o sto šest a všichni vyjmenují základy Gitu i ve dvě ráno. S integračními testy nám pomohl pytest a Selenium (o čemž jsem už párkrát vyprávěl na různých akcích a konferencích; například v Bilbau a českém PyCONu). Jen code review nám dlouho haprovalo. Dnes se už můžeme chlubit párováním, což je nejlepším code review. Odhadem tak třetina, možná i polovina, kódu se páruje a zvyšujeme.

Druhý tool jsem měl pro stahování a parsování logů. Jenže, i když jsem téměř nemusel nic dělat, stále trvalo půl hodiny dostat se k požadovaným informacím. Navíc se hledalo jen to, co se nahlásilo. Takže jsem opět pomaličku nepozorovaně dostal na televizi téměř real-time chyby z provozu se všemi potřebnými informacemi k rychlému vyřešení. (Všichni sborově: áááh.)

Když jsem si říkal, že už jsem udělal dost a zasloužím si po pěti letech změnu, pustil jsem se ještě do jedné věci na rozloučenou. Dokumentace. Abych mohl vše, co o aplikací vím, sepsat. A nejen já, ale všichni v týmu mohli mezi sebou sdílet informace, proč se kde co jak vlastně udělalo. A jak to vlastně používají samotní uživatelé.

Ano. Po pěti letech projekt opouštím a bude mi hodně chybět. Přeci jen to byla parádní jízda a těch pět let mi dalo opravdu hodně. A nebude mi chybět pouze, co jsme na projektu dokázali a jak nám funguje, ale i kluci. Přeci jen celý tým jsem si sám poskládal dle svých představ. Cítím však potřebu změny a věřím, že díky schopnosti kluků moje absence nebude vlastně ani znát. Těším se, až si za dalších několik let sedneme s Viktorem (mým nástupcem) a budeme se smát dnešní situaci jako se nyní smějeme té před pěti lety. Protože je stále co zlepšovat a hlavně je v backlogu spoustu zajímavých projektů. Pro které mimochodem Viktor určitě uvítá dalšího pomocníka.

Každopádně nejdu moc daleko. Seznam mám rád, takže se jen přesunu o pár stolů dál, kde se budu dál zlepšovat na projektu doporučování a cílení.

Star Wars mohlo být lepší. Nebo ne?

A je to tu zase. Neustále dokolečka. Jedněm Star Wars přišlo v pohodě, neurazilo, pro jiné naopak propadák. Spousta lidí se bálo, aby z toho nebyl druhý Star Trek, protože chtěli zachovat duch původních dílů. Nyní se dost lidí rozčiluje, že to zachovává ducha a že to mělo mnohem větší potenciál.

U Star Treku se zlobíte, že už to není ten původní Star Trek. U starého Spidermana jste se rozčilovali, že je málo podle komiksu, u nového zase že až moc. No to je prostě pořád něco. To si nemůžete film užít jednoduše tak jak je? :-)

Osobně se mi nový díl Star Wars líbil. Oceňuji, jak si vyhráli zase s modely. Jak není film uspěchaný a dává si na čas. I jak je spoustu ještě nevysvětleno, takže se mohu těšit na další díl a neříkat si, s jakou slátaninou asi přijdou. Však všechny předchozí díly byly v podobném stylu. Přesto se mi zatím sedmý díl líbil ze všech nejvíc.

Což si občas někdo myslí, že jsem spíš na staré filmy. Aby bylo jasno:

  • Mám raději staré Bondovky, ano. Craig mi na Bonda nesedí.
  • Líbily se mi také staré filmové Star Treky. Ale(!) i ty nové.
  • Sedmý díl Star Wars je asi nejlepší z celé série.
  • Batman jedině od Nolana!
  • Celá série Fast & Furious se mi líbí, především poslední díly.
  • Nový Spiderman věrnější komiksu mi přijde lepší, a to jsem ho nečetl.
  • Zbožňuji celou provázanost Marvelovského vesmíru.

    Ale nemám s jinými verzemi či díly problém. Prostě to tak udělali. Pokud se mi to nelíbí, zhlédnu jen jednou, nebo ani nedokoukám. Víte, že film je dílo jako každé jiné a to že vychází (například) z knihy neznamená, že tak musí být do poslední tečky? Ano, koukám na vás, čtenáře Game of Thrones, Harryho Pottera atd.

    Tedy přijměte film jak je a (nebo aspoň) neotravujte s tím ostatní. :-)

    Tak.

    Nová láska Haskell

    Už tomu jsou dva roky, co jsem si řekl, že chci poznat svět funkcionálního programování. O Haskellu jsem slyšel, že je to ten správný výběr, ale akademický. Takže jsem sáhl po jednom z těch populárnější, Scale. Zkusil jsem si kurz na Courseře a… no, nedokončil jsem. Scala mi nějak k srdci nepřirostla. Nebavilo mne v ní programovat. Asi tam na mne bylo stále moc Javy a málo prostoru pro funkcionální styl. Takže jsem přeci jen nakonec skončil u Haskellu.

    Začít se učit funkcionální styly v Haskellu pro jeho puričnost je náročné. Několikrát mi málem explodovala hlava, jak je vše úplně jinak, než jsem doposud znal. Ale přežil jsem a jak se říká, co tě nezabije… to si zamiluješ. :-)

    Uvědomil jsem si to ve chvíli, kdy jsem napsal Pytohní kód, který na mne zařval výjimkou. Že prý druhý řádek není callable…

    ZeroDict = defaultdict(lambda: 0)
    d = ZeroDict({'a': 1, 'b': 2})

    Aneb technika známá jako partial application ve funkcionálním světě. Každá funkce bere ve skutečnosti pouze jeden parametr, což se v Haskellu velmi často prakticky využívá.

    Jakmile jsem chybu opravil, zařvalo na mne NoneType object has no … Něco, co se mi v Haskellu nemůže stát. Prázdná hodnota totiž neexistuje. Pokud se může něco pokazit, musí se taková událost explicitně vyjádřit. Což může znít děsivě, ale díky monádám (žádný strach) se kód neprasí samými podmínkami a pracuje se s ním mnohem lépe.

    Například jsem si udělal jednoduchý RSS a Atom parser. Samotné poskládání dílčích celků dohromady vypadá nějak takto:

    getSourcesFromDatabase >>= fetchFeeds >>= mapM createQuery >>= commitQueries

    A pokud se něco po cestě pokazí, script nespadne. Normálně doběhne a každá část se může rozhodnout, jak se situací naložit. Může to znít, že nakonec ty podmínky oddře spodnější vrstva. Ano, musí se kód přizpůsobit, každopádně sám jsem podmínku v Haskellu nepoužil. Žádnou! Aspoň ne v podobě, jakou ji běžně známe. Ani cyklus.

    Další skvělá věc je totiž pattern matching. Dalo by se to lehce přirovnat k overloaded funkcím na steroidech. Příklad k parsování RSS či Atom feedu (pro lepší pochopení: vstup je seznam tagů jak jdou za sebou v XML struktuře, včetně komentářů či textu):

    parseFeed :: Tags -> Maybe Feed
    parseFeed [] = Nothing
    parseFeed ((TagOpen "rss" _):tags) = parseRss tags
    parseFeed ((TagOpen "feed" _):tags) = parseAtom tags
    parseFeed (_:tags) = parseFeed tags -- Skip XML declaration and comments at the beggining.

    Hledá se známý tag rss či feed a podle toho se provede další parsování. Jinak se pokračuje dál. A pokud nenajde, vrátí se Nothing, což je možná hodnota typu Maybe. Při použití se pak musí s takovou možností explicitně počítat. Ale jak bylo vidět výše, monády (což není nic jiného, než design pattern) to podstatně zjednodušují.

    Navíc vše je krásně vidět, čímž mám na mysli typy. Haskell je velice chytrý a umí si typy téměř vždy odvodit sám. Ale mohu mu je předat a pomoct si tak při debugování. Jinými slovy Haskell je silně typový, ale zároveň nehází programátorovi klacky pod nohy.

    Na Haskellu je hezká další věc, na kterou nemám vhodnou ukázku, takže si půjčím jak najít pravoúhlý trojúhelník s obvodem 24 jednotek z Learn You a Haskell for Great Good:

    ghci> let allTriangles = [(a,b,c) | c <- [1..], b <- [1..c], a <- [1..b]]
    ghci> let solutions = filter (\(a,b,c) -> a^2 + b^2 == c^2 && a+b+c == 24) allTriangles
    ghci> take 1 solutions
    [(6,8,10)]

    A to, že se problémy neřeší zapsáním algoritmu, ale matematickým zápisem – udělá se množina možných řešení (klidně nekonečných) a aplikují se různé transformace, kterými se odfiltrují nevhodná. Až zůstanou jen ta správná řešení.

    Hm, opravdu nekonečných? No jasně! Však se mrkněte na ukázku, konkrétně právě [1..] generuje čísla do nekonečna. Haskell je totiž, jako správný programátor, lazy. Dokud nemusí, nic neprovede. Pokud spustíte dlouho trvající (či nekonečnou) funkci, ale nepoužijete její výsledek, Haskell ji vůbec nevykoná. Což pak otevírá spoustu zajímavých možností řešit úkoly s nekonečnem. Jako třeba tu, kde chci jen první řešení, takže jakmile se najde, zbytek se vůbec nevyhodnotí.

    Bohužel to taky má své stinné stránky. Sice to v některých případech oproti ostatním jazykům problémy řeší, v jiných naopak je potřeba dávat velký pozor. Aby něco takového fungovalo, musí si Haskell pamatovat stack, jak v případě potřeby danou hodnotu vypočítat. Což u velkých struktur může znamenat stack overflow. Tedy programátor na to musí myslet a na správných místech použít ty správné funkce či jinak vynutit vypočítat hodnotu, protože to je na nějakém místě prostě efektivnější.

    Abych to shrnul: nikdy jsem si nemyslel, že se mi bude líbit jazyk, kde nejsou podmínky, cykly a prázdné hodnoty. Přišlo mi něco takového jako bláznovství. Ale po seznámení s Haskellem se mi takové praktiky opravdu líbí. Bohužel si myslím, že díky puričnosti, za což je často odsuzován, se do praxe moc nedostane. Ale osobně se mi to, překvapivě, opravdu líbí. Myslím si, že právě striktní oddělení pure funkcionálních funkcí dává méně prostoru pro chyby.

    Mimochodem v Haskellu nejde debugovat s print příkazy jako třeba v Pythonu. Nejlepší způsob jsou testy. Takže mne to hezky nutí co nejvíce kódu umístit do lehce testovatelných pure funkcí a psát řádné testy vždy při vývoji, nikoliv až potom. A tím jsem zjistil, že TDD je pouze mentální switch. Nijak to nekomplikuje práci, je potřeba si na takový postup pouze zvyknout.

    Pokud se chcete na Haskell také podívat, doporučuji tyto zdroje:

    Proč odmítám nabídky s větším platem?

    Přijde mi, že lidi stále nepochopili, že peníze nejsou vše. Občas se mne totiž někdo zeptá, proč vlastně nepřijmu nabídku, kde mi dají více peněz? Nejde moc jednoduše vysvětlit, ono to je totiž komplikovanější. V rozhodnutí stojí více faktorů. Pravděpodobně ty nejdůležitější jsou u mne tyto čtyři: lidi, projekt, stabilita a peníze.

    Přiblížím. Jako první jde o lidi. Je velmi důležité, s jakými lidmi pracuji. Je mi úplně ukradené, zda dostávám milion měsíčně a pracuji na super-cool projektu. Pokud se budu muset každý den potýkat s idioty, nebudu v práci šťastný. Nebude mne práce bavit, budu deprimovaný a nemít na nic náladu.

    Další faktor je samotný projekt. Může to být patlání firemních mini webíků, nebo velký projekt se škálou zajímavých projektů a technologií s možností se učit, či služba kterou sám používám a/nebo v ní vidím smysl. Samozřejmě nejlepší je kombinace posledních dvou. Patlat neustále mini webíky mne nebude uspokojovat po žádné stránce a opět mne nebude práce bavit.

    Další v pořadí je stabilita firmy. Ono je sice pěkné se dostat k nějakému start-upu, kde mi dají nehorázné peníze, protože investor má spoustu peněz a je to pro něj zábava. Ale co se stane za půl roku, bude tu ještě ta firma? Mám už tak spoustu aktivit, natož každou chvíli si hledat novou zajímavou práci.

    A v neposlední řadě samotné peníze. Přeci jen mám nějaké závazky a touhy. Ale pokud mohu bydlet, cestovat a mít své koníčky, pak jsem spokojen.

    Tak. A teď když se to zkombinuje, co vyjde? Že potřebuji stabilní práci se zajímavými projekty a dobrými lidmi. Našel jsem si před pěti lety Seznam a jsem spokojen. Laťka je opravdu vysoko a těžko mne někdo přetáhne jinam. Jediné, co mi schází, je, abych dělal na projektu, který sám používám. Pouze pokud dostanu nabídku navýšenou o poslední detail, má smysl pro mne přecházet. Peníze to rozhodně nejsou.

    Ano, samozřejmě, že větší plat neodmítnu. Ale uvědomuji si, že stabilní firma si nemůže dovolit dávat přemrštěné platy. Stabilní firma musí myslet na budoucnost a pojistit se. Zároveň si také uvědomuji své nefinanční priority a jak je (konkrétně) Seznam splňuje. Takže pokud se nemusím nijak zvlášť omezovat, proč bych si komplikoval život?

    Proto mám vzkaz pro uchazeče i headhuntery: hledat podle platu je nesmysl. Rozumné firmy mají mnohem víc, co nabídnout.

    Roztříštěnost JavaScriptích knihoven

    JavaScript mám docela rád. Kór dnes s ES6, ES7 a Reactem je psaní SPAček radost. Dokud není potřeba využít nějakou knihovnu. To pak přestává být sranda. Původně jsem chtěl napsat do nadpisu nepoužitelnost, ale nakonec lze knihovny nějak použít. Je s tím však spousta trápení.

    Jeden z příkladů může být samotný React a známé a používané knihovny. Vyšel nový React 0.14, který přinesl zajímavé featury. Především stateless komponenty. Samozřejmě, že po pár týdnech jsem se rozhodl novinek využít. Jenže problém – používám taky react-bootstrap a podpora je až od verze 0.27. O pět čísel větší než s kterou jsem web napsal. Za tu dobu samozřejmě stihli sáhnout přesně na ty komponenty, které používám. Takže jsem šel upravovat čerstvě napsaný kód…

    Což ještě není to nejhorší, horší je závislost na knihovnu, která na kompatibilitu kašle. Pak jen koukáte do konzole, jak React warninguje, a doufáte, že autor knihovny to také upraví. Nejpozději do vydání nové verze, která warningy transformuje na errory. S takovou zkušeností mi přijde, že se vyplatí záviset jen na populární knihovny a zbytek si udělat sám.

    Má to ještě jeden ironický důvod. Možnost výběru je zlo. Pro různé úlohy existuje bambilion různých balíků. Každý si napsal něco a řekl si „to se bude někomu hodit, zveřejním to“. Taková blbost! Nyní je těžké se v nepřeberném množství zorientovat. S absencí dokumentací (pahýly mezi dokumentace nepočítám) je pak těžké najít, co vlastně řeší můj problém. A hlavně aby taková knihovna byla udržovaná a stabilní, tedy neprefixovaná nulou.

    Další extrémní příklad je pořádná výzva: ES6 a pár ES7 featur, React, JSX. Zkuste s takovou kombinací rozchodit testy. A to ještě není nejhorší varianta! S takovými AMD moduly to je ještě větší sranda. V práci jsme narazili na bug v testovací knihovně. Možnosti jsou dvě – buď se o verzi vrátit. Nebo ve světě JS je naopak vždy dostupná novější verze, takže upgradovat. To nám ale zařvalo z důvodu absence nejnovějšího Nodu. Čerstvého Node 4. Na Debianu samozřejmě není dostupný, ale ne protože to je Debian, kde jsou balíky se zpožděním, ale protože to chce nový gcc dostupný až pod Jessie. Tedy: chceš testovat JavaScript? Upgradni si operační systém. Jo a taky si to zkompiluj…


    Lehce to zveličuji. JavaScript se mi líbí. Občas ale dokáže vytočit. Proto chci spíš šířit následující zprávu, která se nemusí nutně vztahovat na JavaScriptí svět (jen to pro něj patří z důvodu spoustu edge novinek dvojnásob):

    Chápu, každý chce být autorem populární knihovny. Populární knihovna ale nevznikne rychlým nabastlením, zveřejněním a doufat v open source boha. Už na začátku je potřeba se ke kódu chovat jako bych ho měl maintenovat do konce života. Což znamená si dobře promyslet API hned při prvním návrhu, aby se později měnilo minimálně. Také to znamená být připraven řešit všechny use-casy. Jak by se asi používalo request-get, request-post, request-json, … pokaždé od jiného autora? V neposlední řadě řádně zdokumentovat, co vlastně moje knihovna řeší a popsat jak s ukázkami.

    Databáze v testech?

    Včera jsem měl na první české PyCON konferenci přednášku o pytestu. Na Twitteru se rozjela diskuze o tom, že můj ukázkový test není unittest. Což je teda úplně mimo moji přednášku, protože jsem mluvil o unittestu jako toolu, nikoliv typu testu, vs. pytestu. A integrační test jsem vybral, protože je to prostě dobrá ukázka pro běžnou praxi.

    Každopádně bych chtěl sdílet můj názor na toto téma. V Twitter vlákně se řeší několik věcí – udržovatelnost takových testů, zda mít různé typy separátně, spolehlivost, schopnost najít co nejvíce chyb a hlavně teorie. A jak já rád říkám, teoreticky mezi teorií a praxí není rozdíl, ale prakticky… :-)

    Samozřejmě, nejlepší možný test je unittest, který nepotřebuje nic. Žádné cizí rozhraní, žádnou databázi, žádný přístup na disk ani nikam jinam, prostě nic. Pokud něco takového lze udělat a otestuje se tím vše potřebné, pak máte vyhráno. Pak vás nemusí zajímat boj s integračními testy. I to je důvod, proč Selenium je pro puristy tak děsivé.

    Jenže v praxi to tak nefunguje. V praxi často něco čteme a zapisujeme. Můžeme takovou aplikaci otestovat tak, že vrstvu zařizující komunikaci s databází či něčím jiným nějak nahradíme/vyřadíme. Jenže, co jsme tím získali? Nic moc ve skutečnosti. Máme otestované algoritmy a provázanost naší aplikace, ale vůbec nevíme, zda funguje. Mohu otestovat, že moje aplikace správně vygenerovala XML a nikam ho neodeslat. Jenže po nasazení mi mohou klidně poslat neskutečné množství uhlí

    OK, špatný příklad. Ale ten příběh mne fakt pobavil. :-) Vezměme jiný příklad: chci otestovat statistiky. Ano, mohu při testování podsunout aplikaci data, která chci, a podívat se, zda se čísla přelila takhle tam, tohle zase tam, a výsledek je takovýhle. Jenže… jenže po nasazení do provozu zjistím, že v SELECTu je překlep v jednom případě. Který sice testuji, ale bez databáze.

    A takhle to je se spoustou aplikací. Proto, abychom měli větší jistotu, testujeme s databází. A pokud je to pouze s databází, říkáme tomu stále interně unittest. Pokud je to unittest bez databáze, říkáme tomu pure unittest. Pokud voláme i cizí rozhraní, zapisujeme na disk, …, říkáme tomu integrační test.

    Řešili jsme, jak takové testy rozdělit. Původně jsme je měli rozdělené na „unittesty“ a na integrační testy. Ale byl v tom guláš, co kde vlastně testujeme a proč. Někdy bylo prostě vhodnější nechat integrační test, někde zase naopak. Takže testování nějaké funkcionality máme v jednom souboru a rozdíl mezi testy řešíme označením pomocí dekorátoru @pytest.mark.*. Tak hned vidím všechny testy pohromadě.

    A jak nám to funguje? Velice dobře. Testy máme rychlé, v průměru 125 ms na test (s tím, že to hodně zvedá především pár integračních testů kvůli účetnictví pod Windowsem). S databází nemáme problémy. Dokonce CI tool (u nás Jenkins) si pro každý běh nejprve vytvoří novou databázi (cca deset vteřin). Tedy máme pod kontrolou, co v databázi je. Najde nám to více chyb. Nespočet chyb bylo v SQLku… Nemluvě o tom, že nemusíme složitě mockovat.

    Abych to shrnul: integračních testů se nezbavíme a jsou potřeba. S knihovnou unittest je těžké takové testy udržovat a měli jsme s tím opravdu problémy. Ale díky pytestu a tipům, o kterých jsem mluvil a dříve i psal, už problémy žádné nemáme, ba dokonce je to stejně snadné jako jednotkové testy.

    P.S.: Omlouvám se všem puristům za zemřelá koťátka. :-)

    Whisky festival 2015

    Z letošního whisky festivalu jsem si odnesl několik poznámek:

    Až do roku cca 1950 byla whisky bílá. Pilo se tak rychle, že v sudu moc dlouho nevydržela. Takže barva, vůně i chuť byla úplně někde jinde, než jak ji známe dnes. Když dnes takový vzorek pošlete whisky specialistům, odpoví ve stylu „to není whisky, co to je?“ Jelikož na toto téma nemohu nic dohledat, věřím John Lamondovi jen tuto část, a pokračování, že žena chtěla před mužem ukrýt alkohol, na který narazil o několik let později… mu už nevěřím. :-)

    John Lamond s námi prošel šest ochutnávek a chci vypíchnout jedinou – Ben Nevis. Konkrétně jsme pili dvanáctiletou ze sudu po sherry z roku 2002. Koukal jsem, že se nedá sehnat, tak snad bude stačit jiný rok a především ze sudu po sherry. Je to podceňovaná whisky a sám, když jsem byl na whisky cestě po Skotsku, jsem byl celý den kolem ní, a vůbec ji nenavštívil. Jaká škoda. Začátek byl velmi příjemně ovocný a myslím, že by ji měli rádi i newhiskaři. Co ale na whisky mám hodně rád je finish. Tak se podle mého názoru pozná opravdu dobrá whisky. Tento Ben Nevis 12yo měl krásný dlouhý ocas s nádechem kouře a štiplavosti. Hned bych si dal zase.

    Ale moc mne neposlouchejte, jelikož whisky znalec zřejmě vůbec nejsem. Letošní whisky roku vyhrála Kavalan, tak jsem ji chtěl samozřejmě zkusit, když už ji tam měli. Loni jsem poprvé zjistil, že asijské whisky mohou být opravdu dobré a Kavalan příjemně překvapila. Každopádně výherní lahev mi vůbec nechutnala. Marně jsem v ní hledal, za co vlastně vyhrála.

    Aspoň příjemným překvápkem je, že Lost Distillery lze pořídit již i u nás. Prý ve Warehouse #1 kousek od Křižíkovy. Loni jsem si velice oblíbil kouřový Gerston, který chci mít doma. Od loňska navíc přivedli zpět na svět další distilerky, které stojí za to vyzkoušet.

    Na závěr jsem zkusil GlenDronach 18yo (Old Allardice), která je popisována jako „výbuch chutí“ a lepší nechat na závěr večera. Rozhodně to je zajímavá whisky, kterou musím ochutnat znovu a prozkoumat všechny chutě a rozhodnout se, zda se mi to líbí, či ne. Přeci jen jsem ji zkoušel na závěr večera. :-)

    Jediné vím jistě, je to další whisky ze sudu po sherry a zdá se, že krom kouřových whisky budu ujíždět i na ty po sherry. Trochu jsem se bavil na toto téma a zjistil, že sudy po sherry jsou pětkrát tak dražší, než jiné sudy. Proto to není úplně obvyklá whisky. Některé distilerky dokonce kupují celé roční výroby sherry, aby měli zajištěny sudy. Ale samozřejmě o obsah jim nejde, ten klidně někde rozdají.

    Už se těším na další ročník!

    Zbytečné optimalizace

    Vývojáři mají skvělý dar. Optimalizovat to, co je nejvíc zbytečné. Aneb příklad z praxe. :-)

    Potřebovali jsme funkci batches s následujícím chováním podporující jakýkoliv iterable:

    >>> list(batches.batches(range(10), 5))
    [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
    >>> list(batches.batches(range(10), 3))
    [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

    Udělali jsem tedy základní implementaci během minutky:

    def batches(iterable, batch_size):
        bucket = []
        for item in iterable:
            bucket.append(item)
            if len(bucket) == batch_size:
                yield bucket
                bucket = []
        if bucket:
            yield bucket

    Jenže… jenže to nevypadá sexy. Přeci to musí jít udělat nějak cool! A taky efektivněji. Například pro velký batch_size to musí být náročné. Pojďme zkusit udělat generátor generátorů.

    def batches(iterable, batch_size):
        def batch_generator():
            for x in range(batch_size):
                yield next(iterable)
    
        while iterable:
            yield batch_generator()

    Vypadá to docela hezky. Jenže to má zásadní problém. Předchozí ukázka totiž nikdy neskončí. Jakmile se dojede vstupní iterable, funkce bude generovat prázdné generátory do nekonečna. To je potřeba nějak vyřešit…

    První nápad je odchytávat StopIteration. Co ale dál? Během chvilky jsme se dostali do velmi nehezkých konstrukcí a stejně zůstali v nekonečné smyčce. Kolegu napadlo si z vnitřního iterátoru předávat, zda bude něco následovat nebo ne. Idea je taková, že si první elementy ohandluju ručně nějak takto:

    def batches(iterable, batch_size):
        def batch_generator():
            try:
                first = next(iterable)
            except StopIteration:
                yield False  # Hele, uz nic neprijde, tak to stopni.
            else:
                yield True  # Jasne, jeste neco prijde.
                yield first
    
            for x in range(1, batch_size):
                yield next(iterable)
    
        while True:
            gen = batch_generator()
            if next(gen):  # Prvni polozka je rucne vlozena.
                yield gen
            else:
                break

    Super! Máme generátory. Generují dokonce správně. Jenže… jenže to vypadá opravdu ošklivě. Navíc je podporován jen takový vstup, který zvládne next. Zkusili jsme testy, zda jdeme dobrým směrem a… horší než jednoduchá jasná implementace! Možná to šetří pamětí, ale podstatně trpí výkon kvůli samému generování.

    Tak znovu na začátek a jinak a lépe. itertools obsahují funkci groupby. S pomocí Google jsme došli k následujícímu řešni:

    def batches(iterable, batch_size):
        iterable = iter(iterable)
        counter = itertools.count()
    
        def func(key):
            return next(counter) // batch_size
    
        for key, group in itertools.groupby(iterable, func):
            yield group

    Vypadá elegantně, ale pokud k tomu nebude výklad třikrát tak dlouhý, jako celá funkce, nebude jasné, co se děje. A výsledek? Taky pomalejší. Výkon s groupby se od předchozího kódu moc neliší.

    Závěr: neoptimalizujte. Optimalizujte až v případě, kdy je to opravdu potřeba. Raději pište čitelný kód. A pokud už bude potřeba optimalizace, ten čitelný kód nemažte – nechte ho vedle toho optimalizovaného. Aspoň budete moci rychle přečíst, co daná funkce dělá, a především zkontrolovat, zda ta zoptimalizovaná dělá skutečně to, co má.

    Coders at Work: rozhovory s velikány v oboru

    Pořídil jsem si knihu Coders at Work: Reflection on the Craft of Programming. Což jsou rozhovory s velikány z oboru. Chtěl jsem se tedy dozvědět nějaké jejich názory. Takový výtažek, který jsem si z toho vzal, někde i sám přidal, tu sdílím.

    Začneme školou: není potřeba. Pro to, abych mohl programovat. Programátorský svět se tak rychle vyvíjí, že se musí učit neustále. Kdyby na škole záleželo, pak by se škola nikdy nemohla opustit.

    V rozhovorech se řešilo i jaké má programování postavení mezi obyčejnými lidmi. Zda by se mělo zařadit mezi základní předměty jako jazyk a matematiku. Nemělo. Základní věci lze naklikat a pokročilé věci stejně nepochopí, jako programátoři si nezvládnou ušít oblek.

    Taky si pamatujete, jak pracovat ve skupině bylo bráno jako podvod? Tak přesně taková je nejlepší praxe: párovat. Pomůže udržet focus na úkol. Aneb kolikrát začnete lelkovat, když se dostanete do slepé uličky a potřebujete odstup? Ten druhý má ten potřebný odstup. Jiný pohled. A tak.

    Párování je dobré taky pro zaučování. Stejně jako jinde se nováček učí pozorováním mistra, aby se jím jednou taky stal, je dobré toto aplikovat i v programátorském světě. Nemusí to být nutně nováček. Spoustu lidí se naučí algoritmy atp., ale nikde se neučí jak debugovat. Což je skill, který trvá dlouho osvojit si. Párováním ho lze získat mnohem rychleji.

    Pokud není dostupný nikdo do dvojice, stačí mluvit s něčím. Třeba na PyCON UK 2014 rozdávali debug kachničky. Často jen říct si, jaký mám problém, co je jeho vstupem a výstupem, stačí k jeho vyřešení.

    Aby ale nebylo potřeba debugovat, Tony Hoare má skvělou frázi, že lepší mít kód, kde samozřejmě není žádný bug, než kde není žádný zřejmý bug. Nebo lépe v originále: „your code should obiosly have no bugs rather than having no obvious bugs“. Pro to je potřeba držet čitelný kód. Základem je formátovat kód, stejně jako formátujeme běžný text. Což Dijkstra podtrhává větou: „if you can't write in your native language, give it up“. Hned druhým důležitým bodem je správné pojmenování. Jsou i programátoři, kteří mají vždy po ruce slovník.

    Čitelnost se pojí i s optimalizováním. Pravidlo je, zůstat u čitelnosti a neoptimalizovat. Protože často, ne-li vždy, programátoři řeší optimalizaci toho, co je cool, zajímavé, náročné. Ale tyhle části nemají žádný zásadní vliv na optimalizaci a co je skutečně potřeba, na to se nesáhne. Proto lepší zůstat u čitelnosti a až když je opravdu potřeba, teprve pak jít optimalizovat. Což má druhou výhodu – čitelnou verzi už mám a tu nesmažu. Nechám ji vedle té optimalizované. Tak bude kód čitelný a pokud se najde chyba, můžeme přepnout na pomalejší správnou. Mimochodem jednodušší je optimalizovat správný kód, než opravovat optimalizovaný kód.

    Postupným vývojem ale kód tak jako tak degraduje. Když se problém zapisuje do kódu, musí se najít způsob, jak to zapsat. Jenže postupem času se problém může trochu změnit. Nebo naopak problému začneme lépe rozumět. Tím nám původní kód přestává vyhovovat a musí se upravit. Protože když se ponechá ve starém znění, kód začne být nečitelný.

    Otázka je, kdy refaktorovat? Někdo říká neustále, někdo po Xtém sprintu, … Udělejte to tak, jak je potřeba. Často stačí držet se skautského „ponechám kód v lepším stavu, než jsem k němu přišel“. Někdy ale je potřeba změnit celou architekturu. To se dá poznat podle pomalosti vývoje, aplikace, bugovosti atp. Vývojáři nejsou ze dne na den horší. To jen kód degraduje a je horší ho udržovat.

    Jak ale tohle vysvětlit produkťákům/klientovi? Nijak. Refaktor je potřeba si plánovat mezi běžné věci. Něco odhadnu na tři dny, tak tam šoupnu čtyři na potřebný refaktor. Pokud potřebuji velkou změnu, třeba frameworku, o čem se nerado slyší, zabalím to do jiného názvu. Třeba údržba pro zrychlení vývoje, aplikace a menší chybovost.

    Není nic horšího, než když se někdo bojí sáhnout na něčí, či dokonce svůj, kód. Na to je lék testování. Testy dodávají jistotu. Čím lépe otestovaná aplikace, tím větší jistota a klid na duši.

    Aby testy měly smysl, je potřeba testovat inkrementálně, stejně jako se píše kód. Nejlépe formou TDD. Často se naráží na problém, že nelze napsat test, pokud nevím, jak kód bude vypadat. To je chyba. Je dobré nejprve vědět, jak chci kód využívat, a poté napsat implementaci. Když udělám opak a zjistím, že chci nakonec používat jinak, musím celé předělat. Pokud z nějakého důvodu takto nelze, aspoň napsat kód nejvíc jednoduše, poté testy, a nakonec udělat refaktor a případné optimalizace.

    Testy poslouží i jako taková dokumentace, pokud jinou nemáte. Což teda je potřeba nějakou dokumentaci mít a hlavně ji nepsat až po napsání kódu, ale už s ním. Protože jen tak zdokumentujete, co je potřeba. Za dva dny si těžko vzpomenete na všechny konstrukce, které jste napsali a hlavně proč přesně.

    Někdy programátoři dokumentují pouze pokud se jedná o znovupoužitelný kód. Na něco takového zapomeňte, znovupoužitelnost neexistuje. Resp. pouze u stejných problému a často ty stejné problémy jsou již vyřešeny. Vaše aplikace bude jednodušší, pokud ji napíšete jednodušeji bez zbytečných vrstev. Líbila se mi hláška, proč OOP a použitelnost moc nejdou k sobě: „you want banana and they carry around implicit environment with gorilla holding banana within jungle“.

    Je to trochu tím, že vývojáři píší kód. A když píšou knihovnu (nebo klidně i celý jazyk, podívejme se třeba na Ruby), chtějí přidat něco svého. Jenže je více věcí, které lze přidat, než věcí, které by se pouze měli přidat. Na to je potřeba cit, který má málokdo. Softwarový vývoj je hodně jiný oproti cokoliv porovnatelnému a stále se učíme jak ho nejlépe vést. I hardware se vytváří jako například stavba domu – cihlu položit na cihlu, sem dám dveře, okno.

    Například na otázku „jak bude dlouho trvat tahle změna?“ máme několik možných odpovědí. Pokud je to změna na jeden řádek, víme, že máme rychle hotovo. Jenže pokud je taková změna nesystémová, víc takových hacků by znepřehlednilo kód a pozdější maintenance. Takže druhá možnost je zahrnout nějaké lepší pojmenování a refaktor okolního kódu. Tím ale pouze oddálíme čas, kdy se dostaneme do situace, jako v prvním případě. Pak je třetí možnost, že to uděláme pořádně. Což znamená úpravu zanést řádně do celé aplikace. A to musí programátoři vidět a dát odhad přibližující se poslední možnosti. Pak by nemělo dojít k nutnosti začít na zelené louce.

    Nejchytřejší programátoři by neměli automaticky o všem rozhodovat. K dobrému rozhodnutí je právě potřeba ten cit. Bez empatie či emoční inteligence půjde těžko navrhnou dobrý jazyk, API, GUI apod.

    Na závěr zmíním, že produkty se netočí kolem technologie, ale kolem výsledku produktu samotného. Technologie je jen prostředek. Takže je jedno, který framework nebo jazyk se použije a není třeba se kolem toho hádat. Já mám rád třeba čokoládový Häagen Dazs, vy zase jinou zmrzlinu. A taky se u toho nehádáme. :-)

    Matka moudrosti: jak testovat

    Nic z následujícího textu není nové. Určitě to někdo v podobném znění už někde sepsal. Ale pořád se na to naráží a opakování je matka moudrosti. :-)

    Extrémní TDD říká, že by se měl napsat minimální test, počkat až selže, poté napsat minimální kód pro průchod testu, ověřit funkčnost, rozšířit test a tak pokračovat do nekonečna. Například:

    1. Napíšu test na zavolání funkce add.
    2. Test selže, protože funkce add neexistuje.
    3. Napíšu funkci add. Jen definici, aby ji šlo zavolat.
    4. Test projde.
    5. Napíšu test, že funkce vrací pro parametry 2 a 3 hodnotu 5.
    6. Test selže, protože funkce nic nevrací.
    7. Napíšu return 5.
    8. Test projde.
    9. Napíšu test, že funkce vrací správné výsledky i pro jiné hodnoty.
    10. Test selže, protože vrací stále samé číslo.
    11. Napíšu algoritmus pro běžná čísla.
    12. Test projde.
    13. Napíšu test na mezní hodnoty.
    14. Test selže.

    Kvůli tomu často lidi na TDD zanevřou a buď testují špatně nebo vůbec. Aspoň mi to tak přijde. Přitom, pokud se to nevezme tak extrémně, je programování mnohem lepší. Mám totiž svoji verzi TDD: zůstávám u psaní testu předem, ale neiteruji tolik vášnivě. Testy spíš beru jako takový popis, co vlastně chci dělat. Dokumentaci, dá se říct. Ono pokud žádná dokumentace neexistuje, dá se právě z dobrých testů něco pořádného vyčíst. Můj styl je:

    1. Napíšu všechny testy, co mne na začátku napadnou, pro funkci add.
    2. Napíšu celou implementaci funkce add pod dozorem testů.
    3. Při psaní samotné funkce (či potom) dopíšu další testy, co mne při vývoji napadnou.

    Nojo, jenže stále tam je ten test na začátku. Jak mám napsat test, pokud nevím co chci programovat? Otázka, kterou slýchám často. Na to snad jen, pokud nevím, co chci programovat, tak snad raději neprogramovat vůbec. :-)

    Na test předem lze nahlížet jako na ukázku předem. Nejprve si tedy udělám kód jak chci asi danou funkci používat. Samozřejmě kód nebude funkční, jde jen o to podívat se, jak skutečně chci funkci využívat. Jakmile zjistím, jak chci funkci využívat, mohu začít psát implementaci. A pokud mám ukázku, upravit ji na test je už drobnost.

    Tím si zároveň ušetříte práci. Udělat dobrý návrh na první dobrou vyjde málokdy. Proto je lepší se nejprve zamyslet nad použitím a podle toho udělat API. Jednodušší upravovat ukázku, než udělat celou implementaci a pak zjistit, že se to používá špatně a je třeba celé přepsat.

    Dokážu si však představit situaci, kdy nejde napsat test dřív. Například při psaní Selenií. Nebo dělám prototyp čehokoliv a opravdu nemám představu, jaký výsledek bude, a nejde mi o to mít po celou dobu plně funkční kód (který z většiny stejně smažu). Pak je potřeba mít na paměti dvě věci:

    1. Test musím vidět selhat. Pokud napíšu test po kódu, musím kód naschvál rozbít, abych viděl, že test opravdu testuje. Nejde nijak jinak podchytit testy, co nic netestují, než ověřením, že opravdu selhávají. A že jsem už na takové narazil!
    2. Pokud programuji z nějakého důvodu bez testu, napíšu kód nejhloupěji, jak dovedu. Abych při pohledu viděl, že tam není chyba. Poté napíšu testy. Nakonec kód mohu zrefaktorovat a případně i zoptimalizovat. Nejvíce chyb totiž nastává při zhezčování kódu, refaktoringu či optimalizaci. Proto je dobré tuto fázi udělat až s testy.

    To jest vše. Kolem testování se dá filosofovat nad více věcmi, ale tohle jsou nejpalčivější otázky, které dostávám a které jsem kdysi sám řešil. Snad jsem vám nějak pomohl. :-)