Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Jak teda s těma výjimkama v busines logice?

14.11 - 17. května 2013 | Moje práce

Na develu se rozvíjí debaty o tom, zda je zpátečnické odrazovat od vyhazování výjimek. Já patřím mezi zpátečníky. :) Tady je moje odpovědˇ, kterou je IMO škoda nemít veřejně dostupnou, takže ji publikuju i tady na blogu.

Na začátek je dobré se opřít o nějakou autoritu, proto tady pro začátek ocituju .net Framework Design Guidelines doporučení k výjimkám:

  1. Nevracejte chybové kódy. K tomuto účelu jsou určeny výjimky.
  2. Vyhazujte výjimky pokud dojde k běhové chybě. Pokud člen nemůže úspěšně provést úkol za nějž nese zodpovědnost, mělo by to být považováno za běhovou chybu a měla by být vyhozena výjimka.
  3. Pokud se dostanete do stavu, kterému nepomůže žádné ošetření výjimky, proveďtě ukončení procesu pomocí Environment.Fa­ilFast().
  4. Nepoužívejte výjimky k formálnímu řízení toku programu. S výjimkou systémových selhání by vždy měl existovat způsob jak se vyhnout vyhazování výjimek.
  5. Zvažte důsledky vyhozené výjimky na výkon.
  6. Vždy zdokumentujte všechny výjimky vyhazované veřejnými členy z důvodu porušení jejich kontraktu.
  7. Nemějte veřejné členy, které mají nebo nemají vyhazovat výjimky na základě nějakého parametru. Type GetType(string name, bool throwOnError)
  8. Nemějte veřejné členy které vracejí výjimku jako návratovou hodnotu nebo out parametr.
  9. Nastavte všechny relevatní vlastnoti vyhazované výjimky.
  10. Preferujte užití systémových výjimek před vlastními.
  11. Vlastní výjimky vyhazujte v případě, že vyžadují odlišný způsob ošetření než existující typy výjimek.
  12. Nikdy nevytvářejte vlastní typ výjimky, jen proto, aby váš team měl nějakou vlastní výjimku.
  13. Vyhazujte co nejkonkrétnější výjimky.
  14. Nevracejte chybové kódy z obav o výkonostní dopady při vyhazování výjimek.
  15. Používejte vzory jako TryParse, které snižují negativní dopady na výkon způsobené vyhazováním výjimek.

Z texu je zřejmé, že vyhazování výjimek má své opodstatnění. Důležité je však v jakých scénářích se výjimky vyhazovat mají a v jakých ne.

Často se tu objevují příklady s validací vstupních hodnot nebo třeba autentikace uživatele. Začněme validátorem:

Primární zodpovědností validátoru je validovat hodnoty. Je nevalidní hodnota příčinou nemožnosti korektní práce validátoru (důvodem k vyhození výjimky)? Není (viz bod 2). Můžeme to řešit vracením primitivy jako je bool. Já si však myslím, že taková hodnota je nedostatečná. Potřebujeme výsledek validace zpropagovat někam, kde se s ním bude dále pracovat. Vyhodíme tedy výjimku a tu pak odchytíme? Tím se ale dostáváme k bodu 4. Lepším řešením je definovat komplexní návratový typ:

type ValidationResult =
  | Valid
  | Invalid of ValidationError list

Pokračujme s autentikací uživatele. Běžnou praxí je vyhazování SecurityException pokud se nepovede uživatele autentikovat. Výjimka se pak nechá probublat do UI vrstvy, kde se ošetří a uživateli se zobrazí hláška, že zadal špatné jméno nebo heslo. Opět však jde o porušení 4. bodu. Autentikace tam může vracet opět bool nebo rovnou instanci User a v případě nevydařené autentikace null. Tím se zbavíme bezpečnostních výjimek, ale zase si zavedeme potenciální chybu v podobě NRE… Proto si opět myslím, že by se měl vracet adekvátní typ:

type AuthenticationResult =
  | NotAuthenticated
  | Authenticated of User

Autentikační modul samozřejmě může vyhazovat výjimky, ale většinou budou spojené s nefungující infrastrukturou (LDAP, SQL nebo jinej auth provider), to je totiž validní důvod, proč nemůže autentikace doběhnout.

Jak rozběhat Bower ve Visual Studiu

13.13 - 3. května 2013 | Moje práce

Takový malý tutoriálek pro command line junkies, jak si rozběhat bower ve Visual Studiu.

Nainstalujte si Chockolatey:

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin

(Win+R, Ctrl+V, Enter)

Nainstalujte si Node:

cinst nodejs.install

Vytvořte si profil pro nuget konzoli %UserProfile%\Documents\WindowsPowerShell\NuGet_profile.ps1 a do něj přidejte:

Set-Alias npm "C:\Program Files\nodejs\npm.cmd"
Set-Alias bower "~\AppData\Roaming\npm\bower.cmd"

