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

LINQ a lenivé vyhodnocování

08.02 - 21. února 2010 | ASP.NET 2.0

Občas není na škodu být lenivý, mnohdy se to vyplatí! To platí i o našich programech, zejména o těch, které zpracovávají veliké množství dat. Jistě si říkáte, jak může být můj program lenivý, já přeci chci, aby byl co nejvýkonnější. Ano, to se nevylučuje, právě naopak! :)

Když jsem nedávno psal o tom, že Vracet list je špatné, neshledal jsem se s velikým porozuměním. Tento článek byl o návrhu rozhraní našich tříd. Dneska se podíváme na implementační detaily a výhody plynoucí z vracení (zvracení?) IEnumerable<>.

LINQ2SQL a metoda ToList()

Začněme tedy u nevýhod užívání generického listu v LINQu. Vsadím se, že většina z vás pro vykonání LINQového dotazu do databáze zavolá metodu ToList(). Okamžitě se tím vykoná dotaz a jeho výsledek je natažen do paměti, konkrétně jako položky generického listu. Jistě se tím zvýší výkon při iteraci přes výslednou kolekci. To je super, ale pro pro výpis používat velice drahý obal v podobě listu? List umožňuje kolekce snadno modifikovat, to je jistě skvělá vlastnost, ale také sebou nese zbytečně alokovanou paměť navíc.

Mnohem výhodnější je volat metodu ToArray(), která vykoná dotaz úplně stejným způsobem, ale vezme si jen tolik paměti, kolik opravdu potřebuje, nehledě na to, že samotný .net runtime je na práci s Array optimalizovaný. Jednoduchou změnou můžeme ušetřit spoustu paměti a zlepšit tak výkon naší aplikace. Pokud jsme však do našich rozhraní zanesli IList<>, musíme teď měnit spoustu kódu, protože náš původní návrh byl krátkozraký.

Zpátky k lenivosti

To, co jsme si popsali v předchozích řádcích, nemá s lenivostí stále nic společného, jde jen o malou optimalizaci dychtivého přístupu k datům (eager loading). Ten samozřejmě nemusí být z principu špatný, ale hodí se pouze pro data omezeného rozsahu. Pokud zpracováváme velké nebo předem neznámé množství dat je lepší zlenivět. Tím se opět vracíme k IEnumerable<>.

Vezměme si hypotetický příklad že náš program bude zpracovávat nekonečný zdroj pravdy:

static IEnumerable<Boolean> GetInfinityTruth() {
  while (true) {
    yield return true;
  }
}

Fůj, napsal jsem nekonečnou smyčku. Hele, ale jak jinak byste chtěli udělat nekonečný zdroj pravdy? Pomocí listu? Těžko. ;) Takovýhle kód je zcela validní a bezproblémový. Tedy do doby, než se najde někdo šikovný, komu se hodí list, a nad GetInfinityTruth() zavolá ToList(). Asi nám vyskočí OutOfMemoryException. :)

Vážně, výhodou tohoto kódu je, že (teoreticky) vrací nekonečné množství dat a přesto se s ním dá v klidu pracovat, aniž by nám sežralo adekvátní množství paměti (opět nekonečné). A to díky lenivosti! S takovýmto přístupem se nám totiž vyhodnocuje vždy jen jeden prvek z kolekce a to až v případě, že je opravdu potřeba. Oddalujeme načítání prvku do doby, kdy s ním opravdu budeme něco dělat.

Další výhodou je, že zcela bez problémů, můžeme celou tu nekonečnou pravdu popřít:

static IEnumerable<Boolean> Disclaim(this IEnumerable<Boolean> source) {
  return source.Select(value => !value);
}

Samozřejmě bych to mohl napsat i takhle:

static IEnumerable<Boolean> Disclaim(this IEnumerable<Boolean> source) {
  foreach(bool value in source) {
    yield return !value;
  }
}

Ale to je příliš mnoho psaní. Nepište zbytečný kód!

Teďka, když zavolám GetInfinityTruth().Disclaim(), dostanu nekonečný zdroj nepravdy, tedy samé lži. :) Opět to funguje tak, že je popřen každý konkrétní prvek, až když na něj dojde, ne všechny najednou, to by bylo moc práce. On se totiž příjemce naší nekonečné pravdy může kdykoli rozhodnout, že už ho to nebaví. A co my pak s tím, že jsme mu dopředu připravili krásné lži? Nic. Byla to zbytečná práce. V tomto případě se lenivost vyplatila.

Závěr

„Poslouchaj Roubíček, oni jsou nejspíš blázen. Nás tu krměj o nekonečné pravdě, co my s tím?“ No, je to určitá abstrakce. ;)

