čtvrtek 18. prosince 2014

ASP.NET MVC - sdílený helper

Tento článek by se možná hodil do série "Poznámky z code review" - ale vlastně je to jen další variace na téme DRY :-). Narazil jsem na to, že v jednom web projektu (ASP.NET MVC) se poměrně často generoval tooltip (v daném projektu ikonka, která při najetí myší ukáže boxík s dalšími údaji). Na každém místě, kde se i zobrazoval, byl použit téměř stejný kód, lišil se jen vlastním tělem tooltipu - tedy tím, jaké informace zobrazoval. Například na jednom místě vypadal kód na cshtml stránce takto:

<img width="14" height="14" onmouseout="$('#tooltip1').hide();" onmouseover="$('#tooltip1').css('display','inline');" style="margin-bottom:3px; inline-block;" alt="" title="" src="~/info.png">
<div id="tooltip1" style="display:none; position:relative;">
    <span class="arrow"></span>
    <div>
        <ul>
            <li><strong>@Model.Name</strong>@Model.Surname</li>
            <li><strong>@Model.Position</strong> - @Descriptions.Position</li>
        </ul>
        <p><i>@Descriptions.GeneralNote</i></p>
    </div>
</div>


Lepším přístupem v takovém případě se mi jeví založení sdílené helper metody - do adresáře App_Code se přidá soubor s názvem SharedHelpers.cshtml (název není až tak důležitý, ale jméno - tedy SharedHelpers - bude používáno pro volání helperu, tak je dobré s ohledem na tento fakt zvolit příhodné jméno. 

Souborů si můžete navíc založit více. Helper metoda, kterou v souboru definujeme, bude tak dostupná ze všech view (cshtml) v projektu a bude využívat templated razor delegate - více informací lze nalézt například na stránce http://haacked.com/archive/2011/02/27/templated-razor-delegates.aspx/ . Vlastní kód metody bude vypadat takto:

@helper Tooltip(params Func<dynamic, HelperResult>[] templates)
{
    <span class="hoverBox">
        <img src="~/icon.png" height="14" width="14" title="" alt="" />
        <div class="hoverTooltip">
            <span class="arrowShape"></span>
            <div>
                @foreach (var template in templates)
                {
                    @template(null)
                }
            </div>
        </div>
    </span>
}

Na ostatních - běžných - csthml stránkách bude použita jednoduše - stačí ji předat jenom razor kód těla konkrétního tooltipu a metoda jej "obalí" zbytkem kódu:

@SharedHelpers.Tooltip(
   @<span>
       <ul>
         <li><strong>@Model.Name</strong> – @Model.Surname</li>
         <li><strong>@Model.Position</strong> - @Descriptions.Position</li>
       </ul>
       <p><i>@Descriptions.GeneralNote</i></p>
  </span>)

Předávaný razor kód musí mít html značky uzavřené, tj. první a poslední značka musí být z jednoho páru. Protože to někdy nechceme (viz <span> v příkladě), zvolil jsem params definici pro sdílenou helper metodu a je jí tedy možné předat více samostatných razor kódů (tohle vše zní asi trochu kostrbatě, ale nevím jak to správně popsat česky):

@SharedHelpers.Tooltip(
   @<ul>
      <li><strong>@Model.Name</strong> – @Model.Surname</li>
      <li><strong>@Model.Position</strong> - @Descriptions.Position</li>
     </ul>,
   @<p><i>@Descriptions.GeneralNote</i></p>>)


Pokud vždy budete předávat jen jeden kus razor kódu, tak params pole vstupních parametrů sdílené helper metody není potřeba a můžete jej odstranit:

@helper Tooltip(Func<dynamic, HelperResult> template)
{
    <span class="hoverBox">
        <img src="~/icon.png" height="14" width="14" title="" alt="" />
        <div class="hoverTooltip">
            <span class="arrowShape"></span>
            <div>
               @template(null)
            </div>
        </div>
    </span>
}

 A málem bych zapomněl na css nahrazující javascript, to vypadá nějak takto:
...
       .hoverBox div.hoverTooltip {
             display: none;
             ...
       }

       .hoverBox:hover div.hoverTooltip {
             display: block;
       }
...

Programátor se pak nemusí při psaní razor kód zabývat správným kódem pro ikonku, vloží jen to, co chce mít zobrazené a o zbytek se postará sdílený helper.

středa 17. prosince 2014

Poznámky z code review 3

Další nedostatkem, se kterým se setkávám, je buď neznalost používaných technologiích a nebo nevyužívání jejich možností. To lze určitě pochopit, pokud kodér nepracuje s danou technologii určitý čas a nebo je jasné, že není expertem. Code review by ho  v takovém případě mělo upozornit na jiné možnosti a tedy tak vlastně přispívá k jeho odbornému růstu - a nebo může sloužit k vyvolání diskuze, který přístup je lepší a proč.

Například mi byl předán takovýto Razor kód:

@model OurModel

@{
   var mainClass = "main";
   var hasProperty = SomePropertyProvider.HasProperty("Name");

   if (Model.IsReadOnly)
   {
      mainClass += " readonly";
   }

   if(Model.Values.Any(v=> IsWrong(v))
   {
      mainClass += " error";
   }

   var alertId = Guid.NewGuid().ToString();
}
   
<div class="@mainClass">
    <table class="Header">
        <tr>
            <td class="modelType">
.....

Je jasné, že se kodér snažil sestavit  jména stylu - a dopustil se chyb - například v bloku kódu střídá několik věcí - jednak sestavuje jména stylů, mezitím ale provádí ale volání s touto akcí nijak nespjatých. Správně by měl blok vypadat asi takto:

@model OurModel

@{
   var alertId = Guid.NewGuid().ToString();
   var hasProperty = SomePropertyProvider.HasProperty("Name");

   var mainClass = "main";

   if (Model.IsReadOnly)
   {
      mainClass += " readonly";
   }

   if(Model.Values.Any(v=> IsWrong(v))
   {
      mainClass += " error";
   }
}
   
<div class="@mainClass">
    <table class="Header">
        <tr>
            <td class="modelType">
.....

To už vypadá lépe, ale stále to není ono. Kombinování řetězců výše uvedeným způsobem není příliš štastné a i když uvedený příklad by asi nevedl k velké paměťové zátěži, je lepší uvedené "sčítání" řetězců nepoužívat. Jinou možnost nabízí MVC helper:

@model OurModel

@helper GetMainClasses()
{
   @:message

   if(Model.IsReadOnly())
   {
      @:readonly
   }

   if(Model.Values.Any(v=> IsWrong(v))
   {
      @:error
   }
}
   
<div class="@GetMainClasses()">
    <table class="Header">
        <tr>
            <td class="modelType">
.....

a nebo při rozepsání na více helperů:

@model OurModel

@functions
{
    string IsReadonly()
    {
        if (Model.IsReadOnly)
            return "readonly";
        
        return string.Empty;
    }
    
    string IsInError()
    {
        if(Model.Values.Any(v=> IsWrong(v))
            return "error";

        return string.Empty;       
    }
}
   
<div class="message @IsReadonly() @IsInError()">
    <table class="Header">
        <tr>
            <td class="modelType">
.....


a nebo alternativně:

@model OurModel

@helper IsInError()
{
    @(Model.Values.Any(v=> IsWrong(v) ? "error" : string.Empty)
}

@helper IsReadonly()
{
    @(Model.IsReadOnly() ? "readonly" : string.Empty)
}
   
<div class="message @IsReadonly() @IsInError()">
    <table class="Header">
        <tr>
            <td class="modelType">
.....

popřípadě in-line kódem:

@model OurModel

<div class="message @(Model.IsReadOnly() ? "" : string.Empty) @(Model.Values.Any(v=> IsWrong(v) ? "" : string.Empty)">
    <table class="Header">
        <tr>
            <td class="modelType">
.....

a nebo pomocí funkce:

@model OurModel

@functions
{
   string AddClass(bool add, string name)
   {
      return add ? name : null;
   }
}

<div class="message @(AddClass(Model.IsReadOnly(), "readonly")) @(AddClass(Model.Values.Any(v=> IsWrong(v), "error"))">
    <table class="Header">
        <tr>
            <td class="modelType">
.....

Chápu, že na daný příklad a řešení mohou být různé názory, kód nebyl vysloveně špatný a nefunkční, ale jak jsem již napsal, lze code review pojmout i jako možnost vzdělávání se či prostor k diskuzi. Helpery z uvedených příkladů lze přesunout na konec stránky a mít tak  na začátku téměř čisté HTML. Čitelnost ukázek v VS je lepší, nějak mám problém vkládat ukázky, která mají odpovídající barevné zvýraznění.

pondělí 15. prosince 2014

Poznámky z code review 2

Při programování, ale vlastně asi při každé činnosti, je dobré se řídit pár zásadami. Jendou z nich je alespoň v případě programování něco, co se anglicky označuje jako DRY. Ne, neznamená to, že je sucho a je nutno zajít do hospody na jedno, ale jsou to první písmena Don't Repeat Yourself.

Je mnoho způsobů jak tento přístup využít a jedním z nich je i nepsat zbytečný kód. Při code review často ovšem narazím na to, že vývojář tento princip nedodrží a v kódu se objeví něco podobného následujícímu zjednodušenému příkladu: 

public bool CanCreateNew()
{
    if (this.allowedActions == null)
        return false;

    return this.allowedActions.Any(x => x.ActionCode == ActionCode.CreateNew);
}

public bool CanUpdate()
{
    if (this.allowedActions == null)
        return false;

    return this.allowedActions.Any(x => x.ActionCode == ActionCode.Update);
}

public bool CanCancel()
{
    if (this.allowedActions == null)
        return false;

    return this.allowedActions.Any(x => x.ActionCode == ActionCode.Cancel);
}

Když se podíváte na těla funkcí, tak se neustále opakuje ten samý postup a všechny metody lze vyjádřit pomocí jedné:

public bool CanDoAction(ActionCode actionCode)
{
    if (this.allowedActions == null)
        return false;

    return this.allowedActions.Any(x => x.ActionCode == actionCode);
}

Uvedenému přístupu je vhodné se vyvarovat - výjimkou snad mohou být pouze případy, kdy jste placeni od řádku kódu :-). Je dobré si uvědomit, že čím méně kódu napíšete, tím méně v něm může být chyb a kód se bude snadněji udržovat.

sobota 13. prosince 2014

Poznámky z code review 1

Občas dělám i code review hotového kódu a některé věci se neustále opakují či se vyskytují v kódu poměrně často a tak mne napadlo, že by nebylo špatné na ně upozornit a nabídnout k zamyšlení či diskuzi. První příspěvek bude věnován kódu, který provádí rozhodnutí dle výčtové hodnoty, tedy enumu.

Například vznikne takovýto výčtový typ:

public enum Option
{
    One = 1,
    Two = 2
}

Výše uvedené hodnoty pak často vedou k programátora k rozhodování se pomocí následujícího kódu, kdy předpokládá, že buď má proměnná jednu určitou hodnotu a pokud ne, tak musí mít druhou.

if (userOption == Option.One)
    Console.WriteLine("One");
else
    Console.WriteLine("Two");

Což ovšem platí jen do okamžiku, než někdo přidá další hodnoty, například doplní definici enum takto:

public enum Option
{
    Unknown = 0,
    One = 1,
    Two = 2,
    Three = 3
}

Podobné chyby se ale my programátoři často dopouštíme, i pokud má výčtový typ více hodnot než dvě a my se z nějakého důvodu domníváme, že stejně může nabývat jen dvou námi vybraných hodnot. Takto napsaný kód není příliš odolný proti změnám ve výčtovém typu a ve výsledku se pak program často chová "podivně".

Jak to tedy napsat správně? Podle mne takto:

switch (userOption)
{
    case Option.One: Console.WriteLine("One"); break;
    case Option.Two: Console.WriteLine("Two"); break;
    default: throw new NotImplementedException();
}

Pokud se objeví hodnota, s kterou kód nepočítal, vyhodí se výjimka. A nebo se může provést úprava hodnoty  a zalogování chyby, aby šlo později daný kód upravit, tedy například napsat kód takto:

switch (userOption)
{
    case Option.One: Console.WriteLine("One"); break;
    case Option.Two: Console.WriteLine("Two"); break;

    default: 
        Console.WriteLine("Not implemented value {0}, processing as {1}"
                             userOption, 
                             Option.One); 
        goto case Option.One;
}

Upozornění: kód je napsán jen pro účely tohoto článku, má jen ilustrovat problém. K podobným chybám může dojít i při nepoužití výčtu, jen se to na něm dobře ilustruje.

středa 10. prosince 2014

Feraty u jezera Garda 3 - Cima Rocca

Výprava na horu Rocca byla  krásným celodenním výletem - v této oblasti jsou hned čtyři feraty:
  • F.Susati,
  • M.Foletti
  • Sent.dei Camminamenti
  • Sent.delle Lasta

úterý 9. prosince 2014

Feraty u jezera Garda 2 - jezero Idro a Sasse

Další ferátkou byla cesta kolem horského jezera Idro. Má jméno Sasse a více informací lze zde. To je necelou hodinku jízdy od severního konce Gardského jezera  a leží i výše - a je tam tedy i o poznání chladněji. A zatímco u Gardy to koncem října ještě žije, tady sezona už skončila. Velké parkoviště bylo zcela  prázdné a na celé feratě jsme potkali jen čtyři další lidi. A panovalo zde ticho.

neděle 7. prosince 2014

Vybaveni na cesty autem - chlazení

Převoz potravin autem a v chladu byla vždy výzva. Na kratší vzdálenosti stačí polystyrénová krabice, zmražené potraviny a chladící vložky - tenhle systém používáme už roky a je to spolehlivý způsob přepravy většího množství potravin na kratší vzdálenosti. Ale pokud se jede někam na více dnů a není možné využívat lednice, nelze se spolehnout na  box, respektive ten není moc použitelný.