Spusťte si Visual Studio, otevřete Package Management Consoli a napište:

npm install -g bower

A jedem. :)

Parsování primitivních typů znova a lépe

17.10 - 23. dubna 2013 | Moje práce

Způsobů, jak parsovat primitivní typy z texu, je celá řada. Každý typ má svou statickou metodu Parse, která vyhodí výjimku, když se jí to nepovede. Pak tu máme modul Convert, který dokáže parsovat poměrně bezpečně, ale ne vždy chceme dostávat default hodnoty, když se mu podstrčí nic.

Občas zkrátka potřebujeme mít absenci nějaké hodnoty vyjádřenou v logice kódu. Proto často nezbývá než se vrhnout po hlavě do šíleného vzoru, který je v BCL rozšířen jak mor v době temna – TryParse. Protože tyto metody vznikly někdy v době, kdy si ještě mohlo CLR o generikách nechat zdát, je práce s ní celkem neohrabaná a plná nepěkných věcí, jako out parametry nebo porušení základního objektového principu tell don't ask.

int? TryParse(string input) {
  int result;
  if (Int32.TryParse(input, out result)) return result;
  return null;
}

Tohle je jednoduchá ukázka, kterou jistě ve svém kódu najdete nespočetněkrát. Proč už něco takovýho není v BCL od verze 2? Každopádně má to své mouchy, třeba teď to pro změnu vrací null! A to jsme jeden problém vyřešili zavedením jiného.

Tak se necháme inspirovat u F#, které nemá podporu pro out parametry. Ten to řeší nějak tak:

Tuple<bool, int> TryParse(string input) {
  int result;
  bool success = Int32.TryParse(input, out result);
  return Tuple.Create(success, result);
}

Bohužel C# není jazyk, který by prvotřídně podporoval Tuples. Můžeme se ptát proč, ale je to asi taky zbytečný. Každopádně, nemáme problém s null, ale typ samotný nám toho taky moc neřekne. Co je ten bool? Nevíme, dokud se nepodíváme do dokumentace – kódu. Musí to jít i lépe! Maybe. ;)

Maybe<int> TryParse(string input) {
  int result;
  bool success = Int32.TryParse(input, out result);
  return success ? result.ToMaybe() : new Nothing<int>();
}

Nyní máme existenci hodnoty vyjádřenou na úrovni typu a můžeme s ní bezpečně pracovat. Můžeme využít funkcionální kompozice, a hodnotu, pokud existuje, dále zpracovávat. Nikde (skoro), však nemusíme ověřovat, jestli ji máme nebo ne. Kód se tak krásně vyčistí.

Celé řešení můžeme nakonec pěkně generalizovat a vytvořit si vlastní modul pro parsování:

public static class ParseEx {
  public delegate bool ParserDelegate<T>(string input, out T result);

  public static Maybe<T> Parse<T>(string input, ParserDelegate<T> parser) {
    T result;
    bool isSuccess = parser(input, out result);
    return isSuccess ? result.ToMaybe() : new Nothing<T>();
  }

  public static Maybe<DateTime> ParseDateTime(string input) {
    return Parse<DateTime>(input, DateTime.TryParse);
  }

  public static Maybe<int> ParseInt32(string input) {
    return Parse<int>(input, Int32.TryParse);
  }
}

Máme jednu parametricky polymorfní metodu, která nám umožňuje parsovat libovolná data, pomocí předaného delegáta. Tu pak používáme k vytvoření specifických parsovacích metod, které nám umožní opět kód pročistit.

Kdybyste hledali implementaci Maybe, najdete ji v tomhle gistu i se spoustou šikovných extenzí.

Toto řešení může mít u některých performance hunterů některé efekty, ze kterých by nemuseli klidně spát. Hlavně ty alokace na heapu, když jsme mohli zůstat v klidu na zásobníku. No jo. V tohle případě je však zisk z čitelnosti a korektnosti o tolik řádů větší než pár ušetřených cyklů levného výpočetního výkonu, že to snad ani nemá cenu zase v komentářích řešit, co? ;)

Deset důvodů proč nepoužívat funkcionální jazyky

10.15 - 14. dubna 2013 | Moje práce

Máte už plný zuby toho šílenýho povyku okolo funkcionálních jazyků? Já teda jo. Myslím, že je načase sepsat pár rozumných důvodů, proč se od něho držet dál. Funkcionální programování není pro každého. To je důležité zmínit hned na začátku.

