Kečup nebo tatarku?

Představte si, že k vám na pohovor přijde absolvent filosofie, který se živí jako údržbář v Písku (skutečnost byla opravdu jen trošku jiná). Řekne vám, že volba filosofie byla životní omyl, ale že už půl roku se učí programovat. Nic moc, ale docela mu to myslí, tak mu nabídnete dvojnásobek jeho stávajícího platu… a za týden odepíše, že dostal lepší nabídku v jiné IT firmě. Zhruba tak dnes vypadá trh práce.

O blokování reklam

Čas od času se o něm strhne debata. Jedni reklamu obhajují jako jedinou možnou obživu pro provozovatele webů, druzí poukazují na zahlcení reklamou, její nevkusnost, vlezlost a celkově nižší pohodlí jako důvody pro její blokování. Proč na pohledu druhého tábora nevidím nic špatného (a řadím se do něj) vysvětlím v následujících odstavcích.

Začnu lehkým úvodem do toho, jak funguje současný web. Servery (něčí počítače) vystavují volně dostupný obsah na nějakém portu a nějaké adrese. Kdokoli si o ten obsah může říct HTTP požadavkem. Ve většině případů se stáhne text ve formátu HTML, a je na klientovi, jak s tímto textem naloží. Může ho zobrazit v čisté podobě, vykreslit v ASCII artu v textové konzoli, nechat ho přečíst čtečkou pro nevidomé, možností je nepočítaně. Jedna z nich je předat HTML kód vykreslovacímu jádru moderního webového prohlížeče. I v něm mám řadu možností, jak stažený kód interpretovat - mohu zakázat či povolit vykonávání JavaScriptu, nestahovat obrázky, nespustit Flash, namluvit serveru, že jsem na pomalém připojení nebo že mám slabou baterii.

Dnes se hodně mluví o strojovém učení. Představte si prohlížeč, který by se na základě pohybu očí uživatele učil a při opakovaných návštěvách webu by uživateli prezentoval pouze ty části stránky, kterým v minulosti věnoval pozornost. Aby ho irelevantní bloky nadpisů, textů a grafiky nerušily od toho, co ho ve skutečnosti zajímá. Lákavá myšlenka, o kterou by v případě povedeného provedení mohla mít zájem spousta uživatelů.

Až na to, že něco takového tu dnes už máme, a budí to obrovský rozruch. Blokátory reklamy si udržují seznamy prvků na stránkách, které většinu uživatelů obtěžují, a při vykreslování je automaticky odstraňují. Proč to provozovatelům webů vadí? Vždyť mi poskytli ke stažení obsah, který lze interpretovat různými způsoby, a já si jeden z nich vybral.

Provozovatelé se totiž ve svém obchodním modelu spoléhají na to, že ten kód, který si od nich stáhnu, si můj prohlížeč vyloží tím způsobem, který jim vydělá peníze. Jenže to je zastaralé uvažování, které bude fungovat čím dál tím hůř. Jak z toho ven?

Existují snahy, jak blokování reklam zamezit, ale pokud jste pořádně přečetli předchozí odstavce, tak už víte, že to principiálně není možné. Ano, obě strany mohou reagovat na kroky toho druhého a přizpůsobovat se jim, ale vždy to bude hon kočky s myší a blokující strana bude mít zpravidla vždy navrch, protože principy webových technologií jsou ji nakloněny. Stejně jako při sledování televize mám možnost přepnout program a při čtení novin přeskočit stránku s placenou inzercí, tak i na webu budu mít vždy možnost se reklamám vyhnout. Nad blokováním pop-upů vestavěným přímo v prohlížeči se dnes již také nikdo nepozastavuje.

Tudy tedy cesta dlouhodobě nevede. Provozovatelé webů si musí uvědomit, proč uživatelé mají potřebu reklamy blokovat – protože je vlezlá, agresivní, nevkusná, žere daleko více dat, procesorového času a baterie1. Dokud musím při vstupu na zpravodajský portál zavírat křížkem animovanou a ozvučenou reklamu na mobilního operátora, která mi užírá cenné megabajty z FUP, tak ji prostě blokovat budu. Inzerenti, reklamní systémy a provozovatelé by tedy v první řadě měli reklamám napravit reputaci - servírovat je v takovém množství a kvalitě, aby od nich lidé neutíkali a reklamu neignorovali.