Související

Autor: Aleš Roubíček | 2x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Doménové dotazy

13.53 - 30. ledna 2010 | ASP.NET 2.0

V každé aplikaci, kde se pracuje s daty, se dostaneme do situace, kdy se nad daty potřebujeme dotazovat. Pokud přijmeme za svůj vzor Repository, můžeme se dostat do situace, podobné té v následující ukázce:

public interface IArticlesRepository {
  Article FindById(long id);
  IEnumerable<Article> FindAll(PagingInfo paging);
  IEnumerable<Article> FindByCategory(Category category, PagingInfo paging);
  IEnumerable<Article> FindByAuthor(User author, PagingInfo paging);
  IEnumerable<Article> FindByTag(string tag, PagingInfo paging);
}

Jak vidno s každým typem dotazu, rozšiřujeme rozhraní naší repository. Což porušuje Open/closed principle – jeden z pilířů Solidní architektury. Ten nám říká, že objekt by měl být otevřený k rozšíření, ale uzavřený k modifikaci. Přidáváním dalších metod ho očividně rozšiřujeme. Jak na to?

Jedním z možných řešení je zavedení dotazovacích objektů, kde využijeme kompozice k docílení lepšího návrhu. A protože jde o obecný mechanismus, můžeme ho pěkně generalizovat. Dále budu předpokládat, že pracujeme vůči datovému zdroji podporujícímu LINQ dotazování, jako je NHibernate.Linq, LINQ2SQL nebo EntityFramework.

public interface IQueryObject<T> {
  int Count(IQueryable<T> table);
  IEnumerable<T> Fetch(IQueryable<T> table);
  T FetchOne(IQueryable<T> table);
}

Dotazovací objekt má tři metody. První spočítá kolik objektů splňuje podmínky dotazu. Druhá vrátí kolekci vybraných objektů a poslední vrací jedinečný záznam. K takovému rozhraní si můžeme vytvořit bázovou třídu, která nám pomůže s rychlejším vytvářením konkrétních dotazů.

public abstract class QueryObjectBase<T> : IQueryObject<T> {

  protected QueryObjectBase<T>(PagingInfo paging) {
    Paging = paging;
  }

  public PagingInfo Paging { get; private set; }

  protected abstract IQueryable<T> CreateQuery(IQueryable<T> table);

  public virtual int Count(IQueryable<T> table) {
    return CreateQuery(table).Count();
  }

  public virtual IEnumerable<T> Fetch(IQueryable<T> table) {
    return CreateQuery(table).
      Skip(Paging.Skip).
      Take(Paging.PageSize).
      ToArray();
  }

  public virtual T FetchOne(IQueryable<T> table) {
    return CreateQuery(table).FirstOrDefault();
  }
}

Konkrétní dotazovací objekt pak ve většině případů implementuje jedinou metodu, která vrací LINQ dotaz. V případě potřeby si však konkrétní implementace může připravené metody změnit k obrazu svému. Máme zde zavedenou i konvenci stránkování, která předchází generování neomezených dotazů do databáze. A teď už nám zbývá jen zabalit to pěkně do nějakého DAO objektu.

public interface IQueryExecutor<T> {
  int Count(IQueryObject<T> query);
  IEnumerable<T> Fetch(IQueryObject<T> query);
  T FetchOne(IQueryObject<T> query);
}

DAO objekt jediný zná konkrétní implementaci datového uložiště, kterou předává do dotazovacího objektu jako IQueryable<T>. Výsledné použití v praxi může pak vypadat následovně:

var articles = _articlesDao.Fetch(new CategoryArticlesQuery(category, pagingInfo));

Kde konkrétní implementace dotazovacího objektu vypadá takto:

public class CategoryArticlesQuery : QueryObjectBase<Article> {

  public CategoryArticlesQuery(Category category, PagingInfo paging)
    : base(paging) {
    Category = category;
  }

  public Category Category { get; private set; }

  protected override IQueryble<Article> CreateQuery(IQueryable<Article> table) {
    return from article in table
           where article.Category == Category
              && article.IsPublished
           orderby article.PublishDate descending
           select article;
  }
}

Výhodou i je, že pokud potřebujeme upravit dotaz, děláme to vždy jen jednou na jednom místě.

Autor: Aleš Roubíček | 5x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Lokalizované URL v ASP.NET MVC

11.26 - 24. ledna 2010 | ASP.NET 2.0

Pokud se dostanete do situace, že budete potřebovat lokalizovat URL adresy vaší webové aplikace, máte hned několik možností, jak to řešit.

