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

ActionInterceptor pro ASP.NET MVC

10.19 - 23. října 2011 | ASP.NET 2.0

V pátek jsem na twitteru publikoval ActionInterceptor a záhy jsem dostal reakci od Dana Kolmana, „proč ne ActionFilterAttribute?“ Sice jsem to na twitteru shrnul, ale klidně si to shrnu ještě jednou a obšírněji. Dělá mi to totiž dobře.

Action Filtry

Action filtry jsou – už od první verze ASP.NET MVC – způsobem, jak rozšiřovat akce o znovupoužitelné aspekty. Můžete tak zabalit často používané koncepty a jednoduše je aplikovat tam, kde je to třeba. Jenže action filtry mají několik zásadních omezení:

  1. Nepřijde mi úplně košer, aby atributy byly nositeli jednotky práce. Jejich úkolem je poskytovat metadata, nikoli funkcionalitu.
  2. Do verze 3 tu nebyla možnost injektovat závislosti, ve 3. verzi přibyl service lokátor (IDependencyResolver), který má k dokonalosti velmi daleko.
  3. Pokud chci přidat aspekt, musím zasahovat do rozšiřovaného kódu.

Všechny tři body jsou pro mne velmi zásadní chyby v návrhu a použitelnosti. Jak je vyřešit?

Action interceptor

Ve svých aplikacích spokojeně používám IoC kontejner Castle.Windsor, který umožňuje snadno vkládat aspekty. Děje se tak pomocí dynamicky vytvářených proxy nad dekorovanými objekty. Jak se má aspekt chovat a kde aplikovat můžeme snadno popsat v objektu, který implementuje rozhraní IInterceptor.

Ze základního popisu to nemusí být zcela zřejmé, ale aspekty můžete aplikovat pouze na virtuální metody. Proto když chcete přidávat aspekt do vašich kontrolerů musíte sáhnout k tomu, že všechny akce budou virtual (což je IMO nesmysl) nebo prostě využijete již existující extension pointy, které MVC framework nabízí. Kupodivu jsou to známé metody, které se objevují i v Action filterech. Implementace Action interceptoru bude tedy vypadat následovně:

using System;
using System.Linq;
using System.Web.Mvc;
using Castle.DynamicProxy;

public abstract class ActionInterceptor : IInterceptor, IActionFilter, IResultFilter {

 public void Intercept(IInvocation invocation) {
    switch (invocation.Method.Name) {
      case "OnActionExecuting":
        Intercept(invocation, OnActionExecuting);
        break;
      case "OnActionExecuted":
        Intercept(invocation, OnActionExecuted);
        break;
      case "OnResultExecuting":
        Intercept(invocation, OnResultExecuting);
        break;
      case "OnResultExecuted":
        Intercept(invocation, OnResultExecuted);
        break;
    }
    invocation.Proceed();
  }

  void Intercept<TContext>(IInvocation invocation, Action<TContext> action) {
    action(invocation.Arguments.First() as TContext);
  }

  public virtual void OnActionExecuting(ActionExecutingContext filterContext) {

  }

  public virtual void OnActionExecuted(ActionExecutedContext filterContext) {

  }

  public virtual void OnResultExecuting(ResultExecutingContext filterContext) {

  }

  public virtual void OnResultExecuted(ResultExecutedContext filterContext) {

  }
}

Užití je pak už pouhé podědění a přepsání patřičné metody podobně jako u Action filtrů.

Aplikace aspektu

Takovýto aspekt můžeme aplikovat několika způsoby. Nejpřímější cestou, bez nutnosti modifikovat cíl aspektu, je konfigurace kontejneru:

Component.
  For<LoggingInterceptor>().
  Lifestyle.Transient,

Component.
  For<HomeController>().
  Lifestyle.Transient.
  Interceptors<LoggingInterceptor>()

A jedem. :)

Další možností je aplikovat interceptor podobně jako action filtr – pomocí atributu:

[Authorization]
public class HomeController : System.Web.Mvc.Controller {

}

public class AuthorizationAttribute : Castle.Core.InterceptorAttribute {
  public AuthorizationAttribute()
    : base(typeof(AuthorizationInterceptor)) {
  }
}

Každý ze způsobů užití má své přednosti. Konfiguraci kontejneru používám tehdy, když potřebuji přidat dočasné nebo velice obecné aspekty. Naopak, pokud potřebuji explicitně vyjádřit nějaký koncept (autorizaci, invalidaci cache apod.), který má být při čtení controlleru zřejmý, použiji atributy.