Pro jistotu, když píšu „staticky typovaný funkcionální jazyk,“ mám na mysli jazyk, který i dokáže odvozovat typy, jeho datové struktury nepodporují mutace, a podobné nesmysly. V praxi to znamená, že myslím zcela nepraktické (akademické) jazyky jako je Haskell nebo jazyky z rodiny ML (jako OCaml nebo F#).

Důvod první: Nemám zájem sledovat poslední výstřelky

Jako většina programátorů, jsem přirozeně konzervativní a nerad se učím nové věci. To je také důvod proč jsem si vybral kariéru v IT.

Rozhodně nejančím z každé žhavé novinky, jen proto, že všichni frajeři se z ním můžou podělat. Já si radši počkám, až ten výstřelek trochu dospěje a ukáže se trochu perspektivním. Pro mě tu není funkcionální programování dostatečně dlouho, aby mě přesvědčilo, že tu s námi ještě nějakou dobu bude.

Ano, někteří se nás mohou snažit přesvědčovat, že ML nebo Haskell tu byly asi tak stejně dlouho jako naše staré známé oblíbené jazyky, jako je Java nebo PHP, ale já o Haskellu, slyšel poprvé celkem nedávno, takže s tímhle argumentem na mě nechoďte.

A podívejte na to batole, F#. Vždyť je teprve sedm let starý! Jistě, to může být dost dlouhá doba pro geology, ale na internetu? Sedm let!? To je skoro okamžik.

Tak, jak říkám, radši budu opatrný a ještě si pár dekád počkám, jestli tu ještě tohle to funkcionální programování bude, nebo jestli se to nakonec neukáže jako výstřelek doby.

Důvod druhý: Jsem placen od řádky kódu

Já teda nevím jak vy, ale čím víc řádek napíšu, tím víc se cítím produktivní. Pokud můžu za den vychrlit 500 řádků kódu, mám ze sebe dobrý pocit. To byla dobře odvedená práce. Moje commity jsou velké a můj šéf může jasně vidět, že jsem byl opravdu vytíženej.

Když porovnám kód napsaný ve funkcionálním jazyce s kódem ve starém dobrém céčkovém jazyce, chybí tam tolik kódu, že mě to až děsí.

No, podívejte se sami na kód napsaný v povědomém jazyce:

public static class SumOfSquaresHelper
{
   public static int Square(int i)
   {
      return i * i;
   }

   public static int SumOfSquares(int n)
   {
      int sum = 0;
      for (int i = 1; i <= n; i++)
      {
         sum += Square(i);
      }
      return sum;
   }
}

a porovnejte to s tímhle:

let square x = x * x
let sumOfSquares n = [1..n] |> List.map square |> List.sum

To je 17 řádek ku 2. Představte si ten rozdíl, když to rozložíte na celý projekt! Kdybych takhle přistupoval k psaní mých programů, moje produktivita by drasticky klesla. Brrr, lepší si to ani nepředstavovat.

Důvod třetí: Miluju složené závorky

A to taky dost nepobírám. Co je jako na těch jazycích, co se snaží zbavit všech těch závorek? Jak je pak vůbec můžeme považovat za opravdové programovací jazyky?

Pojďme si ukázat, co mám na mysli. Tady je kus kódu se starejma dobrejma složenejma závorkama:

public class Squarer
{
    public int Square(int input)
    {
        var result = input * input;
        return result;
    }

    public void PrintSquare(int input)
    {
        var result = this.Square(input);
        Console.WriteLine("Input={0}. Result={1}", input, result);
    }
}

a tady obdobný kód bez složenejch závorek:

type Squarer() =

    let Square input =
         let result = input * input
         result

    let PrintSquare input =
         let result = Square input
         printf "Input=%i. Result=%i" input result

Vidíte to? Já teda nevím jak vám, ale mně ta druhá ukázka přijde dost znepokojující, jako by tam něco důležitého chybělo. Ruku na srdce, jsem celkem ztracený, když se mé oko nemůže odpíchnout od povědomých složených závorek.

Důvod čtvrtý: Chci vidět explicitní typy

Zastánci funkcionálního programování tvrdí, že odvozování typů, dělá kód čistším, protože ho nemusíte pořád zanášet typovými deklaracemi.

Abych byl upřímný, já typové deklarace vidět chci. Necítím se ve své kůži, když nevidím jakého typu parametr přesně je. To je také důvod, proč mám rád Javu.

Tady je signatůra funkce z nějakýho ML dialektu. Nejsou tam vůbec žádný deklarace typů. Všechny jsou odvozený automagicky:

let GroupBy source keySelector =
    ...

A tady je signatura té samé funkce v C#, s explicitně vyjádřenými typy:

public IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
    IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector
    )
    ...

Já teda nevím jak vám, ale mně se víc líbí druhá ukázka. Pro mě je velice důležité, abych viděl, že ta funkce vrací IEnumerable<IGrouping<TKey, TSource>>.

Tak určitě. Kompilátor jako zjistí, co je to za typ a upozorní mě, když to nesedí, ale proč by to, proboha, měl dělat kompilátor? Od čeho mám svůj mozek?