I když se nedívám a neklikám na reklamy na obsahových webech, nepřipadám si jako příživník. Věřím, že jsem provozovateli webu i tak užitečný. Věnuji jim svůj čas, čtu jejich články, přemýšlím nad danými tématy a pokud mě zaujmou, tak je sdílím dál.

Z předchozích odstavců vám může připadat, že jsem pro zrušení oboru marketingu, tak to ale není. Marketing je kreativní obor a na kreativcích je, aby ke mně v pravou chvíli dostali obsah, který mi bude užitečný a bude mě zajímat. Jsem zastávcem native advertisingu. Pokud se forma reklamy skloubí se správným zacílením, nic proti ní nemám. Takže pokud se v mém oblíbeném technologickém podcastu objeví zmínka o kvalitním registrátoru domén nebo o VPS hostingu, je to pro mě trefa do černého.2

Další čtení: The ethics of modern web ad-blocking

  1. Svižnosti webů se zapnutým blokátorem, obzvlášť na mobilních zařízeních, se nic nevyrovná. iOS 9 přišel na 64bitových zařízeních s podporou tzv. content blockers, které přináší do uzavřeného ekosystému Applu nečekanou podporu pro blokování reklam. Každému doporučuji si nějaký nainstalovat.

  2. Na základě reklam v podcastech jsem si koupil už minimálně čtyři produkty. Kdo tohle může říct o bannerech na českých webech?

Dokonalé scrollování přesně tam, kam uživatel potřebuje

Při vývoji nového košíku Slevomatu jsme čelili řadě výzev. Jedna z nich souvisela s tím, že celý košík byl prezentován na jedné stránce a nové kroky prodlužovaly stránku směrem dolů. Pro uživatele to má ten přínos, že na pozdějších krocích nemusí dohledávat, co v košíku vlastně má, ale stačí se podívat na vrchol stránky.

Aktuální košík

Celý košík je koncipovaný jako SPA1. I když jsme měli wireframy s návrhem podoby jednotlivých kroků košíku, statický návrh, který sám o sobě vypadá dobře, neposkytuje kompletní obraz o fungování aplikace. Musel jsem přijít na to, jak udělat přechod mezi jednotlivými kroky košíku tak, aby se uživatel hned zorientoval a měl představu, kde se nachází.

V momentě, kdy uživatel vyjádří svůj úmysl (pokračuje v košíku do dalšího kroku, nebo se vrací zpět) by mu aplikace neměla klást žádné překážky a další „úkoly“ před tím, než bude moct pokračovat v tom, co chce udělat. V případě našeho košíku se tedy nabízí automatické scrollování, aby uživatel poté, co klikne na „Pokračovat“, mohl rovnou vybírat platbu a nemusel už sahat na kolečko myši.

První naivní implementace posouvala stránku vždy tak, že horní okraj aktuálního kroku košíku byl srovnaný s horním okrajem viewportu:

$('html, body').animate({
    scrollTop: $target.offset().top
}, 500);

Košík -- prvek je zarovnaný s horním okrajem

Stránka se hýbala pokaždé a někdy urazila i zbytečně dlouhou vzdálenost, což bylo nepříjemné pro oči. Řekl jsem si, že cílem vlastně není, aby se aktuální krok objevil vždy na vrcholu viewportu, ale pouze aby byl celý viditelný a to kdekoli na obrazovce.

První optimalizace spočívala v tom, že pokud už je aktuální krok celý ve viewportu, nemusím scrollovat vůbec. K tomu potřebuji výšku viewportu a relativní pozici prvku vůči němu. Žádné scrollování je nejlepší scrollování!

var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var isWholeElementVisible = $element[0].getBoundingClientRect().top >= 0 &&
    $element[0].getBoundingClientRect().bottom <= viewportHeight;
