phpFashion

Na navigaci | Klávesové zkratky

Alpha-beta-RC is so 20th century

Přestanu používat při vývoji označení testovacích fází alpha/beta/RC a nahradím ho jedním slovem.

Existuje řada způsobů, jak software verzovat. U knihoven se často hovoří o sémantickém verzování, kdy verzi reprezentuje trojice čísel major.minor.patch, a hlavní číslo major musí být zvýšeno pokaždé, když se v kódu objeví zpětně nekompatibilní změna. Což zní na první pohled rozumně, ale z mnoha důvodů to takřka nikdo 100% nedodržuje.

Taktéž existují různé způsoby, jak označovat jednotlivé fáze vývoje, z nichž nejznámější je asi slovní označení alpha/beta/RC. V beta fázi by měl být produkt kompletní co se vlastností týče a dále by se měly opravovat chyby, ladit kompatibilita atd. K čemuž se opět přistupuje různě, asi největším extrémem jsou věčné bety, které zpopularizoval Google. Opačným případem je prohlížeč Chrome, který přišel se zrychleným vydáváním verzí a přelévá mezi kanály dev/beta/stable.

Jak vidno, přístupy se u jednotlivých projektů značně liší. Záleží na mnoha faktorech, velikostí týmu počínaje, marketingem konče. Pro mě je prioritou, aby systém vedl k:

  • vydávání odladěných major & minor verzí
  • při maximální snaze zachovat zpětnou kompatibilitu
  • a pokud možno v pravidelných intervalech, včetně přísunu novinek

Což se lehko řekne, mnohem těžší je se k tomu dobrat, navíc aby to bylo v lidských silách.

Flow, které mi celkem vyhovuje a uvedené priority vesměs plní, vypadá následovně:

  1. vývoj probíhá neprve ve větvích (buď veřejně v podobě pull requestů nebo lokálně)
  2. mergnutím do masteru (což je vlastně permanentní alpha-verze) začíná testování. Přičemž žádný commit nesmí rozbít testy, ale může narušit zpětnou kompatibilitu (viz dále)
  3. jednou za půl roku až rok bych rád vydal větší verzi:
    • vytvořím release-větev s názvem jako v3.2
    • musím rozhodnout, které věci vynechat (revert) a které přidat (merge pull requestů a lokálních větví)
    • především však udělat co nejvíc pro zachování kompatibility rozbité v bodě 2)
    • v krátkých intervalech vydávat testovací verze reagující na chyby a připomínky (což bývá zápřah)
  4. a pak po dlouhou dobu udržovat verzi čerstvou
    • cherry-pickováním oprav z masteru (nebo naopak, ale výjimečně někdo připraví pull request oproti release-větvi)
    • vyhýbat se BC breakům (byť je někdy značně ošidné rozlišit, co BC break je)
    • vydávat patch verze (tj. setinkové) v krátkých intervalech

Někomu to připadá jako úplně normální postup, jiní zase brblají, takže musím zmínit, na jaká v reálu narážím úskalí.

S každými novinkami přichází (byť třeba jen hypotetické) BC breaky. To je prostě realita. Přičemž zachovávání kompatibility beru jako velmi důležitou věc a stojí mě tak 70 % času investovaného do vývoje. Je taky nejčastějším důvodem, proč otálím s přidáním nových věcí. V případě Texy to vedlo v podstatě až k ukončení vývoje.

Existuje dost frameworků, které přežily tlustou čáru mezi dvěma major verzemi. Já ji ale dělat nechci. Nechci ani utopit vývoj. Kloním se proto k postupným zdokumentovaným BC breakům u větších verzí. Navádím uživatele co změnit pomocí E_USER_DEPRECATED hlášek. Nejvýhodnější situaci tak mají ti, kteří průběžně updatují.

Nicméně dle sémantického verzování bych proto měl každého půl roku až rok vydat novou major verzi, což se mi nejeví z řady důvodu praktické. Zejména to vyvolává dojem, že přechod bude velmi náročný a uživatelé začnou setrvávat u starých verzí. Což nechci. Proto raději zvedám jen minor verzi (tj. desetinkovou).

Podle sémantického verzování se novinky mohou objevovat jen v major & minor verzích. Vydávání těchto verzí v intervalech kratších než půl roku vytváří dojem, že vývoj uhání příliš rychle, a uživatelé opět přestávají aktualizovat. Ovšem přijít s užitečnou novinkou a odkládat její vydání i půl roku a více, než bude čas na další větší verzi, nepřináší užitek nikomu, a uživatele toužícího po novince tlačí k používání masteru. Což opět nechci. Takže občas zařadím novinky i do bodu 4). Nepříjemné je, že si kvůli tomu vyslechnete dost kritiky, ještě nepříjemnější je, že po letech jejího permanentního poslouchání už nemáte jakoukoliv schopnost rozlišovat kritiku oprávněnou od kritiky české.

Pokud vydám alpha verzi a poprosím o testování, zhostí se toho naprosté minimum lidí. O moc lépe na tom nejsou ani první beta-verze. Teprve až začnu vydávat RC, na fóru to ožije. Což je mnohokrát ověřený fakt. Nepříjemné je, že i když následně vydáte sebestabilnější verzi, bude vám neustále někdo kazit náladu vysvětlováním, jak to babráte a nerozumíte dělení na alpha/beta/RC. Z jeho pohledu jakoby oprávněně, tudíž jsem se rozhodl, že nebudu nadále rozlišovat tyto fáze, protože to nikomu nic nepřináší, cíli udělat co nejlepší stable podřizuji vše nehledě na fázi, a bude stačit vydávat jen číslované -testing verze (v podstatě beta-verze).

Dává smysl mi ukončit tuto testovací fázi a vydat stable ve chvíli, kdy sám jsem spokojený a reportování chyb poklesne na minimum. Samozřejmě mohl bych pár měsíců čekat, ale realita je taková, že aktivita se nastartuje teprve až vydáním stable.