Přiznávám, že pokud pracujete s generiky, lambda výrazy a s funkcemi, které vracejí funkce a s dalšíma cool novinkama, tak pak asi můžou být typové declarace trochu fousaté a komplexní. Může to být trochu těžké, napast správný typ. Ale na druhou stranu, existuje na to jednoduchý lék: Nepoužívejte generika a nepředávejte si funkce. Vaše signatury budou o dost jednodušší!

Důvod pátý: Rád opravuju bugy

Neznám nic napínavějšího než je lov – vystopovat a zlikvidovat pořádnou bugu. A když najdu bugu v produkci, ještě líp, protože navíc budu ještě za hrdinu.

Četl jsem, že se staticky typovnými funkcionálními jazyky, je mnohem těžší zanést chybu.

No tě pic.

Důvod šestý: V debuggeru jsem jako doma

A když už jsem u toho lovení bugů. Většinu dne trávím v debuggeru, krokováním kódu. Já vím, měl bych psát unit testy. Jenže to se snadnějš řekne, než dělá…

Každopádně, podle všeho, pokud se vám ve staticky typovaném funkcionálním jazyce podaří program zkompilovat, většinou i funguje. Prej musíte většinu času zabít tím, aby jste správně poladili typy, aby to pěkně sedělo a pak máte hotovo. Co je na tom zábavného?

Což mě přivádí k…

Důvod sedmý: Nechci přemýšlet nad každým detailem

Všechno to ladění typů a ujišťování se, že všechno perfektně sedí, zní celkem nudně.

Dokonce jsem někde slyšel, že jste nucený přemýšlet o každém možném hraničním případu, všech možných chybových stavech a já nevím, co se ještě může zvrtnout. A to všechno od samého začátku. Prostě nemůžeš být línej a nechat to na pozdějc.

Radši se zaměřuju na to, aby (většinou) všechno prošlo tak jak má a bugy řeším, až když se ukážou.

Důvod osmý: Rád ověřuju null

Jsem velmi svědomitý v případě ověřování null a to v každé metodě. Celkem mě uspokojuje pocit, že vím, že můj kód je ve výsledku úplně neprůstřelný.

void SomeMethod(SomeClass x)
{
    if (x == null) { throw new ArgumentNullException(); }

    x.DoSomething();
}

Kecám. Samozřejmě to nedělám úplně všude, to bych vůbec nic jinýho neudělal.

Ale, nakonec se mi stalo jen jednou, že jsem musel řešit pád, kvůli NRE. Ani jsme nepřišli o moc peněz za těch pár tejdnů, kdy jsem hledal, kde je vlastně problém. Fakt nechápu, proč se z toho dělá taková věda.

Důvod devátý: Rád aplikuju návrhové vzory, kde je to možné

Poprvý jsem o návrhových vzorech četl v knize Návrhové vzory (z nějakýho důvodu se občas označuje jako GoF vzory, ale fakt netušim proč) a od tý doby je fakt pilně aplikuju na všechny problémy. Můj kód pak vypadá líp a má „enterprise“ nádech. A to se mému šéfovi líbí.

Ale ještě jsem ani náznakem neslyšel o nějakých vzorech ve funkcionálním programování. Jak vůbec můžete udělat něco užitečnýho bez Strategie, Abstraktní továrny, Dekorátoru, Proxy nebo dalších?

Možná o nich funkcionální programátoři ani neslyšeli…

Důvod desátý: Je tam příliš matematiky

Tady je další ukázka počítání mocnin. IMO je dost těžký pochopit, o co vlastně jde, protože je tam sousta nesmyslných symbolů:

ss=: +/ @: *:

Dobře, omlouvám se, moje chyba, tohle je kód v J. Ale stejně jsem viděl, jak je ten funkcionální kód plnej různých <*> a >>= a vůbec takových divných konceptů jako jsou „monády“ a „funktory“.

Fakt nechápu, proč tihle funkcionalisti nemohli nechat věci, který už dobře znám – jasné symboly jako ++ nebo != a jednoduché koncepty jako je „dědičnost“ a „polymorfismus“.

Shrnutí: Fakt to nechápu

Víte co? Fakt to nechápu. Nechápu, co by mohlo být na funkcionálním programování užitečný.

Jediný oč žádám je, aby mi někdo ukázal skutečné benefity na jedný stránce, místo chrlení příliš spousty nesourodých informací.

Update: Tak jsem si přečetl tu stránku „vše co potřebujete vědět na jediné stránce.“ Ale je to příliš krátké a zjednodušující, aby mi to dávlo smysl. Fakt hledám něco, co má trochu hloubku, něco, co by mě fakt chytlo.

A ne, fakt nemám chuť číst tutoriály, hrát si s ukázkama a psát vlastní kód. Chci tomu jen přijít na kloub, bez toho, abych musel dělat všechny ty zbytečný věci.