Repository je anti-pattern

10.04 - 10. září 2011 | ASP.NET 2.0

Žádný vzor není špatný. Vezměme si chudáčka Singletona. Skvělý vzor pro udržení jediné instance v požadovaném scope. Jenže pak se dočtete, že Singleton je neslučitelný s Dependency Inversion Principle. Opravdu? Jak to, že používám DIP i Singletonty už léta? On je problem někde jinde. U lidí co nepřemýšlí. :) Fakt.

Mějme třídu, kde se na pěti místech v jejím kódu vyskytuje HttpContext.Current. Opravdu je to problem Singletonu? Ale kdepak, je to problem v užití, potažmo neschopností dělat čistý design. Přitom stačí nadefinovat členskou proměnnou typu HttpContext (lépe HttpContextBase) a tu si nechat naservírovat pomocí konstruktoru.

Volání HttpContext.Current bude v aplikaci pouze na jednom místě – v Composition rootu aplikace (popř. V konfiguraci IoC kontejneru.) Zbytkem aplikace nám bude protékat právě tato instance.

Doufám, že jsem na těchto pár řádcích dokázal, že Singleton není anti-pattern. Že anti-pattern jsou programátoři.

Říkám repository, myslím Data Mapper

A pomalu se dostáváme k jádru našeho problému. Vzory jsou tu od toho, aby sjednotili slovník, aby každý hned věděl o čem se bavíme. Jenže v případě Repository to došlo tak daleko, že lidé říkají Repository, ale 99 % myslí Data Mapper.

Repository nám schovává datový zdroj za rozhraní kolekce a umožňuje nad ním spouštět doménové dotazy. Světe div se, v dotnetu už něco takového máme. Ano, je to IEnumerable a LINQ!

V praxi se však setkáme s repository, která přímo skládá SQL dotazy, menežuje život entit a má tlusté rozhraní jako prase. Proč? Protože opět selhaly schopnosti návrhu. Zde konkrétně Single Responsibility Principle a Interface Segregation Principle.

Tlustá rozhraní

Tlustá rozhraní u “Repository” vznikají z potřeby zapouzdřit často používané dotazy do znovupoužitelné jednotky. Bohužel i v 21. století je jednotkou znovupoužitelnosti procedura. A tak s každým novým dotazem přidáme na rozhraní “Repository” další a další metodu. To nám spokojeně roste a roste. A opět porušujeme Interface Segregation Principle, Single Responsibility Principle a navíc i Open Closed Principle – a vytváříme krásnou kuličku sraček.

Třída, která toto rozhraní implementuje mívá nezřídka tisíc řádků a dá se v nít “snadno orientovat” pomocí stromečků. Jenže, jak praví Uncle Bob:

“V každé velké metodě se schovávají malé třídy!”

Ale málem bych zapomněl na Liskov Substitution Principle. Určitě jste všichni někdy narazili na potřebu cachovat. :)

Dekorováním za čistší kód!

A tak, když dospějeme k závěru, že tenhle dotaz se provádí dost často a přitom se data takřka nemění, budeme cachovat. První věc, co nás napadne, je nasrat tu cache přímo do repository. Tak to bude transparentí a nikde to nemusím měnit. Au. Lepším řešením je udělat třídu se stejným rozhraním, která v konstruktoru přijímá to samé rozhraní a potřebné metody odekoruje cachováním. Zbylé volá přímo na dekorovaném objektu.

Netvrdím, že jde o dokonalý návrh, ale jde rozhodně o mnohem čistší řešení. A to vše díky dodržování principů SOLIDní architektury. Opakuju se tu o tom už léta, ale asi je to stale potřeba. ;)

Lepší události v C#

14.11 - 10. května 2011 | ASP.NET 2.0

Když se nedávno ptali Anderse Hejlsberga na to, co by nejradši v C# změnil nebo udělal jinak, kdyby mohl, odpověděl, že by zrušil události. Přesněji, že by je spojil s konceptem vlastností. Na první pohled jsou totiž vlastnosti a události celkem podobné, ale na druhý zjistíte, že události mají poměrně složitou syntaxi a vyžadují psát ne moc pěkný kód.