Jedno z možných řešení, které se mi ani trochu nelíbí, popsal Augi ve svém článku ASP.NET MVC – lokalizace URL. Já mám raději jednoduchá řešení, která nepotřebují hackovat systém. :)

Když jsme začali psát Trop, byl celý v angličtině. Díky konvencím, které jsem popsal v článku Pojmenované routy v ASP.NET MVC, měla naprostá většina stránek svojí vlastní routu. A protože definice routy není nic jiného než textový řetězec, přesunul jsem jej do resource souboru Routes.resx. To byl první krok.

Když jsme vymýšleli, jakým způsobem poběží jednotlivé jazykové mutace, bylo jasné, že každá mutace bude mít vlastní top-level doménu a bude to samostatná instance aplikace. To je super, protože se pak dá využít konfigurační sekce globalization, kde se nastaví jazyk prostředí konkrétní instance, podle kterého se i vybírají konkrétní resource soubory.

Česká instance pak má ve web.configu následující řádek:

<globalization culture="cs-CZ" />

Díky tomu se použijí routovací pravidla z Routes.cs.resx a URL adresy se generují česky, aniž bych musel v kódu cokoli změnit. Samozřejmě nejsou přeložená všechna routovací pravidla, spousta akcí, které jsou pouze POST, překlad nepotřebuje, protože nejsou součástí prezentační vrstvy.

Nakonec veškeré úsilí, které bylo k implementaci lokalizovaných URL potřeba, bylo v tom, embedovat definované routy do resource souboru, s čímž mi ochotně pomohl Refactor! Pro. :) Zbytek je věc konfigurace v deployovacím scriptu. Ale to už je na jiné povídání.

Autor: Aleš Roubíček | 1x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Ošetření chybových stavů v ASP.NET MVC

12.50 - 22. prosince 2009 | ASP.NET 2.0

Každá aplikace se může dostat do stavu, kdy není vše, jak má být, a nastane chyba. Tyto stavy bychom neměli ignorovat, ale pečlivě ošetřovat. Jak na to se podíváme v tomto spotu.

ASP.NET obsahuje poměrně sofistikovaný mechanismus na zpracování chyb. Ukazuje celkem podrobné chybové výpisy, které jsou nám vývojářům velice užitečné. Nicméně nejsou užitečné naším uživatelům a ještě mohou leccos prozradit našim útočníkům. Proto by v produkci měly být tyto hlášky zakázané.

Konfigurace

Každá aplikace v produkci by měla mít nastavené pěkné chybové stránky, bez podrobností, co se stalo, ale s informacemi, kam dál uživatel může pokračovat. Popřípadě mu pomoci nalézt to, co hledal. K tomu slouží konfigurační element customErrors, kde můžeme nastavit režim zobrazování a jednotlivé stránky, které se mají zobrazit na určitý chybový kód.

Bohužel, systém je navržen zcela kokotsky a pokud chcete v konfiguraci nastavit pro 404 a 500 jiné stránky, jsou vraceny s kódem 200. Proto využijeme jen následující konfiguraci. Zbylá je celkem k ničemu.

<customErrors mode="RemoteOnly" />

Tím docílíme toho, že všechny requesty, které nejsou z tohoto serveru obdrží pěkné chybové stránky. Dotaz z daného serveru dostane podrobný chybový výpis, což se může hodit.

Zpracování chyb

ASP.NET MVC má v sobě základní mechanismus pro ošetřování chyb. Je jím ActionFilter HandleError, který renderuje pohled Error.aspx, pokud dojde k probublání neošetřené výjimky až do akce řadiče.

[HandleError]
public class MyController : Controller {
}

Procento chyb, takto zachytitelných, jistě není malé, ovšem není 100%. Nemusíme chodit pro příklad daleko a stačí udělat chybku v šabloně pohledu. Hned tu máme nepěknou chybovou stránku.

Takovouto chybu odchytíme až v Global.asax:

protected void Application_Error() {
#if DEBUG
  return;
#endif

  Exception exception = Server.GetLastError();

  Response.Clear();

  RouteData routeData = new RouteData();
  routeData.Values.Add("controller", "Error");

  if (IsPageNotFoundException(exception as HttpException)) {
    routeData.Values.Add("action", "NotFound");
  }
  else {
    routeData.Values.Add("action", "Error");
  }

  Server.ClearError();

  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}

Pokud máme aplikaci v debug modu (typicky u vývojáře) je vhodné nechat chyby probublat až k němu. Pokud jsme ale v releasu musíme chybu zpracovat. Na výpis chybové stránky nám poslouží ErrorController, který má dvě akce: NotFound pro 404 a Error pro 500.