if (isWholeElementVisible) {
    return;
}

Pokud tato podmínka neprošla, přistoupil jsem opět k naivnímu scrollu k hornímu okraji. To ale vedlo ke stejně zbytečným a příliš velkým pohybům jako před touto optimalizací. Napadlo mě, že bych mohl nějak pracovat s dolním okrajem prvku a zarovnávat ho ke spodnímu okraji viewportu:

Košík -- prvek je zarovnaný s dolním okrajem

Ale v jakém případě? Nemůžu tak scrollovat pokaždé, protože by to since jakž-takž fungovalo pro přechod do následujících kroků, ale při návratu do předchozích by opět docházelo k většímu pohybu stránky, než je ve skutečnosti potřeba. A to je ten pravý klíč - porovnám vzdálenost, jakou musí stránka cestovat pro zarovnání horního okraje prvku s horním okrajem viewportu a dolního okraje prvku s dolním okrajem viewportu a zascrolluju tam, kam je to blíž!

Ze scrollování na dolní okraje musím ještě vyřadit prvky, které jsou vyšší než celý viewport, protože uživatel by neměl přijít o začátek kroku, kde může být jeho název, vysvětlivky nebo třeba i část formuláře, který musí vyplnit.

var currentScrollPosition = window.scrollY;
var scrollingOffsetTop = $element.offset().top;
var offsetToScrollTo = scrollingOffsetTop;
var fitsInViewPort = $element.height() < viewportHeight;
if (fitsInViewPort) {
    var scrollingDistanceToTop = Math.abs(currentScrollPosition - scrollingOffsetTop);
    var scrollingOffsetBottom = scrollingOffsetTop + $element.outerHeight() - viewportHeight;
    var scrollingDistanceToBottom = Math.abs(currentScrollPosition - scrollingOffsetBottom);
    if (scrollingDistanceToBottom < scrollingDistanceToTop) {
        offsetToScrollTo = scrollingOffsetBottom;
    }
}

$('html, body').animate({
    scrollTop: offsetToScrollTo
}, 500);

Toto řešení se mi už celkem líbilo. Poslední detail, který jsem upravil, byla délka trvání animace. Pro větší vzdálenosti proběhl scroll příliš rychle a výsledný efekt opět nebyl příjemný. Nastavil jsem animaci tedy tak, že pokud během scrollu urazí stránka více jak 60 % viewportu, prodloužím výchozí délku o 30 %:

var duration = 500;
var scrollingDistance = Math.abs(currentScrollPosition - offsetToScrollTo);
if (scrollingDistance / viewportHeight > 0.6) {
    duration *= 1.3;
}
$('html, body').animate({
    scrollTop: offsetToScrollTo
}, duration);

Mám za to, že nad pozicí při scrollování je potřeba uvažovat vždy a nezůstat u naivního řešení v první ukázce. Věřím, že popsaný postup najde uplatnění nejen u jednostránkových košíků.

Osobně se jako doma cítím spíše při programování backendu, ale baví mě občas skočit i na frontend a vyřešit nějaký zajímavý problém - počítání se souřadnicemi na displeji je úplně jiná liga, než psaní SQL dotazů a plnění šablon2. Minulý týden jsem např. strávil nějakou dobu3 laděním pohybu vinylové desky, aby se plynule zastavovala a rozjížděla v závislosti na kurzoru uživatele.

  1. Single-page application – po prvotním načtení už veškerá uživatelova interakce a komunikace se serverem probíhá pomocí AJAXu a nedochází ke znovunačítání stránky, což vede k rychlejší práci s aplikací a zvýšenému uživatelskému komfortu.

  2. Samozřejmě, že práce na backendu není jen o tomhle, ale s klientskými aplikacemi si připadám tak nějak blíž lidem 😉

  3. Víc, než jsem ochotný přiznat.

Zbavte se větví v kódu pomocí promises

Nemám rád rozvětvený kód. Více větví (if/elseif/else) představuje více kombinací k testování, komplikovanější a křehčí kód. Řady ifů se dá zbavit pomocí polymorfismu, to je známá a věřím, že dostatečně zakořeněná technika.