Dřív (C# 1.0) to asi o moc líp udělat nešlo, ale dnes tomuto přání můžeme plně vyhovět. Stačí události vystavovat jako vlastnosti typu IObservable<T>. Toto rozhraní je jednou z novinek BCL .net 4.0 a hlavně reaktivních extenzí (Rx.net).

Pokud píšete nový kód, zvažte užití IObservable<T> namísto událostí. Např. F# má události takto řešené nativně.

Ze starého na nový

Vezměme si ukázkovou deklaraci události v C#:

Dříve jsme museli definovat vlastní typ delegáta události:

public delegate void ChangedEventHandler(object sender, EventArgs e);

Pak vystavit událost tohoto typu:

public event ChangedEventHandler Changed;

A nakonec někde v kódu událost odpálit:

if (Changed != null)
  Changed(this, e);

Nehledě na podivné přetížení operátoru pro zavěšení ovladače události:

List.Changed += new ChangedEventHandler(ListChanged);

Nové verze jazyka a BCL nám přinesly mnohá usnadnění od EventHandler<TEventArgs>, přes automatického odvození typu delegáta až po lambda výrazy. To však nic nezměnilo na samotné koncepci událostí…

A teď si to tedy přepišme na sjednocený model vlastností a událostí s užitím IObservable<T>:

readonly Subject<Unit> changed = new Subject<Unit>();

public IObservable<Unit> Changed {
  get { return changed; }
}

V kódu událost vyvoláme následovně:

changed.OnNext(new Unit());

A ke zpracování události se přihlásíme takhle:

List.Changed.Subscribe(ListChanged);

Krom čistšího kódu (všimněte si absence defenzivního kódu a přetěžování operátorů, které na první pohled nemusí být každému jasné, co dělá) máme i spoustu dalších výhod, které přináší reaktivní programování.

PS. Třidy Subject<T> a Unit nejsou součástí BCL, ale zmiňovaných Rx.net. Do projektu si je přidáte pomocí nuget příkazu Install-Package Rx-Main.

Volme správné názvy!

14.59 - 8. dubna 2011 | ASP.NET 2.0

Ovládat dokážeme pouze věci, které dokážeme pojmenovat. Pokud něco dokážeme pojmenovat, můžeme se na to domluvit i s ostatními a komunikace je počátkem vývoje. Nebo tak to nejspíš bylo u lidí. ;)

“There are only two hard things in Computer Science: cache invalidation and naming things” [Phil Karlton]

Ano jedním z nejtěžších problémů v softwarovém vývoji je správně pojmenovat věci. Ostatně to není problém pouze softwarového vývoje. :) Přijít s dobrým jménem vyžaduje zkušenost a dobré vyjadřovací schopnosti.

Každý programátor dokáže napsat kus kódu, kterému rozumí počítač, ale jen málo z nich dokáže napsat kód, kterému budou rozumět i lidé (ať někdo jiný nebo po čase on sám). A volba správných názvů přímo vede k dobrému porozumění kódu!

Základní pravidla pro psaní srozumitleného kódu

  1. Nepsat kód v jiném národním jazyce než je angličtina (většina programovacích jazyků má klíčová slova anglická).
  2. Používat popisné názvy.
  3. Nepoužívat kryptické kódování alá maďarská notace.

Pravidla pro pojmenování proměnných

Proměnná je předmět a tudíž by to měl její název reflektovat. Nejčastěji by názvy proměnných měla být podstatná jména (případně s přívlastky.) Výjimkou jsou boolovské proměnné a delegáty. Proměnné typu bool by se měly dát číst jako predikáty:

bool isEnabled = false;
bool hasChildren = true;

Pro delegáty platí většinou pravidla stejná jako pro tvorbu názvů metod (viz. dále.)

Délka názvu proměnné by měla reflektovat scope v jakém se nachází. Pokud mám proměnou v lambda výrazu, klidně může být jednopísmenková. Např. x => x * x. Podobně je na tom proměnná pro index ve smyčce nebo prvek foreach cyklu. (Pokud tělo cyklu není delší než 4 řádky.)

V případě proměnných z působností v metodách bychom už měli používat celá slova či popisná sousloví. Pokud máme proměnné s větším polem působnosti, jako jsou parametry metod nebo členské proměnné, měly bychom je pojmenovat delším popisnějším názvem.

readonly IArticlesRepository articlesRepository;

Pravidla pro pojmenování metod

