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.