Ještě jednou to shrnu:

  • master je alpha-verze, do které se dostanou jen nadějné novinky + bugfixy, a bez záruky zpětné kompatibility se tu testují
  • jednou za čas vytvořím release-větev, která po sérii -testing verzí řešících mj. kompatibilitu bude završena rychlým vydáním stable
  • mezi masterem a release-větví cherry-pickuji opravy, občas menší novinky, a vydávám setinkové verze, ideálně s jedním RC

Docela jsem zvědavý, co v případě Nette udělá s uvedeným flow rozdělení na malé projekty. Teoreticky bych mohl najet na rychlé verzování u částí, které se budou vyvíjet, a uživatel by si pomocí Composeru poskládal takové Nette, jaké by chtěl. Zatím ale spíš narážím na nejrůznější překážky a vydávání nových verzí je teď mnohem větší dřina.


Jak předávat závislosti v Nette

Jaké jsou best practice pro předávání závislostí presenterům, komponentám a jiným službám v Nette?

Nejprve: nezávisle na frameworku nebo typu tříd platí vždy tohle:

  • povinné závislosti předávat konstruktorem
  • volitelné buď samostatnou metodou, nebo též konstruktorem

Tečka.


Nicméně existuje případ, kdy je předávání závislostí konstruktorem problematické, tzv. constructor hell, tedy situace, kdy předáváme závislosti konstruktorem určité třídě a zároveň jejímu potomkovi. Tohle nastává často u presenterů, kde je právě zvykem mít nějaký BasePresenter. Jako workaround dává DI kontejner v Nette možnost použít v BasePresenteru místo konstruktoru metodu nazvanou injectBase() (případně jakkoliv jinak, jen musí začínat na inject).

Jde o workaround pro base presentery, takže v jiných situacích metody inject nepoužívejte.

Dále předávání závislostí přes konstruktory nebo jiné metody je otravné v tom, že musíte napsat nějaký rutinní kód. Nutnost psát rutinní kód vždy ukazuje na slabé místo samotného jazyka, jedna ze šancí na zjednodušení byla zamítnuta, nicméně některé IDE ho umí vygenerovat a taktéž DI kontejner ve frameworku nabízí zkratku v podobě @inject anotací. Public (!) proměnnou doplníte anotací např. /** @var Model\UserFacade @inject */ a framework sám do ní vloží službu a vyhnete se psaní konstruktoru nebo inject metody.

Protože to není čisté řešení, funguje opět jen pro presentery. Používání anotací rozhodně není best practice, ale je to sakra pohodlné.


Doporučuji podívat se na přednášku Filipa Procházky a přečíst článek Vojty Dobeše Tvorba komponent s využitím autowiringu.


Nette Revolution 2.2

Asi největší revoluce v dějinách Nette, která se vás nijak nedotkne.

Poměrně krátce po vydání velké verze 2.1, která přinesla řadu vylepšení nejen ve formulářích, je tu další velká verze 2.2, která přichází s úplně novou infrastrukturou. Totiž původní repozitář Nette byl smazán rozdělen do 19 nových samostatných komponent: Application, Bootstrap, Caching, ComponentModel, Nette Database, DI, Finder, Forms, Http, Latte, Mail, Neon, PhpGenerator, Reflection, RobotLoader, SafeStream, Security, Tokenizer, Tracy a Utils.

(Podívejte se na video Kvadratura Nette.)

Co se tím mění pro vás, uživatele Nette Frameworku?

Takřka nic. Stále si můžete stáhnout celý framework nebo jej nainstalovat příkazem composer require nette/nette.

Ale zároveň máte možnost používat zcela samostatně šablonovací systém Latte, Laděnku (neboli Tracy), databázovou vrstvu a vlastně cokoliv.

Rozdělení frameworku do menších částí je trend, který už započalo například Symfony nebo Zend Framework 2. Nutnou podmínkou byl vznik respektovaného nástroje pro správu závislostí v PHP, kterým se stal Composer. Pokud si s ním zatím netykáte, neváhejte a seznamte se, není daleko doba, kdy se v PHP knihovny nebudou distribuovat jinak.

Nette se na rozdíl od Symfony nebo Zendu rozdělilo i fyzicky, což znamená, že každá komponenta má vlastní repozitář, issue tracker a číslování verzí. Zároveň každý repozitář obsahuje testy a všechny související soubory. Podívejte se například na Forms, kde najdete příklady použití v examples, v adresáři src/assets JavaScripty a v adresáři src/Bridges kód, který propojuje různé Nette komponenty. V případě formulářů jde o makra pro Latte.

Stejným způsobem jsou pak strukturovány všechny repozitáře.

Rozdělení frameworku je završením dvouleté práce, s cílem zachovat maximální kompatibilitu. Vlastně i proto zůstáváme u čísla verze 2. Jediným výrazným zásekem bylo oddělení Latte, což si vyžádalo přepracovat systém šablon, který v Nette existuje v takřka původní podobně od verze 0.8. Ale postupně:

Tracy

Laděnka byla přejmenována na Tracy, nemusíte se tak trápit s psaním šíleného Nette\Diagnostics\Debugger, nyní stačí Tracy\Debugger. Původní třída je z důvodu zpětné kompatibility stále funkční.

Pokud píšete doplňky, prefix názvů CSS tříd se změnil z nette- na tracy- a třída nette-toggle-collapsed na dvojici tracy-toggle tracy-collapsed. Původní třídy jsou u starých doplňku změněny na nové automaticky.

Latte

Šablonovací systém byl vždy poměrně úzce provázán s dalšími částmi frameworku, zejména třídami z jmenného prostoru Nette\Templating. Aby bylo Latte samostatně použitelné, bylo potřeba mu vymyslet nové snadno použitelné API, které se obejde bez těchto pomocných tříd. A vypadá takto:

$latte = new Latte\Engine; // nikoliv Nette\Latte\Engine
$latte->setTempDirectory('/path/to/cache');

$latte->addFilter('money', function($val) { return ...; }); // dříve registerHelper()