U metod je to přesně naopak než u proměnných. Metody vyjadřují nějakou akci a proto by měly začínat slovesem. Veřejné metody by měly mít název krátký a jasný. Jedno písmenkové metody patří možná tak akorát do globálního scope (viz. jQuery ;) ale všichni víme, že globální scope je špatný, že ano?) Proč krátké? Protože je pravděpodobné, že je budeme používat často a nejspíš nejen my, ale i někdo jiný. Název však nesmí obsahovat zbytečné implementační detaily (ty by měly být v dokumentačním komentáři). Pěkným příkladem je třeba File.Open.

Naproti tomu privátní metody by měly mít popisné názvy dlouhé i několik slov nebo to může být krátká věta. Nahrazují nám totiž rychle zastarávající komentáře. Nebojte se často používat refactoring Extract Method na menší logické kusy kódu a pěkně je pojmenujte. Kód se vám bude číst mnohem líp!

Pravidla pro pojmenování tříd

Pro třídy platí podobná pravidla jako pro metody, akorát že třídy jsou opět podstatná jména. Privátní třídy pojmenováváme popisně, veřejné stručně a výstižně. U abstraktních tříd používáme abstraktní jména. Potomci pak toto jméno rozšiřují o konkrétní přívlastky.

Závěr

Volba správného jména je velice důležitá, protože, když se na váš kód podíváte pozorně, zjistíte, že jde v podstatě o variace neustále se opakujících konstrukcí a význam jim dodávají právě jména.

K čemu je dobré refaktorovat cykly do LINQ dotazů?

07.30 - 26. prosince 2010 | ASP.NET 2.0

V posledním zápichu jsem ukázal, jak refaktorovat cyklus na LINQ. Boris v komentářích poukázal na to, že výsledný kód nemusí být zrovna optimální, co se týče konzumace zdrojů. Já myslím, že je to malá cena, která je vyvážena obrovskou flexibilitou.

Nejprve se vraťme k ukázkovému kódu. Ještě trochu jsem ho přepsal, aby byl výsledek srozumitelnější:

static void ParseRouteData(Regex regex, Match match, RouteData data) {
  var groups = match.Groups.Cast<Group>();
  var groupNames = regex.GetGroupNames();

  var matchingPairs =
    from item in groups.
      Zip(groupNames, (group, name) => new {
        Pair = CreatePair(name, group.Value),
        IsMatch = group.Success,
      })
    where item.IsMatch && IsValidKeyAndValue(item.Pair)
    select item.Pair;

  matchingPairs.Do(SetRouteData(data));
}

static KeyValuePair<string, string> CreatePair(string key, string value) {
  return new KeyValuePair<string, string>(key, value);
}

static bool IsValidKeyAndValue(KeyValuePair<string, string> pair) {
  return IsNamedKey(pair) && IsNotEmptyValue(pair);
}

static bool IsNamedKey(KeyValuePair<string, string> pair) {
  return !(string.IsNullOrEmpty(pair.Key) || char.IsNumber(pair.Key, 0));
}

static bool IsNotEmptyValue(KeyValuePair<string, string> pair) {
  return !string.IsNullOrEmpty(pair.Value);
}

static Action<KeyValuePair<string, string>> SetRouteData(RouteData data) {
  return pair => data.Values[pair.Key] = pair.Value;
}
  1. Upravil jsem název funkce. Nyní už neiterujeme, ale po odhalení, co funkce vlastně dělá, můžeme říct, že parsuje routovací data. Odstínili jsme se od implementačního detailu. Jsme více deklarativní.
  2. Změnil jsem lambda based zápis na query like zápis. Ten se stejně na lambda based zápis převede kompilátorem, ale odstraní se zbytečný šum.
  3. Přibyly dvě další funkce. CreatePair jsem zavedl pro zkrácení řádku, aby se nemuselo tady na blogu scrollovat doprava, ale opět nás odstiňuje od zbytečné informace (implementační detail).
  4. Oddělil jsem dotaz a jeho samotné vykonání.

Proč tedy LINQ?

LINQ nám umožňuje zapisovat programy deklarativně. To znamená, že píšeme, čeho chceme dosáhnout, ne jak toho chceme dosáhnout. To je obrovská výhoda! Teď zapomeňme na to, že filtrujeme jednotky vyparsovaných hodnot. Mějme jich tisíce nebo rovnou milióny! Pokud potřebujeme výsledek rychle, jsme v imperativním případě namydlený. Sice je výkonostně optimálnější než ten deklarativní, ale pouze v případě, že máme k dispozici jediný stroj s jediným jádrem CPU.