Dnes chci ale představit způsob, jak se můžete zbavit ifů, pokud splňujete tyto dva předpoklady:

  1. Váš kód je asynchronní.
  2. Na něco čekáte.

S asynchronním kódem se můžete běžně setkat ve světě JavaScriptu – v prohlížeči i na serveru v node.js. PHP může být asynchronní, pokud využijete ReactPHP. Všechny tyto implementace mají společné to, že běh aplikace je řízený pomocí event loop. Blokující operace, jako provádění HTTP requestů, dotazování do databáze, čtení z disku apod., se v asynchronním prostředí stávají neblokujícími. To znamená, že během čekání na jejich výsledek (odpověď od vzdáleného serveru, výsledek dotazu, obsah souboru) můžete spouštět jiný kód, proces aplikace se tak nikdy nenudí a využíváte efektivněji systémové prostředky.

Druhou podmínku splňujete, pokud váš kód obsahuje podmínky ve stylu:

  • „Už doběhl ten dotaz?“
  • „Podařilo se navázat spojení, nebo stále ještě čekáme?“
  • „Uplynulo X sekund?“

Cílem je upravit kód tak, aby fungoval, pokud už daná záležitost byla, nebo ještě nebyla splněna, a nemusel kód větvit před/po splnění.

Vezměte si následující příklad: Proces má vykonat nějakou práci a zároveň zůstat alespoň 5 sekund naživu, aby Supervisor jeho spuštění považoval za úspěšné. S běžným sychronním kódem bychom postupovali takto:

$startTime = microtime(true);

// vykonávám práci...

$stayAliveSeconds = 5;
$uptime = microtime(true) - $startTime;
if ($uptime < $stayAliveSeconds) {
    usleep(round(($stayAliveSeconds - $uptime) * 1000000));
}

exit(0);

Ošklivý if, který nás donutí při testování danou metodu spustit alespoň dvakrát.

Díky tomu, že naši consumeři front z RabbitMQ běží pod knihovnou Bunny a tedy v rámci event loop z ReactPHP, můžeme namísto řešení výše použít promises.

Promise je objekt, který se může nacházet ve tří stavech. Pending, resolved a rejected. Při pending čeká na výsledek, při resolved ho už získal a rejected představuje selhání. Na promise se pomocí metody then() navěšují zájemci o výsledek. Vedle promise žije ještě objekt deferred, který slouží jako ovladač ke své promise. Určuje, kdy bude jeho promise splněna. Pro splnění zapouzdření byste ven měli dát k dispozici pouze objekt promise, nikoli deferred.

To, co vám pomůže zbavit se ifů v kódu, je právě chování metody then(). Když ji voláte, tak nezáleží na tom, v jakém stavu se právě promise nachází. Pokud ještě není splněná, tak bude předaný callback zavolaný později, jinak okamžitě.

Příklad s usleep() se dá přepsat takto:

$stayAliveDeferred = new \React\Promise\Deferred();
$this->loop->addTimer(5, function () use ($stayAliveDeferred) {
    $stayAliveDeferred->resolve();
});
$stayAlivePromise = $stayAliveDeferred->promise();

// vykonávám práci...

$stayAlivePromise->then(function () {
    exit(0);
});

Pokud první část zrefaktorujete do zvláštní třídy PromiseTimer, protože se tato logika v kódu často opakuje, tak se kód zredukuje na příjemnější:

$stayAlivePromise = (new \PromiseTimer($this->loop))->wait(5);

// vykonávám práci...

$stayAlivePromise->then(function () {
    exit(0);
});

Zbavil jsem se nejen jakýchkoli ifů, ale i všech počtů s milisekundami.