$latte->onCompile[] = function($latte) {
    $latte->addMacro(...); // when you want add some own macros, see http://goo.gl/d5A1u2
};

$latte->render('template.latte', $parameters);
// or $html = $latte->renderToString('template.latte', $parameters);

Jak vidíte, Latte si řeší samo načítání šablon a jejich kešování, čímž pádem původní FileTemplate a vlastně celý Nette\Templating z velké míry pozbývá na smyslu existence. Tyto třídy i nadále fungují a snaží se zajistit kompatibilitu s novým Latte, nicméně jsou zavržené. Ze stejného důvodu jsou zavržené i třídy Nette\Utils\LimitedScope, Nette\Caching\Storages\PhpFileStorage a služba templateCacheStorage.

Naopak Application přináší vlastní třídu Template (nahrazující FileTemplate a zajišťují kompatibilitu) a továrnu TemplateFactory, která rozšiřuje možnosti, jak v presenterech a komponentách pracovat se šablonami.

Ostatní

Třídy Nette\ArrayHash, ArrayList, DateTime, Image a ObjectMixin jsou nyní součástí balíčku Utils, proto i jejich namespace byl změněn z Nette na Nette\Utils. Obdobně Nette\Utils\Neon se stalo součástí balíčku Neon a bude mít namespace Nette\Neon\Neon. Aby změna byla transparentní, vytváří se pro tyto a některé další třídy tiše aliasy. Aliasy ostatních tříd vytváří Nette Loader spuštěný v loader.php a vypisuje přitom varování (abyste mohli svůj kód upravit). Pokud loader.php nepoužíváte (tj. instalujete Nette přes Composer), můžete Nette Loader načíst ručně třeba v bootstrap.php. Od RC4 se Nette Loader spouští vždy.

Alternativně je možné vytvořit v bootstrap.php rovnou všechny aliasy a obejít tak varování. Ale jelikož PHP při vytváření aliasů načítá zdrojový kód tříd, může to vést k mírnému snížení výkonu:

@array_walk(Nette\Loaders\NetteLoader::getInstance()->renamed, 'class_alias');

Zavržena (tj. stále funguje, jen vyhodí E_USER_DEPRECATED) je třída Nette\Utils\MimeTypeDetector, která od PHP 5.3 není potřeba, neboť ji plně nahrazuje rozšíření Fileinfo (pod Windows jej nezapomeňte zapnout v php.ini).

Byla zrušena podpora anotace @serializationVersion a dohledávání tříd pro vlastní anotace – tyto věci nebyly známé ani používané, ale měly negativní vliv na výkon.

A nakonec, chybné odkazy v šabloně nyní začínají hashem, tj. místo error:... se vypisuje #error:, aby když na takový odkaz omylem kliknete, browser nevypsal strašidelnou hlášku. Upravte si proto CSS.

Novinky

Verze 2.2.0 přichází i s řadou novinek.

Formuláře nyní přenášejí parametr do v POST datech, takže nebude strašit v URL. Přibyly nové validátory Form::MIN a Form::MAX. A do funkcí obsluhující událost onSuccess se nyní jako druhý parametr předávají hodnoty formuláře, takže ušetříte psaní $values = $form->getValues().

V databázi přibyla nová funkce fetchAssoc(). Můžete se podívat na pár příkladů použití v testech.

Anotace @inject nyní respektují aliasy definované pomocí use.

Pokud do data- atributů třídy Html vložíte pole, bude se serializovat do JSONu.

V souboru config.neon můžete jednotlivým uživatelů definovat také jejich role.

A přibyla nová třída Nette\Security\Passwords, která řeší hashování hesel.

Tip: nástroj na automatické přejmenování tříd.


Právě jsem smazal Nette Framework

Nemáte náhodou zálohu?

Po dnešním commitu s 80.000 smazanými řádky se z Nette stal ultralehký framework. Nová verze vám ušetří místo na disku i datový traffic při stahování! Sám jsem z toho všeho ještě takový nevzpamatovaný, honí se mi hlavou nesmazatelné vzpomínky na všechny ty smazatelné soubory, kolik jsme toho spolu prožili, při debugování, někdy to byla i legrace, jindy spíš moc ne, a hlavně bych chtěl poděkovat rodičům a přítelkyním, klukům z Nette Foundation a číšníkům z hospody, zejména taky sponzorům, osvětlovačům a všem, co se podíleli na tomto ocenění, moc si toho vážím, dovolte abych se nyní vzdálil mezi novináře a sdělil jim své tiskové dojmy a plány do budoucna i minula, ještě však pozvedněme čísi číši šampaňského a NA NETTE!


Tak ať to nevypadá jako trapný aprílový žertík: Nette jsem fakt smazal. Ale na oplátku vyprodukoval pár nových open source projektů: Application, Caching, ComponentModel, Nette Database, DI, Finder, Forms, Http, Latte, Mail, Neon, PhpGenerator, Reflection, RobotLoader, Routing, SafeStream, Security, Tokenizer, Tracy a Nette Utils. Endžoj!


Proč používám Nette Tester

„Přesvědč mě, proč bych měl používat Nette Tester, v čem je lepší než PHPUnit?“ Tyhle otázky mě vždycky trošku uvádějí do rozpaků, neboť nemám potřebu nikoho přesvědčovat, aby Tester používal. Sám bych se bez něj ale neobešel.

Samotné testování je poněkud zakletá věc. Pět let se na všech konferencích stále dokola mluví o testování a přitom skoro v žádné firmě se kód „netestuje“. Píšu to v uvozovkách, protože ve skutečnosti všichni programátoři dnes a denně testují, co ale nedělají je, že nepíšou testy v PHPUnit, z mnoha důvodů. Pravda je, že sám jsem v oné krátké době, kdy byl Nette Framework testován PHPUnitem, také ztratil chuť testovat. Přičemž testování je pro vývoj frameworku natolik zásadní prvek, jako třeba verzovací systém.

Ale popořadě. Proč všichni programátoři testují, i když „netestují“?