Pokud máme jader víc nebo celý cluster, můžeme vykonání LINQ dotazu distribuovat na více jader (pomocí TPL) nebo do celého clusteru (pomocí Dryad). Více jader je u dnešních počítačů už standard a tak mikrooptimalizace jsou většinou ke škodě.

Samozřejmě existují výjimky, jako jsou např. mobilní zařízení, kde není výkonu nazbyt. Otázkou je, zda raději nepřenechat výpočetně složité operace na serverech? ;)

Pryč se stromečky! Nyní interaktivně.

07.35 - 22. prosince 2010 | ASP.NET 2.0

Včera jsme si předvedli jak pomocí několika základních refaktoringů snížit cyklomatickou komplexitu, zpřehlednit tak kód a snížit riziko výskytu chyb. Dnes se posuneme ještě o kousek dál a zbavíme se nadbytečného imperativního kódu.

LINQuj, LINQuj

Když jsem dorefaktoroval metodu IterateMatchingPathGroups do poslední verze s guardy, hned jsem viděl, že tohle je jasný LINQoký dotaz přepsaný imperativně. Původně jsem ho chtěl přidat do včerejšího článku, ale pak jsem zjistil, že nejde úplně o triviální dotaz a že to nechám na pozorném čtenáři na rozvinutí v komentářích.

Netrvalo dlouho a na twitteru mi psal Tomáš Petříček, že by tato metoda šla přepsat pomocí LINQu. Inu, funkcionální srdcař se nezapře. :) Tak mi to nedalo a píšu tenhle druhej díl.

Interaktivní extenze na scénu!

Jak už jsem psal, nejde o úplně triviální LINQ dotaz, protože tu potřebujeme mapovat dvě kolekce na sebe. Ve čtvrté verzi .net frameworku byl přidán kombinátor Zip, který dělá přesně to, co potřebujeme. (Pokud používáte .net 3.5, najdete tento v knihovně System.Interactive v rámci Rx.net.) Tady je výsledný kód, který si popíšeme dále:

static void IterateMatchingGroups(Regex regex, Match match, RouteData data) {
  GroupCollection groups = match.Groups;
  string[] groupNames = regex.GetGroupNames();

  groups.Cast<Group>().
    Zip(groupNames, (group, name) => new {
      Pair = new KeyValuePair<string, string>(name, group.Value),
      IsMatch = group.Success,
    }).
    Where(x => x.IsMatch).
    Select(x => x.Pair).
    Where(IsNamedKey).
    Where(IsNotEmptyValue).
    Do(SetRouteData(data));
}

static bool IsNamedKey(KeyValuePair<string, string> pair) {
  return !(string.IsNullOrEmpty(pair.Key) || char.IsNumber(pair.Key, 0));
}

static bool IsNotEmptyValue(KeyValuePair<string, string> pair) {
  return !string.IsNullOrEmpty(pair.Value);
}

static Action<KeyValuePair<string, string>> SetRouteData(RouteData data) {
  return pair => data.Values[pair.Key] = pair.Value;
}

Nejprve si připravíme vstupní kolekce skupin a jejich názvů. Protože GroupCollection implementuje pouze negenerické IEnumerable použijeme extenzi Cast<TResult>, která vytvoří generickou kolekci, nad kterou už se dají použít LINQ kombinátory. Dále obě kolekce sezipujeme a vytvoříme si pomocný anonymní objekt, který drží pár klíč-hodnota a informaci, zda byl úspěšný match.

Odfiltrujeme si neúspěšné matche a dál už potřebujeme jen pár klíč-hodnota, tak si ho vyselectujeme. Odfiltrujeme páry, které nemají klíč nebo je klíčem číslo, a pak ještě ty, které nemají hodnotu. Tím jsme sémanticky nahradili guard podmínky z cyklu předchozí verze. A dostali jsme se k tomu, co vlastně chceme udělat, tj. přidat nalezené hodnoty do kolekce routovacích dat. K tomu slouží kombinátor Do z interaktivních extenzí.

Pokud zrovna nechcete používat skvělou knihovnu Rx.net, můžete kombinátor Do nahradit zápisem ToList().ForEach nebo AsParallel().ForAllTPL.