public class ErrorController : Controller {

  [StatusCode(HttpStatusCode.NotFound)]
  public ActionResult NotFound() {
    return View();
  }

  [StatusCode(HttpStatusCode.InternalServerError)]
  public ActionResult Error() {
    return View();
  }
}

Díky způsobu hledání šablon v ASPX View enginu, můžeme na Error využít stejnou Error.aspx ve složce Shared jako pro chyby zachycené už na řadiči.

Neplatná referenční identita

Teď se ještě dostaneme k tomu, proč tu máme zpracování HttpException s kódem 404. Kde se vezme?

Typicky, když máme nějakou detail page, snažíme se pomocí nějakého jedinečného identifikátoru natáhnout zobrazovanou entitu. Pokud žádnou nenajdeme je vhodné vyhodit výjimku a protože jsme na úrovni webové aplikace, měla by to být HttpException s kódem 404, která říká, že hledaná stránka nebyla nalezena.

Její zpracování už máme výše vyřešeno. A to je snad pro dnešek vše. :)

Autor: Aleš Roubíček | Zatím bez komentáře | Delicious | FriendFeed | Facebook | Linkuj!

Community meeting .net

12.50 - 7. října 2009 | ASP.NET 2.0

Máte nějaké dotazy ohledně vývoje v ASP.NET potažmo v .net obecně? Máte chuť se seznámit s dalšími lidmi, kteří se dotnetem zabývají, nebo jen chcete osobně potkat lidi, které znáte jen virtuálně? Budete v sobotu 17. Října na WebExpu 2009?

Pokud jste alespoň na jednu z výše položených otázek odpověděli kladně, přijďte se podívat na Community meeting .net, kde najdete Štěpána Bechynského a moji maličkost. Můžete se nás zeptat na cokoli o ASP.NET a dalších dotnet technologiích a pokud to bude v našich silách, tak vám odpovíme. Štěpán slíbil, že součástí meetingu bude i coolkeg s náplní od Staropramenu, tak kdo bude mít žížu, tak ať si přinese vlastní pulitr, pokud nechce pít z plastiku…

Naviděnou příští sobotu!

Autor: Aleš Roubíček | 1x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Odsazování kódu a bloky

20.42 - 20. září 2009 | ASP.NET 2.0

Konečně jsem se dostal k tomu, abych si otevřel knihu Co programátory ve škole neučí. Hezky shrnuje žalostný stav našeho školství s ohledem na přípravu budoucích vývojářů. Dále zanáší pravidla pro psaní přehlednějšího kódu. S mnoha pravidly, které autor zanáší, musím plně souhlasit. K některým mám výhrady a jedno bych teď chtěl doplnit.

Pravidlo sedmé

Kód musí obsahovat dostatek volných řádek oddělujících od sebe krátké logické celky.

Tohle pravidlo mi mluví z duše. Sám se vypořádávám s kódem kolegy, který o „odstavci“ v životě neslyšel. Číst monolitický text je velice těžké a ještě těžší je mu porozumět. Pan Paleta (autor knihy) dále doporučuje:

Zdrojový kód zpřehledňují prázdné řádky, které by podobně jako tačka za větou měly „ukončovat myšlenku.“ Nepřerušená sekvence příkazů může být dlouhá asi tak dva až pět řádků, jen výjimečně o něco více. Někdy je vhodné jako první řádek sekvence uvést krátký komentář…

Schválně jsem zvýraznil poslední větu, protože to je ta část, ke které mám výhradu. Možná si vzpomenete, jak jsem minule psal: Nepiště zbytečný kód. Klidně takový kus kódu napiště. Ale ihned ho refaktorujte! Logický blok uvozený konetářem, je totiž ideální místo pro vytvoření metody. Název této metody je právě obsah komentáře a tělem následný logický blok.

Pokud máte Refactor! Pro od Developer Express, stačí onen blok označit a stisknout Ctrl+; (klávesa nad Tab) a vybrat Extract method. Tento refaktoring udělá přesně to, co jsem popsal v minulém odstavci.

Autor: Aleš Roubíček | 2x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Nepište zbytečný kód

07.19 - 3. srpna 2009 | ASP.NET 2.0

Jedním ze základních pravidel psaní přehledného kódu je nepsat zbytečný kód. Pojďme si ukázat pár příkladů zbytečného kódu, kterému je zahodno se raději vyhnout.

Komentáře

Programátoři jsou často velice kreativní lidé, ale svou kreativitu si vybíjí nesprávným způsobem. Tak se může stát, že během své kariéry narazíte na skvosty typu:

  1. zakomentované kusy kódu
  2. označení dočasného kódu v kometáři
  3. vylévání srdíčka nad vlastní blbostí