Stejný trik lze použít na frontendu při získávání dat AJAXem. Pokud má o ta samá data zájem více komponent zároveň, ale chcete je od serveru žádat až pokud si o ně jedna z nich řekne, najednou ve vašem kódu bojujete s těmito stavy: Nikdo si o ta data ještě neřekl, data se právě stahují ze serveru, data už máme. Abyste se vyhli duplicitním požadavkům na server a dalším chybám, které se mohou projevit třeba v momentě, kdy se sejde rychle klikající uživatel na pomalém připojení, můžete napsat sadu nepřehledných a těžko testovatelných ifů, nebo využít promises. Veškerý kód, který má o data zájem, si o ně řekne pomocí then() – nebude tedy předpokládat, že už jsou stažena, ale pokud už jsou, zavolá se předaný callback okamžitě:

getProducts().then(function (products) {
    // ...
});

Why Is Everyone Outraged?

Phil Sturgeon naprosto racionálně, objektivně a se zdravým odstupem o nedávných kauzách ohledně Code of Conduct v PHP, politické korektnosti a rovnosti v IT:

So, instead of freaking out about problems you don’t understand, assuming everyone is just being overly sensitive, being absolutely awful to under-represented groups when they point out reasons they feel uncomfortable in the tech community, then having the gall to suggest there are far fewer of these people in the tech community because they aren’t as interested in tech… maybe take a few steps back and think about that whole situation.

Osobně jsem nikdy nepocítil potřebu po Code of Conduct v komunitách, ve kterých se pohybuji, ale také by mě v žádném případě nenapadlo zesměšňovat projevy těch, kteří po CoC volají. Nejsem v jejich kůži a respektuji, že tu potřebu mají. A nenávistné a trollící projevy těch, kteří proti CoC brojí, asi opravdu dokazují, že ho potřebujeme.

Slevomat Coding Standard

English version of the article is available on Medium.

Každý vývojářský tým by měl mít k dispozici kvalitní podpůrnou infrastrukturu, která pomáhá zajišťovat a vynucovat konzistentní výstupy všech jeho členů. Coding standard je jednou z mnoha věcí, které drží projekt pohromadě.

Ze zdrojového kódu by nemělo být poznat, kdo ho psal. Celý tým by měl mít jednotné zvyklosti. Některé (způsob formátování) jdou kontrolovat automaticky, jiné (např. dodržování navržené architektury) je potřeba řešit při code review.

Ve Slevomatu máme již rok a půl velmi striktní standard, který nám v každém pull requestu za pomoci PHP_CodeSnifferu kontroluje Jenkins. Před šesti měsíci jsme k základu v podobě Consistence Coding Standardu přidali řadu pokročilých sniffů, které jsme měli provizorně commitnuté v našem privátním repozitáři. Některé podporují i automatické opravy, což ulehčuje jejich integraci do projektu.

Na dnešek jsem čekal hodně dlouho. Tyto sniffy konečně vydáváme jako open-source pro veřejné použití. Vypíchnu zde ty, které považuji za nejzajímavější a nejužitečnější:

Nepoužité privátní properties a metody

SlevomatCodingStandard.Classes.UnusedPrivateElements

PHP_CodeSniffer je sice nevhodný pro statickou analýzu zdrojových kódů, protože umí analyzovat najednou pouze jeden soubor a nemá přístup k reflexi, ale některé specifické kontroly pomocí něj provádět jdou. Tento sniff detekuje nepoužité a write-only privátní properties a nepoužité metody, které lze bez obav smazat. Díky tomuto sniffu se pravidelně zbavujeme mrtvého kódu při refaktoringu a zbytečně injektovaných nepoužívaných závislostí v konstruktoru.

Čárka za posledním prvkem pole

SlevomatCodingStandard.Arrays.TrailingArrayComma

Čárka za posledním prvkem ve víceřádkovém poli zjednodušuje přidávání dalších prvků a zpřehledňuje verzovací diffy.

Zákaz Yoda podmínek

SlevomatCodingStandard.ControlStructures.YodaComparison

Pokud máte radši klasické pořadí při zápisu podmínek namísto Yoda stylu, tento sniff dokáže najít a dokonce i automaticky opravit prohřešky.

Abecedně seřazené uses

SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses

Dobrý coding standard by měl vynucovat sjednocení podoby veškerého kódu, co jde objektivně sjednotit. Tento sniff odbourává hádky o tom, jak řadit importy z jiných jmenných prostorů na začátku každého souboru.