A to je pro dnešek vše. Nějaká dotazy, výtky a tak? :)

Pryč se stromečky!

16.44 - 21. prosince 2010 | ASP.NET 2.0

Blíží se Vánoce, čas stresu a shonu za nejdražšími Vánočními dárky, pro naše nejdražší. Čas, kdy se ve velkém mordují kapři (navíc na veřejnosti, no fůj!) a kácí se stromky. Ano, ty které potom ověsíme nejrůznějšími koulemi a řetězy, aby pak několik týdnů opadávalo jehličí a vůbec. O tomhle ale psát nechci. :)

Dneska se podíváme na jiný úkaz. A to na problematiku zanořování kódu, který ve výsledku vypadá jako stromečky. (Pokud jste takový kód ještě neviděli, což pochybuju, tak ukázku najdete třeba tady v sekci Dark Magic.)

V čem je problém?

Možná si říkáte, „co to ten Roubíček zase vymejšlí? Vždyť takovej kód je přece v pořádku,“ (tj. dělá, to, co má.) „A navíc nemusíme chodit do lesa, protože máme stromečky pořád na očích!“ To je fakt. Jenže, ruku na srdce, dokážete odhadnout na první pohled, co takový kód vlastně dělá? Moc asi ne. A proč? Protože pět úrovní zanoření (nepočítám namespace, class ani metodu) je už trochu moc.

Jak moc? No hrozně moc. Fakt! Ono se to totiž dá krásně změřit, např. pomocí metriky Cyclomatic complexity.

Když se podíváme na tuto metriku u ukázkového příkladu, zjistíme, že metoda GetRouteData dosahuje hodnoty 17. Co to znamená? Když si přečtete odkazovaný popis metriky, dozvíte se také, že její hodnota určuje míru možného výskytu chyb. Čím větší je cyklomatická komplexita, tím více toho modul dělá a zvyšuje se riziko přehlédnutých chyb. Doporučuje se mít komplexitu do deseti a tady tuto hodnotu překračujeme takřka dvojnásobně.

It smells like refactoring time!

Refaktorovat, refaktorovat, refaktorovat

Jednoduše jsme pomocí metriky odhalili kandidáta, kde budeme kácet stromy. Když se podíváme pozorně na ukázkový příklad, můžeme si všimnout bloků kódu, které začínají komentářem. Budu se opakovat, ale nepište zbytečný kód! Tohle jsou jasné samostatné metody. Použijeme tedy refaktoring Extract method. Po extrahování čtyř metod, už se nám metoda vejde na jednu obrazovku a cyklomatická komplexita spadne na trojku. Takřka na jednu šestinu!

Huh. A to je teprve začátek.

Po prvním kole vypadá výsledek asi tak:

public override RouteData GetRouteData(HttpContextBase httpContext) {
  // Build regex
  domainRegex = CreateRegex(Domain);
  pathRegex = CreateRegex(Url);
  // Request information
  string requestDomain;
  string requestPath;
  RequestInformation(httpContext, out requestDomain, out requestPath);
  // Match domain and route
  Match domainMatch = domainRegex.Match(requestDomain);
  Match pathMatch = pathRegex.Match(requestPath);
  // Route data
  RouteData data = null;
  if (domainMatch.Success && pathMatch.Success) {
    data = new RouteData(this, RouteHandler);
    AddDefaultsFirst(data);
    IterateMatchingDomainGroups(domainMatch, data);
    IterateMatchingPathGroups(pathMatch, data);
  }
  return data;
}

Není to žádná nádhera, ale je mnohem přehlednější, než originál. A v řeči čísel:

Rozplétáme podmínky

Tak, snížili jsme komplexitu metody, ale tiše nám narostla komplexita třídy. Klidně můžeme pokračovat v cestě, to zvládneme později. Teď se zaměříme na ty velký šestky. Na první pohled cyklus se spoustou zanořených podmínek. Pryč s nimi! Teď přijde na řadu refaktoring Flatten Conditional a z vše objímajícího ifu uděláme jen guarda. Dále tu mám dva zanořený ify? Proč, když je můžeme logicky spojit operátorem &&? Směle do toho! Pro změnu použijeme refaktoring Combine Conditionals a vytvoříme jednu dlouhou a ošklivou podmínku…

Čas na refaktoring Extract method a podmínku pěkně smysluplně pojmenovat. Po pár magických kombinacích kláves se nám z týhle zrůdičky:

private static void IterateMatchingPathGroups(Match pathMatch, RouteData data) {
  for (int i = 1; i < pathMatch.Groups.Count; i++) {
    Group group = pathMatch.Groups[i];
    if (group.Success) {
      string key = pathRegex.GroupNameFromNumber(i);
      if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0)) {
        if (!string.IsNullOrEmpty(group.Value)) {
          data.Values[key] = group.Value;
        }
      }
    }
  }
}

Stane opět poměrně snadno pochopitelný kód:

private static void IterateMatchingPathGroups(Match pathMatch, RouteData data) {
  for (int i = 1; i < pathMatch.Groups.Count; i++) {
    Group group = pathMatch.Groups[i];
    if (IsNotMatch(group))
      continue;

    string key = pathRegex.GroupNameFromNumber(i);
    if (IsInvalidKeyOrValue(group, key))
      continue;

    data.Values[key] = group.Value;
  }
}

Navíc, když se teď na ten kód podíváme, zjistíme, že se ty původní šestky (z kterých už jsou čtyřky) liší jen v tom, že používají každá jinou členskou proměnou, naštěstí stejného typu. Tak jdeme zase refaktorovat. :) Nejprve si tedy tuto proměnou uložíme lokálně na začátku metody a ze zbytku extrahujeme metodu novou s jedním parametrem navíc.

U druhý čtyřky už to dělat nemusíme, páč výsledný extrakt z té první je plně vyhovující našim potřebám a tak ho zavoláme jen s jiným parametrem. Ze dvou čtyřek máme jednu a dvě jedničky. A komplexita třídy se nám vrátila k původní hodnotě. Ano, máme tu teď spoustu malých metod, které ale mají jasný účel. A když se podíváme blíž, zjistíme, že můžeme úplně v klidu některé z nich extrahovat no úplně nové třídy. Ale to už je na jinou pohádku.

Stromečky jsou totiž fuč. :)

PS. Zmíněné refaktoringy a vizualizace metrik jsou součásti skvělého produktu Refactor Pro! nebo samotné refactoringy v neplacené verzi Code Rush Xpress.

Single Responsibility Principle in the wild

13.31 - 20. listopadu 2010 | ASP.NET 2.0

Tak jsem se tak koukal na poslední CloudCover, kde představovali novou službu Windows Azure AppFabric Caching. Služba je to jistě zajímavá a je jednou z možností jak zajistit session state affinity v Azure. Ale proč o tom píšu.

Součástí epizody byla i živá ukázka, jak přidat cachování do existující aplikace.

Teď to možná některé NHibernate znalé veterány zaskočí, ale v Microsoftím světě ORM si o něčem jako 2nd level cache můžete nechat zdát. Vůbec tam spoustu krásných věcí nemají, ale o tom třeba až jindy.

No prostě tam měli repository (používat repository, tam kde je LINQ, je už samo o sobě obskurní, ale budiž), která vypadala nějak tak:

public class ProductsRepository : IProductsRepository {

  public List<string> GetProducts() {

    List<string> products = null;

    NorthwindEntities context = new NorthwindEntities();
    var query = from product in context.Products
                select product.ProductName;

    products = query.ToList();

    return products;
  }
}

Mno, měli tedy respository produktů, která vracela názvy všech produktů přímo z databáze. No a pak se jali přidat cachování takového listu:

public class ProductsRepository : IProductsRepository {

  DataCacheFactory dcf;

  public ProductsRepository() {
    dcf = new DataCacheFactory();
  }

  public List<string> GetProducts() {

    List<string> products = null;

    DataCache dc = dcf.GetDefaultCache();

    products = dc.Get("products");

    if (products != null) {
      return products;
    }

    NorthwindEntities context = new NorthwindEntities();
    var query = from product in context.Products
                select product.ProductName;

    products = query.ToList();

    dc.Put("products", products);

    return products;
  }
}

Úžasné! Jenže tím jsme porušili princip jediné zodpovědnosti. Teď se nám repository účastní hned na dvou záležitostech – na své původní (načíst data z databáze) a nově na cachování načtených dat. Takže to smrdí. It smels like refactoring time!

Jak na to?

Cachování je věc hodná vlastního aspektu. Když se podíváme na výsledný kód, zjistíme, že jsme neudělali nic jiného než, že jsme odekorovali načítání dat logikou pro cachování. Tudíž zvolíme vzor dekorátor! Repository vrátíme do původního stavu, protože ten svůj účel splňuje a přidáme další třídu s jedinou zodpovědností:

public class CachedProductsRepository : IProductsRepository {

  const string ProductsKey = "products";

  readonly DataCacheFactory cacheFactory;
  readonly IProductsRepository repository;

  public CachedProductsRepository(DataCacheFactory cacheFactory, IProductsRepository repository) {
    this.cacheFactory = cacheFactory;
    this.repository = repository;
  }

  public List<string> GetProducts() {

    var cache = cacheFactory.GetDefaultCache();
    var products = cache.Get(ProductsKey);

    if (products == null) {
      products = repository.GetProducts();
      dc.Put(ProductsKey, products);
    }

    return products;
  }
}

No a máme to! Dokonce si teď můžeme vybrat, kdy chceme používat cache nebo sahat do databáze přímo. A to vše díky díky dodržování Open/Closed principle a jednoduché kompozici objektů, které dělají jen a pouze to, co mají. :)

Můj pohled na F# se mění

14.04 - 9. listopadu 2010 | ASP.NET 2.0

Za posledních pár měsíců se mi celkem změnil pohled na F#. Ještě do nedávna jsem si myslel, na co další jazyk, když C# zvládá funkcionální paradigma taky obstojně?

Ale pak jsem psal v C# pár aplikací, kde docházelo k masivnímu přelívání dat a jejich transformaci. V podstatě jednoduchý funkcionální kód jsem obaloval poměrně košatou objektovou strukturou. A pak jsem začal číst knihu Expert F# 2.0 a měl pár pěkných aha momentů. A začal jsem vidět, jak jsem si mohl spoustu práce ušetřit…

Teoreticky. :)

Facebook Graph API pro .net

09.19 - 6. srpna 2010 | ASP.NET 2.0

Možná jste postřehli, že před nedávnem vypustil Facebook na svém githubu csharp SDK pro jejich Graph API. Z počátku mě ta zpráva potěšila, protože Facebook Toolkit, který jsem používal, tak nějak nefunguje s novým JS SDK a vůbec má zvláštní problémy.

Po chvilce nadšení přišla chvilka zklamámí. Podíval jsem se na kód a ukázky a zděsil se. S dotnetem to má asi tolik společného jako, že to jde zkompilovat pomocí csc. Tohle opravdu nechcete používat. Na netu jsem objevil i další implementace Graph API, ale stále nic, co bych chtěl používat.

Nakonec jsem se rozhodl napsat vlastní implementaci. Základ je inspirován již zmiňovaným csharp SDK, ale napsanej tak, aby využíval věci, co framework již nabízí, místo vlastní implementace JSON jsem použil osvědčený JSON.NET, přidal podporu pro ověřování pomocí OAuth i klientské JS ověření pomocí tlačítka fb:login.

No a hlavně další vrstvu abstrakce včetně objektů API.

Tohle všechno je moc hezký, mně se to dobře používá, je to IoC ready, ale zatím chybí podpora pro desktopové ověřování. Ale hlavně mi nefunguje postování na wall, což je celkem zásadní problém. Zkoušel jsem už spoustu cest. Práva na publikování publish_stream uživatel potvrdí, ale Facebook s ledovým klidem odpovídá:

Exception: (#200) The user hasn't authorized the application to perform this action

A mě z toho může trefit šlak. :)

Proto moc prosím, zkuste si stáhnout projekt, a trošku si pohrát, jestli třeba nebudete mít větší štěstí. Případného mistra odvšivovače pak pozvu na skleničku něčeho moc dobrého.

Pokud si nebudete vědět rady, jak to rozběhat, klidně mi napište mail nebo na IM. Dám vám klíče pro testovací účely a jistě další dobré rady. :)

Update

Chybu jsem nakonec odhalil a pozval se na pár Black Labelů. Chyba byla v tom, že se nastavavovala špatná hodnota vlastnosti Method objektu HttpWebRequest. Namísto hodnoty POST tam byl Post (hodnota enumerce HttpVerb a ToString). Navíc se ještě špatně encodovalo tělo requestu, kde se encodovalo všechno, ne jen hodnoty. Škoda, že se takováto chyba nedala vyčíst z chybové hlášky, která byla v tomto případě celkem zavádějící. Každopádně GraphApi už je plně funkčí a můžete ho plnohodnotně požívat.