K bodu prvnímu. Když něco takového potkáte, rovnou to smažte, nesnažte se nad tím přemýšlet. Jestli někdy ten kód budete potřebovat, najdete ho ve vašem VCS. Pokud se přistihnete, že máte označený blok kódu a chystáte se zmáčknout magickou kombinaci kláves Ctrl+K, C, ušetřete si práci a rovnou zmáčkněte Delete. ;)

K bodu druhému. Nejprve zjistěte, kdo to napsal a dejtemu rovnou pohlavek nebo seberte prémie. Nejlépe oboje, u někoho lépe fungují podmíněné reflexy spojené s fyzickou bolestí, někdo si vzpomene, že mu nezbyly peníze na tu skvělou chlastačku a už to nebude chtít opakovat. Zjišťovat, co na koho platí je moc drahé. ;)

Pro představu, našli jsme takovýto kód:

//Testovaci verze
if (Request.Cookies.AllKeys.Contains("ticket")) {
  InvitedUser iuser = _invitedUsersRepository.FindByHash(Request.Cookies["ticket"].Value);
  if (iuser != null) {
    iuser.Valid = false;
    _invitedUsersRepository.Save(iuser);
  }
}
// --
;

Autora jsme ztrestali. První krok máme za sebou, co dál? Tohle asi hned smazat nepůjde. Prvním možným krokem, je nadefinovat build symbol a tyto bloky obalit preprocesorovou podmínkou:

#if TEST_VERSION
if (Request.Cookies.AllKeys.Contains("ticket")) {
  InvitedUser iuser = _invitedUsersRepository.FindByHash(Request.Cookies["ticket"].Value);
  if (iuser != null) {
    iuser.Valid = false;
    _invitedUsersRepository.Save(iuser);
  }
}
#endif
;

Sice jsme se nezbavili špatného kódu, ale aspoň pujde dočasná featura vypnout z jednoho místa a kód hovoří trochu jasněji.

Pokud napíšete blok kódu a pocítíte potřebu uvodit ho komentářem, nebo takových bloků máte zasebou víc, je čas refaktorovat! Místo komentářů vytvořte metody s popisnými názvy (ekvivalent komentáře). Komentáře mají tu vlastnost, že rychle ztrácejí význam a zůstávají v místech, kde už není co komentovat.

Pravidlo: Když ucítíte potřebu napsat v kódu komentář, zamyslete se, zda nenajdete lepší způsob jak tuto myšlenku vyjádřit.

Poznejte svůj jazyk

Za programátora se považuje každý, kdo se v nějakém jazyce naučí pár příkazů. Někdo si vystačí s málem a pak mu nezbývá než prasit. Základem je poznat jazyk, ve kterém se chystáme sdělovat své myšlenkové pochody. Zdrojový kód se mnohem častěji čte než píše, proto by měl obsahovat co nejméně zbytečností, které odvádějí pozornost.

Začněme třeba u JavaScriptu. Často se setkávám s kódem, který napovídá, že autor má hrubé znalosti C a jemu podobných jazyků, ale o JavaScriptu moc potuchy nemá.

Podmíněné přiřazení

Jednou z typických ukázek může být validace vstupních parametrů a nastavení výchozí hodnoty, pokud přišlo null. Nejhorší případ:

function example(options) {
  if (options == null) {
    this.options = { color: 'red' };
  }
  else {
    this.options = options;
  }
}

O něco chytřejší programátor použije ternární podmíněný operátor:

function example(options) {
  this.options = (options != null) ? options : { color: 'red' };
}

Vývojář znalý JavaScriptu však odbourá zbytečné ruchy a vznikne logický zápis:

function example(options) {
  this.options = options || { color: 'red' };
}

Pojďme teď dál k mému oblíbenému C#. Předchozí příklad tam nejde řešit operátorem || protože C# je silně typový a null != false. Ovšem C# 2.0 přinesl s Nullable<T> hodnotovými typy i operátor ??. Ten slouží jako zkrácená varianta ternárního podmíněného operátoru. Díky němu můžeme následující kód:

public void Example(Options options) {
  _options = (options != null) ? options : new Options { Color = Color.Red };
}

Pěkně odšumět:

public void Example(Options options) {
  _options = options ?? new Options { Color = Color.Red };
}

Pokud pracujete s Nullable hodnotovými typy, zkraťte i následující zá­pis:

public void Example(int? pageIndex) {
  int page = 1 + (pageIndex.HasValue ? pageIndex.Value : 0);
}

Na:

public void Example(int? pageIndex) {
  int page = 1 + (pageIndex ?? 0);
}

Generické parametry

Dalším, velice častým šumem, je explicitní uvádění generických parametrů, tam kde to není potřeba. Za příklad si vezměme třeba generickou metodu Assert.Equal<T>(T expected, T actual) z frameworku xUnit.net. Můžeme ji volat takto:

Assert.Equal<string>("test", null);

Jenže kompilátor není žádný hlupák a dokáže správně dosadit typ generického parametru podle typu předávaného parametru. Proto je možné napsat, mnohem přehlednější zápis:

Assert.Equal("test", null);

FxCop vám přímo radí: Pište generické metody tak, aby se typ generického parametru dal implicitně odvodit.

Zbytečné volání ToString

Často se také můžete setkat s voláním metody ToString tam, kde to není pořeba. Příklad:

double rating = 3.8;
string output = "Your rating is: " + rating.ToString();

Tohle lze napsat bez šumu:

double rating = 3.8;
string output = "Your rating is: " + rating;

Teď si jistě, říkáte, že jsem asi blbec, jak můžu po silně typovém C# chtít, aby sečetl string a double. Nuže můžu. Krom toho, že je C# silně typový, podporuje i přetěžování operátorů! A designéři třídy String přidali přetížení operátoru + na kterýkoliv Object, na kterém zavolají metodu ToString za vás. :)

Zbytečné explicitní přetypování

Občas se bohužel můžeme v kódu potkat s věcmi, nad kterými zůstává rozum stát. Někdo například už pochopil dědičnost. Pochopil, že třída List<T> implementuje rozhraní IList<T> a to dědí z ICollection<T>, které je potomkem obecného rozhranní IEnumerable<T>. Jenže pak přijde na praktické použití a je zle:

public class UserDetailViewData {
  public IEnumerable<User> Friends { get; set; }
}

public class UsersController : Controller {
  public ActionResult Detail(userName) {
    IList<User> friends = GetFriendsOfUser(userName);
    return View(new UserDetailViewData {
      Friends = (IEnumerable<User>)friends
    });
  }
}

Takhle vytržené z kontextu to nemusí vypadat až tak zle, ale originál mě stál málem ukroucení hlavy.

Roztahaná inicializace

Novinkou C# 3.0 jsou inicializátory. Zvažte následující kód:

var user = new User();
user.Name = "Josef Novák";
user.Gender = Gender.Male;

Přepsat na:

var user = new User {
  Name = "Josef Novák",
  Gender = Gender.Male,
};

Vyhnete se tím, zbytečnému opakování user..

Závorky navíc

Častý šumem v kódy bývají také závorky, které nemají žádný význam, zejména u inicilizátorů s bezparametrickým konstrukotrem new User() { Name = "Josef Novák" } a u anonymních bezparametrických delegátů delegate() { return true; }. Tyto závorky klidně smažte a u delegátů zvažte kompresi na lambda výraz: ()=> true.

Na druhou stranu existuje spousta případů, kdy vhodně vložené závorky zvyšují čitelnost.

Závěr

Když říkám, že je lepší psát némě kódu, rozhodně tím nemám na mysli kriptické zápisy alá regexy. To, co ušetříte na zbytečném kódu, klidně investujte do popisných názvů proměnných a metod. Důležitá je čitelnost. Se spoustou výše zmiňovaných úprav vám mohou pomoci nástroje, např. CodeRush Express, který je zcela zdarma.

Rozhodně jsem nepokryl všechny možnosti, kde psát zbytečný kód navíc. Jistě taky některé znáte, podělte se o ně v komentářích. :)

Autor: Aleš Roubíček | 16x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Vracet List je špatné

08.51 - 1. srpna 2009 | ASP.NET 2.0

Přinejmenším na veřejnosti. Mnohdy se v různých APIs můžete setkat s tím, že různé objekty veřejně vystavují metody/vlasnosti, které mají návratový typ List<T>, v lepším případě IList<T>. Krásná ukázka nerozvážného návrhu.

Proč je špatné vracet List<T>

Když vracíte List<T>, znamená to, že říkáte všechno o vnitřní implementaci a zavíráte si vrátka pro její možnou změnu. Generický List sice neni nerozšiřitelný (sealed), ale ani nebyl při návrhu moc k rozšiřování zamýšlen. Navíc tim porušujute jeden ze základních principů OOP – a to zapouzdření. Dále tim umožňujete konzumentovi vaši kolekci přímo měnit.

Ukázka naivního/lenošného přístupu

Pro ukázku mějme třídu reprezentující uživatele User, kterému můžeme přiřadit uživatelské role Role. Naivní přístup:

public class User {
  public List<Role> Roles { get; private set; }
}

public class UserConsumerCode {
  public void SomeMethod {
    var user = new User();
    user.Roles.Add(Role.Admin);

    // ...

    user.Roles.Remove(Role.Reviewer);

    // ...

    var isAdmin = user.Roles.Contains(Role.Admin);
  }
}

Jak vidíte, klient má nad kolekcí rolí uživatele plnou moc. Může si přidávat, odebírat, přetřizovat jak se mu zlíbí. A my nemáme nejmenší šanci tyto celkem důležité operace jakkoli ovlivnit, nebo u nich vědět. Kdybychom změnili typ kolekce Roles na IList<Role>, tak sice můžeme změnit vnitřní implementaci a místo List<T> použít třeba LinkedList<T>, ale stále zveřejňujeme příliš mnoho. Jedinou výhodou tohoto řešení je, že jsme nemuseli napsat moc kódu a máme hodně funkcionality.

Jak zlepšit náš kód?

Rovnou přeskočím použití ICollection<T>, a přejdeme k tomu, jak by to mělo vypadat. Zachováme si zapouzdření, uzavřeme se vůči nechtěné manipulaci z vnějšku a otevřeme se snadným vnitřním změnám a rozšiřitelnosti (Open/closed principle).

public class User {
  private readonly ICollection<Role> _roles;

  public User() {
    _roles = new List<Role>();
  }

  public IEnumerable<Role> Roles {
    get { return _roles.ToArray(); }
  }

  public void AddRole(Role role) {
    _roles.Add(role);
  }

  public void RemoveRole(Role role) {
    _role.Remove(role);
  }

  public bool IsInRole(Role role) {
    return _roles.Contains(role);
  }
}

Pěkně jsme obohatili a zpřehlednili naše API (zápis user.IsInRole(Role.Admin) říká mnohem více, než user.Roles.Contains(Role.Admin), navíc neporušuje Demeterův zákon), skryli jsme implementační detaily a zároveň nechali otevřená vrátka pro snadnou výměnu vnitřní implementace… Dalším krokem by měly být kontrakty manipulačních metod, aby byly bezpečné. Dále pak můžeme přidat vyvolávání událostí, abychom o manipulaci věděli. Míst pro rozšíření je tu spousta.

Hloupá otázka na závěr

„Ale, jak teď zjistim, kolik rolí má uživatel přiřazených, když IEnumerable nemá vlastnost Count ani Length?“ using System.Linq; ;)

Autor: Aleš Roubíček | 14x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Tip na přehlednější šablony

09.25 - 12. července 2009 | ASP.NET 2.0

Zastávám názor, že šablona by měla být co nejvíce přehledná a obsahovat co nejméně programového kódu. Pojďme se podívat na některé kousky zasmrádlého kódu a jak se s nimi vypořádat.

Html.ActionLink je pro mne jedním z prvních míst, kde zvednout ukazováček a říct: „takhle ne.“ Již ve spotu o routingu jsem psal, že procházení routovací tabulky při hledání akce je výpočetně náročné a pokud vaše tabulka obsahuje mnoho řádků, může být zdrojem nepříjemného zdržení. Mějme například helper odkaz na detail zboží v jehož URL obsahuje informace o kategorii zboží, jeho id a název:

<%= Html.ActionLink(Model.Name, "Detail", "Goods", new { id = Model.Id, name = Model.UrlName, category = Model.Category.UrlName }) %>

Tohle rozhodně není přehledné, ani výkonné. Začněme tedy refaktorovat. Nelíbí se mi, že tag odkazu je generovaný a že pořádně netuším, jestli třeba nejsou parametry ve špatném pořadí. A co potom takový kodér? Co když bude chtít přidat nějakou třídu, aby mohl tento konkrétní odkaz lépe nastylovat nebo mu dát nějaký sémantický význam? Přejděme na Url.Action!

<a href="<%= Url.Action("Detail", "Goods", new { id = Model.Id, name = Model.UrlName, category = Model.Category.UrlName }) %>"><%= Html.Encode(Model.Name) %></a>

No moc jsme si nepomohli. Sice teď máme větší kontrolu nad generovaným kódem, ale kód je o dost delší a pořád pěkně „smrdí.“ Zbavme se teď kouzelných řetězců, ty mohou být zdrojem špatně dohledatelných chyb!

<a href="<%= Url.RouteUrl(RouteTo.CommodityDetail, new { id = Model.Id, name = Model.UrlName, category = Model.Category.UrlName }) %>"><%= Html.Encode(Model.Name) %></a>