Představte si, že naprogramujete funkci foobar()

function foobar($x, $y) {
    // tady se počítají nějaké věci
    return $val;
}

První věc, kterou každý programátor udělá, je, že vyzkouší, jestli to funguje:

echo foobar(10, 20);

Spustí to, vypíše to 30, což je správně, vypadá to, že to funguje a třeba ještě vyzkouší pár jiných vstupů.

Jinými slovy funkci otestuje. Tedy testuje!

Potom následně udělá to, že testovací skript smaže. A to je právě průšvih! Všechny ty argumenty vývojářů, že na testování nemají čas, v tento moment padají, protože realita je taková, že na testování čas je, dokonce jsou napsané i testy, jenže pak je programátoři smažou. Ironie.

Testem není pouze třída v PHPUnit, testem je i tento jednořádkový skript. Přesněji řečeno, test musí obsahovat ještě informaci, jaká je správná návratová hodnota, takže by vypadal takto:

assert( foobar(10, 20) === 30 );
assert( foobar(-1, 5) === 4 );
...

Až někdy v budoucnu upravím implementaci funkce foobar, nebo do ní sáhne kolega, stačí tento skript spustit, a pokud vyhodí warning, hned vím, že je funkce rozbitá.

A to je vše. Tohle je testování. Tak je to jednoduché. Děláme to všichni, bohužel hodně z vás si ty testy pak maže.


Časem se nám nahromadí obrovská spousta takovýchto testovacích skriptíků a vznikne otázka, jak je spouštět hromadně. Dá se to řešit nějakým shell skriptem, já jsem si na to napsal PHP skript. Jeho podstatnou výhodou je, že umí testy pouštět paralelně (běžně spouštím 40 vláken), což otestování celé sady dramaticky zrychlí. A taky přehledně vypisuje, ve kterém souboru kde přesně došlo k selhání.

Také místo PHP funkce assert jsem si napsal vlastní funkce (třída Assert), které se liší především v tom, že při selhání hezky a čitelně vypíšou, co funkce měla vrátit a co místo toho vrátila, abych rychle věděl, kde je problém.

A ten spouštěč, třída Assert a ještě dalších pár věcí tvoří zmíněný Nette Tester. Čtyři roky spolehlivě testuje Nette Framework.


Když mi někdo nahlásí, že ve funkci foobar je chyba, že pro vstupy 0 a -1 vrací prázdný řetězec místo čísla -1, tak začnu tím, že to nejprve ověřím:

Assert::same( -1, foobar(0, -1) );

Spustím to a skutečně, vypíše se:

Failed: '' should be -1

Tedy jsem napsal failující test. Neudělal jsem to proto, že v příručkách o testování se píše, že test musí nejdřív failovat, nebo proto, že bych se řídil TDD, dělám to proto, že mě nenapadá, co jiného by se dalo dělat, než prostě napsat krátký kód, který bugreport ověří, tedy failující test.

Chyba tam vážně je a je třeba ji opravit. V IDE začnu kód krokovat a hledám místo, kde je zakopaný pes. (O programátorech, kteří píší kód v poznámkovém bloku, ať už se jmenuje TextMate nebo Sublime, namísto plnohodnotného IDE, a nemohou proto kód krokovat, napíšu článek někdy příště.) Ano, kde je chyba bych asi zjistil i bez krokování, zíráním do kódu a umisťováním var_dump, echo nebo console.log, ale trvalo by to mnohem déle. Chci zdůraznit, že krokování a testování nejsou alternativy, ale zcela odlišné činnosti, které je fajn používat dohromady.

Odhalím a opravím chybu, Assert::same je spokojený a do repozitáře komitnu nejen opravu funkce foobar, ale také onen soubor s testem. Díky tomu se tahle chyba už nikdy v budoucnu nevyskytne. A věřte mi, že chyby mají tendenci se opakovat, dokonce pro tento jev existuje název: regrese.


Tohle povídání vám asi muselo připadat strašně samozřejmé. A to je dobře, ono je samozřejmé a já chci bourat předsudky a obavy z testování. Ale stále jsem neodpověděl na úvodní otázku: proč nepoužívám PHPUnit? Protože s ním takto přímočaře pracovat neumím.

Abych mohl otestovat foobar, musel bych napsat celou třídu, která dědí od jiné třídy, jejíž jméno si nejsem schopen zapamatovat. No budiž, použiji šablonu. PHPUnit neumí testy spouštět paralelně, takže otestování celé sady trvá násobně déle. V případě Nette Frameworku jde o 35 sekund versus 2 minuty, což už saje. Ale především, testy psané v PHPUnitu lze spouštět jen v PHPUnitu, nejde o samostatně spustitelné skripty. Takže není způsob, jak napsat failující test a potom ho úplně snadno krokovat a hledat výše uvedeným způsobem chybu.

Nejjednodušší řešení proto bylo napsat si vlastní triviální testovací udělátko. Za ty čtyři roky velmi pozvolna vyspěl v plnohodnotný nástroj, už ho nevyvíjím sám a díky klukům z Oracle má dnes integrovanou podporu v NetBeans 8.0. Jelikož generuje výstup ve formátu TAP, neměl by být problém ani s integrací do jiných nástrojů.

Nebudu vás přesvědčovat, abyste používali Nette Tester, ale rád bych vás přesvědčil, abyste si testy, které píšete, nemazali :-)


Kdy používat preprocesory a kdy ne?

Složitá otázka, těžké kodérovo dilema, které se pokusím rozseknout: používejte preprocesory vždy!

Jak se Internet přesunul do mobilů, staly se největší zabijáci rychlosti načítání stránek tyhlety úhledné sloupečky:

<link rel="stylesheet" href="/css/reset.css">
<link rel="stylesheet" href="/css/content.css">
<link rel="stylesheet" href="/css/layout.css">
<link rel="stylesheet" href="/css/screen.css" media=screen>
<link rel="stylesheet" href="/css/fancybox/jquery.fancybox.css" media=screen>
<link rel="stylesheet" href="/css/print.css" media=print>
<script src="/js/jquery.js"></script>
<script src="/js/jquery.autocomplete.js"></script>
<script src="/js/fancybox/jquery.fancybox.js"></script>
<script src="/js/tooltips/jquery.tooltips.js"></script>
<script src="/js/jquery.scrollspy.js"></script>
<script src="/js/main.js"></script>

To nechceš! Chceš tohle:

<head><link rel="stylesheet" href="/assets/combined.css"></head>

<body>
...
<script src="/assets/combined.js"></script>
</body>

Taky chceš, aby se ti všechny soubory posílaly zazipované a vypadaly hutně: combined.css a hustě: combined.js.

Byly doby, kdy se komprimované skripty psaly ručně. Vynechávaly se komentáře, v JavaScriptu používaly jednopísmenné proměnné atd.

Nedělejte to. Vaším úkolem je psát čitelný kód a komprimování JavaScriptu nechte na bedrech Google Closure Compiler, který to dokáže řádově lépe než vy a navíc upozorní na chyby, o minifikaci stylů se postará například Less.

Ten dokonce zvládá vychytávku, kdy u všech miniobrázků můžete nahradit:

textarea {
    background: #fff url("img/input.gif") repeat-x;
}

za

textarea {
    background: #fff data-uri("img/input.gif") repeat-x;
}

a preprocesor je přímo vloží do combined.css, čímž odpadne hromada HTTP requestů na 100bajtové drobky.

Preprocesory prostě a jednoduše chcete!


Rád bych přispěl do Nette, ale… FAQ

Otázky a stručné odpovědi pro všechny, kteří by rádi přispěli do Nette Frameworku, ale netuší jak.

Rád bych přispěl do Nette, ale nejsem moc dobrý programátor…

Kód je jen jedna ze šíleně mnoha částí, co tvoří framework. Do Nette mohou přispívat lidé tím, že pomáhají vylepšit dokumentaci, píší články, knihy, korigují naše pokusy o angličtinu, přednáší, točí tutoriály, trpělivě odpovídají na fóru, dělí se o své nápady, programují doplňky, testují nové verze, tvoří pro Nette nástroje, pomáhají s Poslední sobotou, hledají prostory, natáčejí přednášky, stříhají je, organizují a navštěvují hackathony a nebo prostě podporují framework finančně. Určitě existuje ještě dalších 101 věcí, které mě teď nenapadly.

Přispívání do kódu je vlastně jen jedna specifická záležitost, o kterou mi dříve ani tolik nešlo a rozhodně bych nerad vyvolal pocit, že každý by měl přispět řádkem ;-) Podstatné je, že něco dělat chceš, čehož si vážím a dodávám: možností jsou mraky.

Existuje teda nějaký seznam úkolů, které je třeba udělat?

Žádný bodový seznam v tuto chvíli nemám, byť časem třeba něco vznikne. O seznam totiž nejde, inspiraci máš kolem sebe. Třeba když vyřešíš nějaký zajímavý úkol v Nette, máš příležitost o tom blognout (jako to třeba dělá Filip a jiní). Určitě tě mnohokrát napadlo, co by se v Nette mohlo vyřešit líp, netvrď mi, že ne, a to je příležitost napsat RFC (česky: „zlepšovací návrh“).

Ono stačí ve věcech místo problémů vidět příležitosti. Stěžovat si, že Nette nemá zdaleka tolik tutoriálů, co třeba Laravel či Rails versus uvědomit si příležitost a napsat třeba seriál a udělat si tím jméno. Stěžovat si, že v issue trackeru visí bugy versus uvědomit si, že můžu některé vyřešit. Vidět, že mnoho lidí bylo frustrovaných z quickstartu versus uvědomit si možnost s tím něco udělat. A tak dále.

Spíš mám za to, že nevědět, co se žádá, znamená mít klapky na očích.

Dobře, ale pokud chci přímo programovat, musím znát road map

Nejnepraktičtější frameworky jsou ty, které navrhneš tzv. „od stolu“. Nejlepší nápady totiž přicházejí z praxe, když se snažíš něco zjednodušit. Nebo čistě náhodou. Tyhle myšlenky pak tvoří road map.

Sepsal jsem seznam svých idejí, které jsem plánoval do Nette přidat. Mohou tě zaujmout a můžeš se pokusit je realizovat, mohou tě inspirovat k novým nápadům, ale také si jich vůbec nemusíš všímat a můžeš přijít s čímkoliv novým.

Prostě to normálně zkus.

A co když něco naprogramuji a pak to nebude přijato?

To se pochopitelně stát může. Sám jsem naprogramoval stovky věcí, které jsem nakonec do frameworku nedal. Proto je vhodné s nápadem přijít v raném stádium na fórum a sepsat RFC. Odargumentovat nebo si zkusit věc nahrubo naprogramovat často pomůže utřídit myšlenky. Na fóru proběhne diskuse, někdy nelehká, jindy se záměr takřka okamžitě schválí. A domluvili jsme se s kolegy, že budeme RFC věnovat velkou pozornost, aby nevyšuměly.

Někdy není ideální, aby se nápad stal přímo součástí frameworku, ale může z něj být výborný doplněk.

A to musím kvůli všemu psát RFC?

Pochopitelně ne, u jednodušších věcí stačí poslat Pull request (návod). Přičemž kód a commit message musí respektovat coding standards, součástí má být test a nejlépe i zápis do dokumentace.

…což je dost práce. Existuje nějaký nástroj na ověření coding standards?

Poprvé to asi není úplně snadné, ale chceme přece všichni používat framework, který má vysoké nároky na kvalitu, ne? Zvládnout základy Gitu a Githubu se ti milionkrát vrátí. (Btw, dejte prosím Nette hvězdičku!)

Nástroj na ověření coding style zatím nemáme. Osobně mi nechybí: když přispívám do jiných projektů, kód formátuji stejně, jako vypadá ten okolní, v logu se podívám, jak hlavní vývojáři zapisují commit message a je to řádově snazší, než studovat coding standards či instalovat nějaký nástroj. Ale beru, že by se mohl hodit – opět je to příležitost takovou věc udělat.