Nechci měnit to, jak myslím, jen se chci naučit to nové paradigma.

článek je volným překladem Ten reasons not to use a statically typed functional programming language od Scotta Wlaschina

Zatočíme s null

13.29 - 11. dubna 2013 | Moje práce

Chtěl bych se v takové kratké sérii blogů dostat k jednotlivým bodům NOOO manifesta a jeho principům. V této sérii, bych chtěl vyjádřit, proč se k tomuto manifestu hlásím, a i ukázat pár praktických důsledků v hodnotách a principech obsažených.

Dnes se zastavíme u toho, proč preferuji Option před null a proč:

Upřednostňujeme reprezentaci invariant na úrovni typů před reprezentací na úrovni hodnot.

Problematika null

Snad každý, kdo se zabývá programováním narazil na problém s null hodnotami. Většinou se projevuje tak, že z ničeho nic dostaneme někde NullReferenceException (NRE) a máme po ptákách. Tento problém je celkem častý a odhaduje se, že každý rok má za následek ztrátu $1B. To je pro představu 1 000 000 000 USD. Ztráta je způsobena jak samotným projevem, tak i náklady vynaloženými na obranu před NRE.

Obrana

Jistě znáte techniky defenzivního programování, jako jsou Code Contracts v jejich různých mutacích, které efektivně zvyšují náklady na tvorbu a správu přebujelé code base, v který aby se čert vyznal. Další se snaží o zavedení nového syntaktického prvku, který vypadá následovně: foo?bar() a dělá to samé co if (foo != null) foo.bar();. Pořád jen to jen maskování symptomů. Ne řešení problému.

Prevence

Další možností je používat jazyky, jako je třeba F#, které null nemají. (Ok, F# zná null, ale musíte si explicitně o něj říct a označit tak potenciálně nebezpečný kód, jinak by asi moc nemohl pracovat s již existujícími .net knihovnami.) Většinou se jedná o funkcionální jazyky se sofistikovanými typovými systémy.

Nechme se inspirovat některými dobrými vzory:

Null Object

Pokud děláte TDD, nebo se zajímáte o návrhové vzory, jistě už jste na tento vzor narazili. Jde o to, že ve vašem systému máte definované „implementační“ třídy, které nemají žádnou implementaci, nebo prostě jen nikdy nevyhazují výjimky. Slouží pouze k tomu, že pokud systému nenabídneme nějakou jinou funkční implementaci, nemusíme se obávat NRE, protože tu máme bezpečný Null Object.

Já ho třeba používám vždycky, když implementuji logování:

ILogger logger = NullLogger.Instance;
public ILogger Log {
  get { return logger; }
  set { logger = value ?? NullLogger.Instance; }
}

Po inicializaci je logger nastaven na instanci NullLoggeru. Logger je možné injektovat, ale pokud se někdo pokusí podstrčit null, tak si podržíme NullLogger. Za předpokladu, že se logger nepoužívá nikde jinde, než v property, máme bezpečno a nemusíme psát spoustu defenzivního kódu jako:

if (Log != null) Log.Info("foo");

Samozřejmě to není jediný případ užití. Další, z možných implementací, je vyjádření speciálního stavu, jako třeba:

public class AsyncResult {
  public AsyncResult(XElement response) {
    Response = response;
  }
  public XElement Response { get; set; }
}

public class QueryInProgress : AsyncResult {
  public QueryInProgress() : base(null) { }
}

Tohle je Null Object implementovaný v SOA. Systém dostane ke zpracování nějaký požadavek (dotaz) a když se následně zeptá na výsledek, dostane buď Null Object QueryInProgress, který říká, že dotaz se ještě zpracovává, nebo už samostatnou odpověď. Null Object může mít i sémantický význam.

Option

Jazyky, které podporují pattern-matching, umožňuji práci s typem Option:

let exists (x : int option) =
    match x with
    | Some(x) -> true
    | None -> false

None nám nahrazuje null, ale nejedná se o hodnotu, ale o typ. Typ Option je totiž definován jako generické disjunktivní sjednocení:

type Option<'T> =
  | None
  | Some of 'T

Na rozdíl od vzoru Null Object tu neztrácíme nutnost rozhodování se, jak se v případě None zachovat. Nadruhou stranu, máme speciální případ explicitně sémanticky vyjádřen na úrovni typu.

Maybe

Maybe je v případě užití celkem podobný Option s tím rozdílem, že jde o monad. Krom toho, že je opět parametricky polymorfní (generický), splňuje i požadavky na monadičnost. Monadičnost nám umožňuje kompozici a můžeme i využít vlastostí jazyka jako je LINQ, který je postavený nad sekvenčním monadem, ale nic nám nebrání ho použít i nad jinými. Třeba právě nad maybe.

Nepotřebujeme však nutně Haskell, abychom mohli využívat všech výhod. Tohle je třeba C#:

return TryGetCachedData(message, metadata, request).Match(
  some: data => CachedResult(message.RequestId, data, stats),
  none: () => Result(message, context, metadata, request, stats));

Implementaci Maybe monadu z ukázky najdete v tomto gistu.

Závěr

Null je zlo. Je možné s ním bojovat silou nebo inteligencí. Já byl vždycky slabý a líný, proto preferuji výše zmíněné možnosti prevence před samotnou obranou. Náš projekt sice není úplně imuní vůči null, ale díky Null Objectu a Maybe monadu, které se dají krásně kombinovat, je kód čistší a méně náchylný k chybám. Snad. :)

Krátká pohádka o zlobivých bytech v DB

20.19 - 3. dubna 2013 | Moje práce

„Celý je to pomalý a taví nám to databázi. Vždyť tam máme pár uživatelů a už to nezvládá. To jste se vyznamenali, borci.“ A to byl začátek celkem slušného redesignu, dva měsíce po spuštění do produkce, s celkem zajímavě naplněnou databází. Po létě proseděným ve stínu kolbenky, značném vyčerpání z posunování vostrýho startu. Krásná vize na barevný podzim a začátek zimy.

A za celým tím byla úžasná myšlenka ukládat obrázky do databáze. Vždyť MS SQL to umí, dokonce má datový typ FileStream, který ukládá soubory „fyzicky“ na file systém! Jistoty, prosperita. Samý profit. Vzpomínáte na Longhorn a jeho geniální WinFS, která měla spasit správu souborů?

O necelý rok dříve

Když jsem s novým rokem nastupoval do nového zaměstnání, projekt už pár měsíců běžel. Při procházení datové vrstvy mě zarazila jedna věc a to, že se obrázky ukládaly přímo do databáze. Entity, které měly navázané obrázky prostě obsahovaly vlastnost typu byte[]. Puristický dogmatik ve mně zpozorněl. „Přijde vám ukládat nerelační data do relační databáze ok? Mně teda ne.“ Říkám pánům architektům. Neviděli na tom nic špatného a jelo se dál.

Za pár měsíců se rozhodlo, že budeme hostovat v cloudu. SQL Azure sice FileStreamy neumí, ale pořád můžeme mít varbinary sloupce. Mně se to furt nezdálo. Je to „good enough,“ jak se s oblibou říkalo.

Problem solved.

Asynchronní vs. paralelní zpracování úloh

12.42 - 26. března 2013 | Moje práce

S příchodem async/await do C# 5.0 se nám v různých APIs objevily asynchronní metody, které vracejí Task nebo Task<T> a jejich název je vybaven kouzelným postfixem Async. Jde o takzvaný Task-based Asynchronous Pattern (TAP). A protože je postavený na datové struktuře Task může svádět k vlastním implementacím, které nejsou tak říkajíc zrovna optimální.

Začneme malou ukázkou z praxe. V předchozím tweetu Jirka ukazuje na „asynchronní“ implementaci metody ToFirstAsync() ze SQLite:

public Task<T> ToFirstAsync ()
{
  return Task<T>.Factory.StartNew(() => {
    using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) {
      return _innerQuery.ToFirst ();
    }
  });
}

Co je na ní špatného?

Když pominu maďarské podtržítko, mezery před závorkama, zbytečné složené závorky a vůbec… Zkrátka vidíme ukázkový pokus o implementaci TAP pomocí Task Parallel Library (TPL). A to je špatně. Ve skutečnosti nejde o asynchronní operaci, ale o operaci odpálenou na jiném threadu.

Asynchronní vs. paralelní

Nejprve bychom si asi měli ujasnit, jaký rozdíl je mezi asynchronním a paralelním zpracováním, protože to stále většině lidí není jasné. Ano, i já se v tom občas ztrácím, ale snažím se. :)

Představte si dlouhotrvajících operaci. Řekněme takovou, která není přímo spojená s výpočtem (CPU-bounded), ale takovou, která si šahá na některé ze vstupně/výstupních zařízení (IO-bounded). Tak tedy vězte, že asynchronní operace je taková, která běží v tomto zařízení. Pokud třeba čteme z disku, databáze nebo z cloudu, pošleme požadavek na zařízení a nečekáme na odpověď přímo, ale necháme zařízení ať se nám ozve samo, až bude jeho odpověď připravená. Mezi tím se může aplikace věnovat jiným úlohám. Žádná vlákna zde není třeba zmiňovat.

Pokud zadáme úlohu ke zpracování jinému vláknu, zavádíme konkurenci. Ukázka výše odstartuje nový Task, který se s největší pravděpodobností spustí na jiném threadu a záhy ho zamkne. Sice neblokuje vlákno, ze kterého operaci voláme, ale blokujeme vlákno jiné. Jaký je účel? A náklady na synchronizaci?