Nepoužité uses

SlevomatCodingStandard.Namespaces.UnusedUses

Další detekce mrtvého kódu. Proč trpět nadbytečné uses, když je tento sniff umí najít a dokonce i smazat? Poradí si i s Doctrine anotacemi.

Začněte je používat ještě dnes!

Zmíněné sniffy nejsou jediné, které jsme dnes vydali. Jejich kompletní výčet a vyčerpávající návod k použití celého standardu, ale i třeba jen pár jednotlivých sniffů, společně se zdrojovými kódy naleznete na GitHubu. Kód považujeme za tak prozkoušený a stabilní, že jsme neváhali vydat verzi 1.0.0.

Detekce neuzavřených transakcí

Vezměte si následující kód:

public function processRow(Row $row)
{
    $this->databaseConnection->begin();
    try {
        // nějaké počáteční kontroly
        // $isProcessed = ...

        if ($isProcessed) {
            return;
        }

        // spousta práce se zpracováním row

        $this->databaseConnection->commit();
    } catch (\Exception $e) {
        $this->databaseConnection->rollback();
        throw $e;
    }
}

Spatřili jste tu chybu? V případě, že $isProcessed === true, dojde k opuštění metody bez commitu či rollbacku transakce, čímž zůstane v databázi otevřená až do uzavření spojení. A může zbytečně držet příliš dlouho zámky, o které mohou mít zájem ostatní vlákna aplikace.

Pokud by vám nevadily zamčené řádky, tak se vám stejně nebude líbit další důsledek tohoto bugu:

$this->databaseConnection->begin();
try {
    // volaná metoda vyhodnotí předaný řádek jako $isProcessed
    $this->processRow($fooRow);
    $this->databaseConnection->commit();
} catch (\Exception $e) {
    $this->databaseConnection->rollback();
    throw $e;
}

V tomto případě totiž commitujete či rollbackujete jinou transakci, než byste čekali!

S lepším transakčním API se nastalá situace dá detekovat:

$databaseTransaction = $this->databaseConnection->begin();
try {
    // volaná metoda vyhodnotí předaný řádek jako $isProcessed
    $this->processRow($fooRow);
    $databaseTransaction->commit();
} catch (\Exception $e) {
    $databaseTransaction->rollback();
    throw $e;
}

V tomto případě je databázová transakce reprezentovaná odděleným objektem, takže je vždy jednoznačně určeno, s jakou konkrétní transakcí pracujeme.

Jak ale detekovat, že nám v aplikaci zůstává viset nevyřešená transakce? Pomůžeme si destruktorem:

class DatabaseTransaction
{

    //...

    public function __destruct()
    {
        if (!$this->resolved) {
            // destruktor nemůže vyhazovat výjimky
            trigger_error('Unresolved transaction!', E_USER_NOTICE);
        }
    }

    //...

}

Kdy ale PHP destruktor objektu zavolá?

The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.

V závislosti na podobě implementace DatabaseConnection se destruktor zavolá jakmile PHP opustí metodu, ve které proběhlo přiřazení $databaseTransaction, a nebo taky ne, třeba v případě, že si všechny transakce ukládáme do interního pole pro pozdější odkazování.

Pokud se tedy destruktor transakce zavolá ihned po opuštění metody, tak v zalogované chybě dostaneme stack trace, ve které snadno dohledáme to místo, kde je špatně ošetřená transakce, a budeme ho moci opravit.

Pokud se destruktor zavolá až při shutdown sekvenci, tak už nám stack trace chyby o místu, kde vzniká neošetřená transakce, nic neřekne. V případě, že máte velkou mnohovrstevnatou aplikaci s mnoha transakcemi, se takové místo hledá velmi těžko.

Přišel jsem ale s funkčním řešením, které v této situaci debugging transakce usnadní. V PHP lze instanciovat výjimku, aniž bychom jí vyhazovali, a takto vytvořená výjimka si v sobě nese stack trace z místa svého vzniku!

class DatabaseTransaction
{

    /** @var UnresolvedTrasactionException */
    private $originException;