Jakým způsobem lze psát do dokumentace?

Celá dokumentace je na Githubu, takže se do ní dá přispívat stejně, jako do kódu, tedy poslat pull request. Repozitář je rozdělen do větví, dokumentace pro Nette 2.0 je ve větvi doc-2.0, pro verzi 2.1 v doc-2.1. Každou změnu je záhodno udělat v českém i anglickém jazyce. Pro náhled použijte editor.

Pokud bys chtěl (v angličtině) představit nějaký doplněk nebo publikovat návod, je ti k dispozici i nový Nette Blog.

Proč anglicky? Není trapné mluvit anglicky, když jsme tu samí Češi?

Já vím, ale věc se má tak, že skoro nikdo na světě neumí česky ;-)

Na rovinu, angličtina je pravděpodobně mnohem větší problém pro mě, než pro tebe, protože jde o můj pátý jazyk, který se, když jsem byl na základce, ani nevyučoval. Ale bojuji s tím! Psát anglicky má pár výhod: lidi se v diskusích nemají tendenci moc rozkecávat, při psaní se zdokonaluješ ve znalosti nejdůležitějšího jazyka a tvůj článek si může přečíst tisíckrát více lidí.

Kde se dozvím, co se chystá a tak?

Primárním místem je již zmiňovaný Nette Blog + Discussion on development a RFC na fóru. Můžeš sledovat také twitterový účet @NetteFramework či #nettefw a samozřejmě číst tento blog.

Jinak každý měsíc pořádáme Poslední soboty a komornější #NetteFwPivo, kde se pije převážně kubánský rum.

A nemělo by zapadnout, že máme i Youtube kanál s hromadou videí.

A kdo je to „my“?

Skupina samozvaných lidí, kteří prostě chtěli být u toho a chtějí něco dělat. Přidej se!

Btw, pokud jsi napsal nějaký článek, seriál, natočil video apod, bylo by fajn to dát na Planette. Stačí, když mi pošleš odkaz do komentářů, s pár průvodními slovy.


Nevěřím statistikám, které si sám nezfalšuji

Václav Novotný připravil infografiku porovnávající aktivitu vývojářů v Nette a Symfony. Rád a zvědavě se podívám, leč bez vysvětlení metriky umí být čísla krutě zrádná. S nadsázkou: při určitém workflow a naivním měření mohu ve statistikách vyjít jako autor 100 % kódu, aniž bych naprogramoval jedinou řádku.

I při přímočarých workflow je poměřování množství komitů zákeřné. Není komit jako komit. Pokud přidáte pětici důležitých komitů a zároveň deset lidí vám opraví překlepy v komentářích, jste co do počtu komitů autorem třetiny kódu. Což ale není pravda, jste autorem celého kódu, opravy překlepů se za autorství (jak ho obvykle vnímáme) nepovažují.

V GITu dále věc komplikují „merge-commits“. Pokud někdo připraví zajímavý komit a vy ho schválíte (tedy vznikne ještě merge-commit), jste autorem poloviny komitů. Ale jaký je vlastně skutečný podíl? Obvykle nulový, schválení je otázka jednoho kliknutí v GitHubu, byť někdy diskusí strávíte víc času, než kdybyste si kód napsal sám, ale neuděláte to, protože potřebujete vývojáře vychovávat.

Proto místo počtu komitů je vhodnější analyzovat jejich obsah. Nejjednodušší je brát v úvahu počet změněných řádek. Ale i to může být zavádějící: pokud vytvoříte 100 řádkovou třídu a někdo jiný soubor s ní jen přejmenuje (či rozdělí na dva), „změnil“ vlastně 200 řádků a opět jste autorem třetiny.

Pokud týden ladíte u sebe několik komitů a až potom je pošlete do repozitáře, jste v počtu změněných řádek v nevýhodě oproti tomu, kdo je pošle hned a teprve poté dolaďuje následujícími komity. Nebylo by tedy od věci analyzovat třeba až souhrny za celý den. Je třeba odfiltrovat i údržbové komity, zejména ty, které mění u všech souborů letopočet nebo verzi v hlavičce.

Do toho přicházejí ještě situace, kdy se automatizovaně kopírují komity z jedné větve do jiné, nebo do jiného repozitáře. Což de facto znemožňuje dělat jakékoliv globální statistiky.

Analýza jednoho projektu je věda, natož ta srovnávací. Docela mi to připomíná skvělý analytický kvíz od Honzy Tichého.


Související: Jak se počítá „Hall of fame“ na nette.org


„dibi vs. Nette Database“ story

Je nejvyšší čas rozseknout FUD a nejasnosti kolem databázových vrstev.

Databázovou vrstvu dibi jsem začal psát cca před devíti lety se záměrem shrnutým v tomto historickém článku. Šlo mi především o to sjednotit API různorodých klientů, ošetřovat chybové stavy, přidat uživatelsky pohodlné bindování parametrů a také dynamické generování základních konstrukcí SQL, jako jsou například podmínky, řazení a INSERT & UPDATE:

$db = new DibiConnection(...); // or via monostate dibi::connect(...)

$pairs = $db->fetchPairs('SELECT id, name FROM countries');

$arr = array(
    'name' => 'John',
    'modified%d'  => time(),
);
$db->query('UPDATE users SET ', $arr, ' WHERE `id`=%i', $id);
// UPDATE users SET `name`='John', `modified`= '2005-10-12' WHERE `id` = 123

Časem se v PHP objevila nativní knihovna PDO, která v podstatě řešila polovinu věcí, co dibi, nicméně její API pro bindování parametrů bylo těžkopádné, neporadilo si s datumy a skládání základních konstrukcí SQL chybělo úplně. Takže dibi nenahradilo.