Paralelizace je vhodná tam, kde máme výpočetně náročné operace a dají se rozdělit na několik kroků, které mohou běžet konkurenčně. Tj. nesdílí stav a slouží třeba jako mezivýsledky v nějakém větším výpočtu. Typickým příkladem může být Map/Reduce. Mapování může běžet paralelně, redukce pak proběhne po doběhnutí mapování, případně může také probíhat paralelně v nějakých clusterech dat.

Tak, snad je to už jasné. :)

Dalším velkým rozdílem je, že paralelizace zpracování je implementační detail, a ten by jako takový neměl prosakovat do APIs. Pokud tedy uvidíte metodu, která vrací Task nebo Task<T> a v její implementaci je Task.Run potažmo starší verze Task.Factory.StartNew, považujte to za smell.

Taky jsem ji měl

Popravdě, taky jsem takovou metodu měl. Zjednodušeně vypadala asi nějak tak:

class Client : ConsumerOf<Message> {
  readonly ManualResetEvent sync = new ManualResetEvent(false);

  public Task<Result> QueryAsync(Request request) {
    return Task.Factory.StartNew(() => {
      SendQuery(request.ToQuery());
      sync.Wait();
      return Result;
    });
  }

  public void Consume(Message msg) {
    sync.Set();
    Result = msg.ToResult();
  }
}

Jak vidíte, spouští se tu nový Task, který odešle dotaz a čeká na odpověď. Dotaz i odpověď jsou zprávy cestující po service busu. Všimněte si synchronizace (sic!) pomocí signální události. Navenek se to tváří jako asynchronní, ale je to celkem pěkně drahá sranda. Jak to udělat opravdu asynchronně?

Lepší řešení

Klíčem k úspěchu je využití třídy TaskCompletionSource:

class Client : ConsumerOf<Message> {
  readonly TaskCompletionSource tcs = new TaskCompletionSource();

  public Task<Result> QueryAsync(Request request) {
    SendQuery(request.ToQuery());
    return tcs.Task;
  }

  public void Consume(Message msg) {
    tcs.TrySetResult(msg.ToResult());
  }
}

Task nevytváříme pomocí TaskScheduleru, který ho alokuje někde na ThreadPoolu, ale pomocí TaskCompletionSource. Result se nastaví ve chvíli, kdy přijde odpověď. Nepotřebujeme žádné semafory, vše funguje pěkně asynchronně. Můžeme snadno přidat řešení chybových stavů, timeout nebo cancellation. Dle potřeb a celkem levně.

Závěr

Asynchronní APIs má cenu vystavovat pouze u IO operací. Výpočetně náročné operace můžeme vnitřně paralelizovat, ale nikdy tento detail nezveřejňujeme v APIs. Pro asynchronní operace nepoužíváme TaskFactory ani synchronizační objekty, ale TaskCompletionSource. Já už si to budu pamatovat. Doufám, že i vy. :)

Manifest Nejen Objektově Orientovaného Vývoje

14.01 - 25. března 2013 | Moje práce

Narazil jsem na velmi zajímavý manifest Nejen objektově orientovaného vývoje a myslím, že by neměl ujít pozornosti ani tady v československu.

Překlad Manifestu

Objevujeme lepší způsoby vývoje software tím, že jej tvoříme a pomáháme při jeho tvorbě ostatním. Při této práci jsme dospěli k těmto hodnotám:

  • Funkce a typy před třídami
  • Čistota před mutacemi
  • Kompozice před dědičností
  • Funkce vyššího řádu před method dispatch
  • Option před null

Tak jest, přestože je v bodech napravo hodnota (kromě null), bodů nalevo si ceníme více.

Principy

Ctíme následující principy:

  • Učení se vícero paradigmat nám umožňuje lepší řešení problémů.
  • Používáme nejvhodnější paradigmata pro daný problém, nejen ta, která aktuálně ovládáme.
  • Upřednostňujeme přemýšlení nad nejlepším řešením problému před slepým následováním best practice, směrnic, vzorů a frameworků.
  • Upřednostňujeme reprezentaci invariant na úrovni typů před reprezentací na úrovni hodnot.

Traits v C#

08.34 - 23. března 2013 | Moje práce

Poměrně užtečným konceptem v programovacích jazycích je trait. Můžete ho znát třeba ze Scaly, nebo se brzy objeví v PHP. Tyto jazyky mají pro traits prvotřídní podporu. Tedy, mají traits jako jazykový konstrukt. C# nic takového nemá. Nebo jo?

Co je to trait?

Trait je něco jako rozhraní. Umožňuje tedy vícenásobnou „dědičnost“. Narozdíl od rozhraní, má však maximální možnou implementaci. Když se podíváme do dokumentace Scaly na malý příklad:

trait Similarity {
  def isSimilar(x: Any): Boolean
  def isNotSimilar(x: Any): Boolean = !isSimilar(x)
}