    public function __construct(DatabaseConnection $databaseConnection/*, ...*/)
    {
        // ...
        $this->originException = new UnresolvedTrasactionException();
    }

    public function __destruct()
    {
        if (!$this->resolved) {
            \Tracy\Debugger::log($this->originException);

            // destruktor nemůže vyhazovat výjimky
            trigger_error('Unresolved transaction!', E_USER_NOTICE);
        }
    }

    //...

}

Pro každou započatou transakci tedy vytvořím výjimku, kterou v případě nevyřešené transakce zaloguji a získám tak místo, kde chybná transakce vznikla. V destruktoru stále vyvolávám notice, aby si programátor této chyby všiml i během vývoje, kdy běžně složku s logy nesleduje.

Z hlediska čistoty a architektury kódu jde samozřejmě o dost neobvyklé až šílené řešení, které bych nikdy při code review nevpustil do běžné business logiky aplikace, kde si vývojář musí vystačit s běžnými vyjadřovacími prostředky a návrhovými vzory z OOP, ale v tomto případě jde o infrastrukturní pomocnou záležitost, která se v celé aplikaci vyskytuje právě jednou a nijak neovlivňuje architekturu zbytku aplikace. Pro usnadnění debuggingu a tedy ušetření času vývojáře jsem ochotný překročit hranici, ke které bych se běžně vůbec nepřiblížil.

What is Code?

Naprosto epický článek extrémní délky (38 tisíc slov) se spoustou animovaných ilustrací, který popisuje vše okolo vývoje software, co potřebujete vědět. Od pochopitelného popisu, jak funguje procesor a co všechno se musí stát, aby se znak stisknutý na klávesnici zobrazil na obrazovce, přes programovací jazyky, knihovny, algoritmy, debugging, datové struktury, databáze a verzování, až po vývojářské konference, flamewary a management. Pokud zvažujete nebo již máte kariéru v softwarovém vývoji, tohle je pro vás skutečně must-read.

Začněte monolitem

Martin Fowler reaguje na aktuální módu mikroservisní architektury tvrzením, že začínat vývoj nové aplikace od mikroservis je nebezpečné, protože dopředu nevíte, jak do nich projekt rozdělit, a spíše si špatně aplikovanou architekturou uškodíte.

Any refactoring of functionality between services is much harder than it is in a monolith. But even experienced architects working in familiar domains have great difficulty getting boundaries right at the beginning. By building a monolith first, you can figure out what the right boundaries are, before a microservices design brushes a layer of treacle over them.

Mikroservisy jsou sice výhodnější z dlouhodobého hlediska, protože lze vývoj projektu rozdělit a škálovat do více nezávislých týmů, ale zároveň s sebou nese i nevyhnutelnou režii při navrhování a implementaci API, reagování na selhávající požadavky, nemožnost stahovat si všechna data z jedné databáze naráz apod. A pokud vyvíjíte něco nového, nevíte, zdali daný projekt bude mít takový úspěch a růst, aby se mikroservisy a pomalejší příchod na trh vyplatily.

When you begin a new application, how sure are you that it will be useful to your users? It may be hard to scale a poorly designed but successful software system, but that‘s still a better place to be than its inverse. As we‘re now recognizing, often the best way to find out if a software idea is useful is to build a simplistic version of it and see how well it works out. During this first phase you need to prioritize speed (and thus cycle time for feedback), so the premium of microservices is a drag you should do without.

Objektivní srovnávání technologií

David Grudl na Twitteru vyzval vývojáře ke srovnání vývoje webové aplikace v Nette oproti řešení v JavaScriptu. Ať už by v rychlosti vyhrála jakákoli ze soutěžících technologií, zastávám názor, že to nic nevypovídá o její vhodnosti pro dlouhodobý vývoj a údržbu seriózních aplikací.

Tato srovnání mají mnoho společného se syntetickými benchmarky. To, co se v nich testuje, neodpovídá tomu, co v běžném provozu aplikace provádí, a zároveň hrubý výkon frameworku není to hlavní kritérium, které by vývojáře při výběru mělo zajímat.