V dibi jsem si hrál i s experimenty, jako DibiTable, DibiFluent nebo DibiDataSource, ale sám jsem je nikdy nepoužíval. Jsou tam pochopitelně i věci, které bych dnes udělal lépe, ale z hlediska zpětné kompatibility je takřka nemožné do nich zasahovat. Třeba mám zmatek v tom, co který modifikátor znamená – je jich příliš mnoho. (Moc se to neví, ale místo přemýšlení, který modifikátor použít, můžete obvykle použít otazník.)

Protože téměř v každém demu pro Nette bylo potřeba pracovat s databází, vyžadovalo to nainstalovat dibi nebo Doctrine (jiné vrstvy se v podstatě nepoužívají). Dnes je to díky Composeru otázka pár úderů do klávesnice, ale tehdy neexistoval. Přemýšlel jsem proto, že bych si v příkladech vystačil jen s čistým PDO. Jenže pokud jste rozmlsaní z dibi, není návratu zpět. Chybí vám nejen přívětivé API, ale i pohodlí Tracy (tj. Laděnky) pro monitorování dotazů či chyb.

Tehdy mě napadlo, že by nebylo od věci udělat „dibi model 2010“, nově, bez historických zátěží, založené čistě nad PDO. Vyhodit hromadu driverů, všechny modifikátory nahradit jedním otazníkem a implementovat jen vlastnosti, které budou skutečně potřeba.

Nette Database

Takhle vzniklo Nette Database (NDB). Moderní ekvivalent dibi:

$db = new Nette\Database\Connection(...);

$pairs = $db->fetchPairs('SELECT id, name FROM countries');

$arr = array(
    'name' => 'John',
    'modified'  => new DateTime,
);
$db->query('UPDATE users SET ', $arr, ' WHERE `id`= ?', $id);

Brzy jsem narazil na hromadu nedostatků PDO, nareportoval a obešel mraky chyb a když bylo Nette Database odladěné, vyšlo v únoru 2012 jako součást finálního Nette Framework 2.0.

Tady musím zdůraznit, že navzdory šiřitelům FUD v Nette Database skutečně takřka žádné chyby nebyly a jediným větším problém se ukázal bug v PDO způsobující memory leaky, kvůli němuž musely být třídy NDB přepsány a v Nette 2.1 již Connection není potomkem PDO (dědit od PDO byla z dnešního pohledu stejně blbost.)

Dnes nevidím důvod, proč pro nový projekt použít staré dibi namísto NDB. Chybí asi jen:

  • bohatší možnosti skládání v klauzuli WHERE (zatím se nezdá, že by byla potřeba)
  • statická obálka dibi:: (tu v Nette nahrazuje DI Container)
  • samostatnost (vyřeší Nette 2.2)
  • fetchAssoc (tu v Nette nahrazuje NDBT, eventuálně by ji šlo doplnit je v 2.2-dev)

A tím se dostáváme k Nette Database Table (NDBT), prapříčině mnoha zmatků.

Nette Database Table (nyní Explorer)

V prosinci 2010 jsem do tehdy beta verze Nette 2.0 začlenil knihovnu Jakuba Vrány NotORM, ve kterém jsem viděl úžasný nástroj pro dolování dat z databáze:

$pairs = $db->table('countries')->fetchPairs('id', 'name');
// SELECT `id`, `name` FROM `countries`

$name = $db->table('book')->get($id)->author->name;
// SELECT `id`, `author_id` FROM `book` WHERE `id` = 123
// SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (456))

$arr = array(
    'name' => 'John',
    'modified'  => new DateTime,
);
$db->table('users')->where('id', $id)->update($arr);
// UPDATE users SET `name`='John', `modified`= '2005-10-12' WHERE `id` = 123

Kód i API jsem upravil tak, aby zapadlo do koncepce Nette, v podstatě z myšlenky využít NotORM v Latte pochází i nápad na tzv. předbíhání budoucnosti, neboli načítání jen těch sloupců, které budou později potřeba, taktéž s Latte může skvěle fungovat koncept NotORM 2.

A právě NotORM v NDB nese označení NDBT (nyní Nette Database Explorer). Přičemž její použití je volitelné.

Zapojení do Nette vyvolalo o NDBT resp. NotORM velký zájem a ukázalo se, že byť byla knihovna pro potřeby Jakuba odladěná, pro různorodější požadavky bylo třeba odvést ještě hodně práce. Té se od poloviny roku 2011 ujal Hrach a z velké části původní NDBT přepsal a pečlivě doplnil testy. Knihovna procházela mnohem rychlejším vývojovým cyklem než zbytek frameworku, nicméně nebýt jeho součástí, nevyvíjí se asi vůbec.

Nette tedy má

  • NDB (nyní Nette Database Core), obdobu dibi, stabilní od verze 2.0.0
  • NDBT (nyní Database Explorer), obdobu NotORM, vhodnou pro produkční nasazení až od verze 2.1

Dibi nadále udržuji, koneckonců běží mi na něm většina webů, ale žádné novinky nechystám. V podstatě ani do NDB ne. Naopak s NDBT, jenž pohání mimo jiné i tento blog, má Hrach ambiciózní plány.

Aktualizace 2017: Původní Nette Database Table (NDBT) nese nový název Nette Database Explorer a samotné jádro je Nette Database Core


Dokumentační úchylnosti

Dobře udržovaný software má mít kvalitní API dokumentaci. Jistě. Ovšem stejným prohřeškem, jakým je absence dokumentace, je i její přebytečnost. U psaní dokumentačních komentářů je totiž potřeba, podobně jako u návrhu API nebo uživatelského rozhraní, přemýšlet.

Přičemž přemýšlením bych nenazýval proces, který se udál v hlavě vývojáře, když doplnil konstruktor tímto komentářem:

class ChildrenIterator
{
    /**
     * Constructor.
     *
     * @param array $data
     * @return \Zend\Ldap\Node\ChildrenIterator
     */
    public function __construct(array $data)
    {
        $this->data = $data;
    }

Šest řádků, které nepřidaly ani jednu jedinou informaci. Místo toho roste