Můžeme si všimnout, že trait obsahuje dvě metody, z toho jedna je abstraktní a druhá implementovaná pomocí té první. Koheze metod je důležitou vlastností traits. Ostatně by to tak mělo být i u definice rozhraní.

Jak na to v C#?

Není vám ten koncept trochu povědomý? Ne? A co třeba LINQ? Stačí, abyste implementovali metodu GetEnumerator a zbytek máte for free. Můžeme se tedy inspirovat u LINQu a podobně implementovat i Similarity:

interface ISimilarity<T> {
  bool IsSimilar(T other);
}

static class Similarity {
  public static bool IsNotSimilar<T>(this ISimilarity<T> @this, T other) {
    return !@this.IsSimilar(other);
  }
}

Kód sice není tak úsporný, jako v jazycích s prvotřídní podporou, ale funguje to a to je důležitý! :)

Parametrizace balíčku

11.40 - 21. března 2013 | Moje práce

Balíček je připravený k nasazení, ale je potřeba ho ještě nakonfigurovat. Jedna možnost je projít ručně konfigurační soubory na file systému… Počkat, kam se to vlastně nahrálo!?

Pokud vezmete balíček tak, jak byl, a nainstalujete ho přes IIS manager, dostanete možnost vyplnit pár vydedukovaných parametrů:

Tak jako dobrý, ale je tam spousta duplicit a co je to Parameter 1? Moc uživatelsky příjemné to teda není. Navíc, když to potvrdíme a pokračujeme dál, tak to spadne na instalaci windows služby. Proč? Protože ve scriptu je jiná cesta ke službě než kam se ve výsledku nakopírovala. Problémy, jen samé problémy. Naštěstí jdou snadno vyřešit.

Parameters.xml

MS Deploy naštěstí umí definovat a následně i nastavovat parametry. Můžeme to udělat buď pomocí parametrů z příkazové řádky nebo pomocí XML souboru s definicí parametrů. Já zvolil druhou variantu:

<parameters>
  <parameter tags='IisApp' defaultValue='MultiConnector\$instance' name='IIS Web Application Name'>
    <parameterEntry kind='ProviderPath' scope='IisApp'
      match='^$($webSource.Replace(`"\`", `"\\`").Replace(`".`", `"\.`"))$'/>
    <parameterEntry kind='ProviderPath' scope='setAcl'
      match='^$($webSource.Replace(`"\`", `"\\`").Replace(`".`", `"\.`"))$'/>
  </parameter>
  <parameter tags='WinSvc' defaultValue='$destPath\$instance' name='Windows service path'>
    <parameterEntry kind='ProviderPath' scope='dirPath'
      match='^$($winSource.Replace(`"\`", `"\\`").Replace(`".`", `"\.`"))$'/>
    <parameterEntry kind='TextFile' scope='Presync\.cmd$'
      match='$($destPath.Replace(`"\`", `"\\`").Replace(`".`", `"\.`"))\\$instance'/>
    <parameterEntry kind='TextFile' scope='Postsync\.cmd$'
      match='$($destPath.Replace(`"\`", `"\\`").Replace(`".`", `"\.`"))\\$instance'/>
  </parameter>
  <parameter tags='SqlConnectionString' defaultValue='$connString' name='Connection String'
      description='MultiConnector Connection String used in web.config by the application to access the database.'>
    <parameterEntry kind='XmlFile' scope='Web\.config$'
      match='//connectionStrings/add[@name=`"MultiConnector`"]/@connectionString'/>
    <parameterEntry kind='XmlFile' scope='Svc\.exe\.config$'
      match='//connectionStrings/add[@name=`"MultiConnector`"]/@connectionString'/>
    <parameterEntry kind='ProviderPath' scope='dbDacFx'
      match='^$($dbSource.Replace(`"\`", `"\\`").Replace(`".`", `"\.`"))$'/>
  </parameter>
</parameters>

V UI jsou definována políčka pro tři parametry.

  1. Nastavuje název webové aplikace v IIS
  2. Nastavuje cestu k windows service
  3. Nastavuje connection string aplikace

Tyto parametry jsou použity na několika místech (zbavili jsem se duplicit v UI). Například cesta k windows service se aktualizuje i ve vygenerovaných cmd souborech a tak se nám úspěšně nainstaluje windows služba i když změníme cestu k ní. Connection string určuje, kam se nadeployuje DB a aktualizuje se jak ve webové aplikaci, tak ve windows službě.

Při volání MsDeploy pro tvorbu balíčku z manifextu, přidáme následující parametr a máme vystaráno:

-declareParamFile:$paramFile

A výsledek vypadá nějak tak:

A máme uživatelsky přívětivý balíček, který nasadí databázi, web i windows službu, která se nainstaluje a rovnou i spustí. Life's good, dokonce i pro deployment specialisty. ;)