Při vývoji není důležité, za jak dlouho dokáže vývojář na zelené louce nabušit první verzi aplikace podle pevného zadání, ale řada jiných kritérií:

  • Rozšiřitelnost a udržovatelnost aplikace, jak snadné je ve zdrojovém kódu provádět změny
  • Testovatelnost a pokrytí testy
  • Stav a předatelnost zdrojových kódů jinému vývojáři či týmu
  • Přívětivost uživatelského rozhraní
  • Dostupnost a cena vývojářů se znalostí technologie
  • Dokumentace, komunita a budoucnost technologie

Sám jsem se před třemi lety účastnil Souboje frameworků. Úkolem bylo, podobně jako v Davidově výzvě, za jeden den vyvinout e-shop s administrací. Každý ze soutěžících k úkolu přistoupil jinak a výsledné pořadí ne nutně odpovídalo tomu, jak by si daná aplikace vedla dlouhodobě. Např. vítězný Jakub Vrána ke tvorbě administrace použil svůj Adminer Editor, což mu v soutěži ušetřilo spoustu času, ale generovaná administrace na základě definice tabulek v relační databázi neposkytuje žádný prostor pro customizaci chování a tudíž přináší nižší uživatelský komfort.

Do srovnání samozřejmě promlouvá i zkušenost jednotlivých účastníků – a to jak celkově s programováním, tak s konkrétní soutěžní technologií.

Jak tedy postupovat při výběru technologie, když ji nám nepomůže vybrat jednodenní hackaton? Když jsme na konci loňského srpna začínali s vývojem nového nákupního košíku na Slevomatu, věděli jsme, že chceme, aby celý fungoval jako single-page aplikace a veškerá komunikace se serverem probíhala pomocí AJAXu. Potřebovali jsme technologii, která by do šablony na straně klienta promítala stav dat na serveru. Jako vhodné kandidáty jsme vybrali React, Knockout a šablonovací engine Handlebars. Strávil jsem tři dny porovnáváním těchto technologií tak, že jsem v každé naimplementoval první krok košíku, a posléze porovnával, která technologie je nejbližší tomu, jak ve firmě vývoj probíhá, odhadoval, s čím by mohly být v budoucnu problémy a jak snadno nám to či ono pomůže řešit. Vybral jsem Knockout a košík jsme o dva měsíce později úspěšně spustili bez nějakých větších problémů na klientu ani na serveru.

Vybral jsme tedy správně? Zádrhel spočívá v tom, že to nelze určit. Jak by vývoj probíhal v případě, kdybych zvolil React? Možná by byl košík hotový o dva týdny dříve, ale po spuštění bychom přišli na nepříjemný a těžko odladitelný bug. Možná bychom termín nestihli. Možná by byl při překreslování DOMu výkonnější, ale kodér by nedokázal upravovat jeho šablony. Pokud by nás opravdu zajímalo, zdali jsme vybrali dobře, mohli bychom se pokusit celé dva měsíce vyvíjet paralelně v obou technologiích zároveň, což by jednak stálo více peněz, a druhak bychom opět narazili na různou úroveň zkušeností vývojářů, srovnání by tedy opět nebylo objektivní.

Pokud by mě po dokončení košíku v Knockoutu zajímalo, jak by si vedl React, a pustil se do implementace v něm sám, opět bych nedokázal technologie srovnat objektivně, protože jsem při vývoji první verze nabral zkušenosti, které bych v Reactu aplikoval a dokázal se tak vyhnout některým slepým uličkám. Spravedlivé srovnání tedy nejenže nelze získat od více různých vývojářů, ale ani od jednoho.

Při výběru technologie berte vpotaz, jaká od ní máte očekávání a zdali je dokáže splnit, dovednosti a zkušenosti lidí, kteří s ní budou pracovat, a moc se netrapte úvahami, jak by projekt mohl dopadnout, pokud byste se na začátku rozhodli jinak. 1

  1. A určitě nehleďte na výkonnostní benchmarky, ankety popularity a co používá vaše oblíbená osobnost.