  • vizuální šum
  • duplicita informací
  • objem kódu
  • možnost chybovosti

Nesmyslnost uvedeného komentáře vám možná připadá evidentní, pak jsem rád. Občas totiž dostávám pull requesty, které se snaží podobné smetí do kódu propašovat. Někteří programátoři dokonce používají editory, které takto znečišťují kód automaticky. Au.

Nebo jiný příklad. Zkuste se zamyslet, zda vám komentář prozradil něco, co by bez něj nebylo zřejmé:

class Zend_Mail_Transport_Smtp extends Zend_Mail_Transport_Abstract
{
    /**
     * EOL character string used by transport
     * @var string
     * @access public
     */
    public $EOL = "\n";

S výjimkou anotace @return lze pochybovat o přínosnosti i v tomto případě:

class Form
{
    /**
     * Adds group to the form.
     * @param  string $caption       optional caption
     * @param  bool   $setAsCurrent  set this group as current
     * @return ControlGroup
     */
    public function addGroup($caption = null, $setAsCurrent = true)

Pokud používáte výmluvné názvy metod a parametrů (což byste měli), pokud ty ještě navíc mají výchozí hodnoty nebo typehinty, nedá vám tento komentář takřka nic. Buď bych ho zredukoval o informační duplicity, nebo naopak rozšířil.

Ale pozor na opačný extrém, jakým jsou romány v phpDoc:

/**
 * Performs operations on ACL rules
 *
 * The $operation parameter may be either OP_ADD or OP_REMOVE, depending on whether the
 * user wants to add or remove a rule, respectively:
 *
 * OP_ADD specifics:
 *
 *    A rule is added that would allow one or more Roles access to [certain $privileges
 *    upon] the specified Resource(s).
 *
 * OP_REMOVE specifics:
 *
 *    The rule is removed only in the context of the given Roles, Resources, and privileges.
 *    Existing rules to which the remove operation does not apply would remain in the
 *    ACL.
 *
 * The $type parameter may be either TYPE_ALLOW or TYPE_DENY, depending on whether the
 * rule is intended to allow or deny permission, respectively.
 *
 * The $roles and $resources parameters may be references to, or the string identifiers for,
 * existing Resources/Roles, or they may be passed as arrays of these - mixing string identifiers
 * and objects is ok - to indicate the Resources and Roles to which the rule applies. If either
 * $roles or $resources is null, then the rule applies to all Roles or all Resources, respectively.
 * Both may be null in order to work with the default rule of the ACL.
 *
 * The $privileges parameter may be used to further specify that the rule applies only
 * to certain privileges upon the Resource(s) in question. This may be specified to be a single
 * privilege with a string, and multiple privileges may be specified as an array of strings.
 *
 * If $assert is provided, then its assert() method must return true in order for
 * the rule to apply. If $assert is provided with $roles, $resources, and $privileges all
 * equal to null, then a rule having a type of:
 *
 *    TYPE_ALLOW will imply a type of TYPE_DENY, and
 *
 *    TYPE_DENY will imply a type of TYPE_ALLOW
 *
 * when the rule's assertion fails. This is because the ACL needs to provide expected
 * behavior when an assertion upon the default ACL rule fails.
 *
 * @param  string                                  $operation
 * @param  string                                  $type
 * @param  Zend_Acl_Role_Interface|string|array  $roles
 * @param  Zend_Acl_Resource_Interface|string|array $resources
 * @param  string|array                          $privileges
 * @param  Zend_Acl_Assert_Interface                $assert
 * @throws Zend_Acl_Exception
 * @uses   Zend_Acl_Role_Registry::get()
 * @uses   Zend_Acl::get()
 * @return Zend_Acl Provides a fluent interface
 */
public function setRule($operation, $type, $roles = null, $resources = null, $privileges = null,
                        Zend_Acl_Assert_Interface $assert = null)

Vygenerovaná API dokumentace je pouhá referenční příručka, nikoliv kniha, kterou by si člověk četl před spaním. Litanie sem skutečně nepatří.

Asi nejoblíbenějším místem, kde se lze dokumentačně vyřádit, jsou hlavičky souborů:

<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license http://framework.zend.com/license/new-bsd    New BSD License
 * @version $Id: Abstract.php 25229 2013-01-18 08:17:21Z frosch $
 */

Kolikrát se zdá, že záměrem je hlavičku natáhnout tak, aby po otevření souboru vůbec nebyl vidět kód. K čemu je 10řádková informace o licenci New BSD, obsahující klíčové zvěsti, jako že její znění najdete v souboru LICENSE.txt, že je dostupná přes world-wide-web a pokud náhodou nedisponujete moderními výstřelky, jako je tzv. webový prohlížeč, máte odeslat email na license@zend.com a oni vám ji okamžitě pošlou? Navíc v balíku zopakovaná 4400×. Schválně jsem žádost zkusil poslat, ale odpověď nepřišla :-)

Též uvedení letopočtu v copyrightu vede k vášni dělat komity jako update copyright year to 2014, které změní všechny soubory, což komplikuje porovnávání verzí.

Je vůbec potřeba uvádět v každém souboru copyright? Z právního hlediska to potřeba není, nicméně pokud open source licence dovolují uživatelům používat části kódu s tím, že musí zachovat copyrighty, je vhodné je tam mít. Stejně tak je užitečné v každém souboru uvádět, z jakého produktu pochází, pomůže to lidem v orientaci, když na něj jednotlivě narazí. Dobrým příkladem je třeba:

/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

Přemýšlejte proto prosím nad každým řádkem, jestli skutečně má pro uživatele přínos. Pokud ne, jde o smetí, které nemá v kódu co dělat.

(Prosím případné komentátory, aby článek nevnímali jako souboj frameworků, tím rozhodně není.)


phpFashion © 2004, 2017 David Grudl | o blogu

Pokud není uvedeno jinak, podléhá obsah těchto stránek licenci Creative Commons BY-NC-ND Creative Commons License BY-NC-ND

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.