Zbavili jsme se možnosti udělat chybu v názvu akce nebo řadiče a navíc teď probíhá vyhledávání routy v tabulce podle klíče, tudíž mnohem efektivněji. Ale pořád mi tu něco smrdí. Ano je to ta anonymní třída, i tady je velká šance na vnesení chyby. Ačkoli to tak na první pohled nevypadá, názvy vlastností této třídy nejsou nic jiného než kouzelné řetězce, jen bez uvozovek. Na pozadí se totiž překládají jako klíče ve slovníku. Lék na to je jednoduchý. Vytvoříme si vlastní helper!

<a href="<%= Url.CommodityDetail(Model) %>"><%= Html.Encode(Model.Name) %></a>

Zápis se rázem zpřehlednil, snížila se možnost zanesení chyby a množství opakovaného kódu.

Autor: Aleš Roubíček | 8x komentováno | Delicious | FriendFeed | Facebook | Linkuj!

Service Locator

11.44 - 30. května 2009 | ASP.NET 2.0

Znáte vzor Service Locator? Pravděpodobně jste s ním setkali nespočetněkrát aniž byste o tom věděli.

Update

Service locator je code smell. Následující řádky jsou zajímavé jako programátorské cvičení, nedoporučuji ho však používat v praxi. Třída by měla mít co nejméně závislostí a pokud chceme zredukovat počet parametrů konstruktoru, není následující řešení řešením vhodným! Měli bychom přemýšlet o změně návrhu.


Service Locator je návrhový vzor, který je součástí techniky dependency injection / inversion of control. Jeho úkolem je dodat hotové instance služeb na vyžádání. Můžeme si ho představit nějak tak:

public interface IServiceLocator {
  TService GetService<TService>();
  IEnumerable<TService> GetServices<TService>();
}

Jeho úkolem je poskytnout službu daného typu nebo všechny služby, které splňují danný kontrakt. K čemu je to dobré? Dobré je to především pro konfigurovatelné a rozšiřitelné aplikace. Řekněme, že budu chtít do svého redakčního systému přidat možnost oznámení na e-mail, když někdo zadá komentář. První řešení bude nejspíš to přímočaré:

public class CommentsController : Controller {
  public ActionResult AddComment(Comment comment) {
    // validace a persistence komentáře

    var notificator = new EmailNotificator();
    notificator.send(new CommentNotification(comment));

    return Json(comment);
  }
}

Proč nepřidat možnost odesílání SMSek? Tak jo:

public class CommentsController : Controller {
  public ActionResult AddComment(Comment comment) {
    // validace a persistence komentáře

    var emailNotificator = new EmailNotificator();
    emailNotificator.send(new CommentNotification(comment));

    var smsNotificator = new SmsNotificator();
    smslNotificator.send(new CommentNotification(comment));

    return Json(comment);
  }
}

Skvěle, teď ještě posílání na Twitter nebo na FriendFeed a akce nám krásně roste… :) Takže se oprostíme od toho, že dopředu víme, kudy všudy se chceme nechat informovat o nových komentářích a využijeme Service Locator:

public class CommentsController : Controller {

  private readonly IServiceLocator _serviceLocator;

  public CommentsController(IServiceLocator serviceLocator) {
    _serviceLocator = serviceLocator;
  }

  public ActionResult AddComment(Comment comment) {
    // validace a persistence komentáře

    var newCommentNotification = new CommentNotification(comment);
    var notificators = _serviceLocator.GetServices<INotificator>();
    notificators.Each(notificator => notificator.send(newCommentNotification));

    return Json(comment);
  }
}

Tím jsme také vyřešili problém vznikající při constructor injection a to rostoucí počet parametrů konstruktoru s rostoucími závislostmi. Protože service locator nám dokáže obstarat potřebné služby, nemusíme je injektovat zvlášť.

Ještě se vrátím k poznámce v úvodu, kde jsem psal, že jste se určitě s tímto vzorem setkali, aniž byste si toho byli vědomi. O co jde? O Singleton! :) Singleton je speciální případ service locatoru, který vrací službu jediného typu s řízeným životním stylem jedináčka.

To je všechno pěkné, ale jak tedy service locator ví, jak ty služby získat a jakej mají životní styl? Pokud používáte IoC kontejner, odpověd je snadná: service locator si udělám jako fasádu nad kontejnerem a tu do něj zaregistrujeme. Pokud žádný IoC kontejner nepoužíváte, tak si honem nějaký sežeňte! :)

Autor: Aleš Roubíček | 4x komentováno | Delicious | FriendFeed | Facebook | Linkuj!