čtvrtek 22. prosince 2011

Desatero uživatele Twitteru

Twitter je tu už více než pět let. Je na čase, aby někdo stanovil pravidla pro chování uživatelů :-)

  1. Nebudeš využívat jiné sociální sítě. Drž se Twitteru a budeš svobodný. Odprosti se od užívání Facebooku a Google+, které Tě svádí z cesty a narušují Tvé schopnosti stručného vyjadřování.

  2. Nebudeš posílat dotazy na nové tweety nadarmo. Nesmíš mě neustálým ručním dotazováním donucovat, abych Ti vracel nové tweety každých několik vteřin. Dost nám to zatěžuje systém. Nechej to na Tvém klientovi, který má interval občerstvování určitě nastavený rozumně.

  3. Budeš zachovávat každý den několik minut odpočinku. Během této doby se na Twitter ani nepodíváš. Naužírej se strachem, že zmeškáš nějaký zajímavý tweet, můžeš se kdykoliv podívat do historie.

  4. Budeš ctít zakladatele Twitteru i jeho mateřskou firmu Ordeo Inc. Budeš sledovat uživatele @Jack, @Biz, @Noah, @Crystal, @Jeremy, @Adam, @TonyStubblebine, @Ev, @Dom, @Rabble, @RayReadyRay, @Florian, @TimRoberts a @Blaine, protože právě oni byli na počátku vzniku našeho Twitteru.

  5. Nebudeš provádět Unfollow. Nebudeš přestávat sledovat uživatele, jenom proto, že je to Tvůj konkurent a protože nechceš, aby Tě předstihl v počtu followerů.

  6. Nesmilníš a nevložíš příspěvek do jiné mikroblogovací služby. Nemáš zapotřebí, aby ses karikaturami nezralých a příživnických mikroblogovacích služeb připravil o radost ze skutečného Twitteru. Drž se ho a naučíš se psát zajímavě a trefně a ocení Tě mnoho followerů.

  7. Neukradneš obsah cizího tweetu. Nesnaž se zalíbit tím, že zajímavý tweet označíš za svůj. Používej raději Retweet. Co získáš na oblíbenosti a počtu followerů, to ztratíš na klidné mysli.

  8. Nebudeš vystupovat pod falešnou identitou. Nesnaž se být zajímavý tím, že budeš vystupovat za známou osobnost. Twitter Tě odhalí a zablokuje Tvůj účet. Snižuješ důvěryhodnost celé sítě a sám sebe.

  9. Nebudeš žádostivě dychtit po followerech toho, kdo Tě sleduje. Nevnucuj se a nevyžaduj laciné retweety, jenom aby jsi získal rychlé followery. Svým chováním můžeš způsobit rozvrat a Unfollow dotyčného. Buď trpělivý a followeři si cestu k Tobě určitě najdou.

  10. Nebudeš závistivě projíždět seznamy followerů top uživatelů a chamtivě vyhlížet své nové followery. Neužírej se tím, že máš málo followerů. Někomu to jde líp a stačí mu pouze jeho jméno. Někdo to má těžší. Drž se své vize a kvalitním obsahem si své followery určitě získáš.

středa 21. prosince 2011

Vlastní úlohy pro MSBuild

Automatizujte. Váš čas je drahý. Nedělejte ručně činnosti, které za Vás mohou udělat stroje během několika okamžiků a s minimální chybovostí.

Součástí Visual Studia je buildovací systém MSBuild. MSBuild pracuje ve Visual Studiu na pozadí při kompilaci projektů do spustitelného kódu. MSBuild je také využíván při týmovém vývoji na speciálních buildovacích serverech. Ať už provádíte build produkční verze aplikace, build testovací verze, build kontinuální integrace nebo úplně jinou automatizovanou úlohu, může se stát, že vyvstane specifická potřeba, kterou standardní úlohy MSBuildu nezvládnou. Pak se přiblíží okamžik, kdy budete potřebovat napsat úlohu vlastní. Určitě se toho nebojte. Odměnou Vám bude zrychlení práce a uznání ostatních členů vývojového týmu ;-)

Vytvoření knihovny s úlohou

Vytvoříme projekt typu Class Library. V našem příkladu založíme projekt Firma.Nastroje.Build.Ulohy.

Do projektu přidáme reference na frameworkové assembly Microsoft.Build.Framework a Microsoft.Build.Utilities.v4.0 (pro .NET FW 4.0).

Založíme třídu úlohy. Třída musí implementovat rozhraní Microsoft.Build.Utilities.ITask. Je vhodné podědit abstraktní třídu Microsoft.Build.Utilities.Task. Překryjeme metodu Execute(), která je vyvolána MSBuildem během provádění. Provádění úlohy je konfigurovatelné přes nastavení veřejných vlastností třídy. Nezapomeňte projekt zkompilovat ;-)

using System;
 
namespace Firma.Nastroje.Build.Ulohy
{
    /// <summary>
    /// Implementace mojí úlohy sestavení.
    /// </summary>
    public class MojeUloha : Microsoft.Build.Utilities.Task
    {
        /// <summary>
        /// Povinný parametr úlohy.
        /// </summary>
        [Microsoft.Build.Framework.Required]
        public string PovinnyParametrUlohy { getset; }
 
        /// <summary>
        /// Nepovinný parametr úlohy.
        /// </summary>
        public string NepovinnyParametrUlohy { getset; }
 
        /// <summary>
        /// Provádění úlohy.
        /// </summary>
        /// <returns>true, pokud je provádění úspěšné.</returns>
        public override bool Execute()
        {
            throw new NotImplementedException();
        }
    }
}

Nasazení knihovny na buildovací počítač

Všechny rozšiřující knihovny, které by měl znát MSBuild, umístěte na buildovacím počítači do adresáře definovaného v proměnné $(MSBuildExtensionsPath). Obvykle se jedná o "c:\Program Files (x86)\MSBuild". Doporučuji knihovnu zařadit ještě do podadresáře podle firmy nebo projektu. Ukázková knihovna bude mít v našem příkladě absolutní cestu "c:\Program Files (x86)\MSBuild\Firma\Firma.Nastroje.Build.Ulohy.dll".

Použití v buildovacím projektu

Nejprve musíme zadeklarovat naši úlohu na začátku řídícího projektového souboru (.proj, .csproj) ...

<UsingTask 
   TaskName="MojeUloha" 
   AssemblyFile="$(MSBuildExtensionsPath)\Firma\Firma.Nastroje.Build.Ulohy.dll"/>

... a poté zadefinovat konkrétní použití buildovací úlohy:

<MojeUloha
   PovinnyParametr="HodnotaPovinnehoParametru"
   NepovinnyParametr="HodnotaNepovinnehoParametru"
   />

Poznámky

  • Pro účely ladění třídy Vaší úlohy si vytvořte jednotkové testy, viz. TDD.
  • Framework Vaší knihovny musí být na buildovacím počítači podporován. Úlohy v jednom buildovacím projektu mohou být napsány pro různé frameworky.
  • Úlohy mohou poskytovat i návratové argumenty.
  • Argumenty úloh mohou být kolekce hodnot, např. kolekce názvů souborů.
  • Alternativou k vlastním úlohám pro MSBuild je vytvoření spustitelné exe aplikace, které předáte potřebné parametry přes příkazovou řádku.

sobota 3. prosince 2011

Pravidla pro pojmenování objektů v C# - část 2

  1. Úvod, jazyk, názvy tříd
  2. Názvy vlastností (properties), polí (fields) a proměnných (variables)
  3. Názvy metod a argumentů
  4. Další pravidla, názvy balíčků, testovací třídy

V předchozím díle jsme se podívali na několik základních pravidel pro názvosloví tříd. Neméně důležité je logicky a významově pojmenovat její vlastnosti, pole a proměnné uvnitř metod.

Názvy vlastností, polí a proměnných

Chtělo by se říci, že názvy veřejných vlastností jsou důležitější než názvy neveřejných vlastností, polí a proměnných. Není to však úplně pravda. Veřejné rozhraní třídy je samozřejmě důležité pro klienty třídy. Správný název urychlí pochopení a ušetří přepínání mezi kódem a dokumentací. Neveřejné názvy jsou však významné pro znovupoužitelnost, čistotu kódu, pochopení významu a efektivní navigaci v kódu.

Na co bychom si tedy měli dát pozor?

  1. Pro názvy vlastností (properties) se používá PascalCase, např. RodneCislo.

  2. Pro názvy polí (fields) se používá camelCase, např. rodneCislo.

  3. Pro názvy konstantních polí se používá PascalCase, např.

    private const int PocetMesicu = 12;

  4. Pro názvy proměnných (variables) se používá camelCase, např. polozky.

  5. Pokud vlastnost a pole (a proměnná) odpovídají stejnému logickému významu, měli by si jejich názvy odpovídat. Např.

    private string rodneCislo;
    
    public string RodneCislo
    {
        get
        {
            return rodneCislo;
        }
    
        private set
        {
            rodneCislo = value;
        }
    }
    

  6. Pokud je vlastnost autoimplementovatelná, tzn. getter je jednoduché vrácení hodnoty pole a setter je jednoduché nastavení hodnoty pole, je vhodné použít zkrácený a přehlednější zápis:

    public string RodneCislo { get; private set; }
    
    Výhodou tohoto zápisu je mimo jiné i jednodušší refaktorizace názvu. Nemusíte refaktorovat zvlášť název vlastnosti a pole.

  7. Pravidla uváděná dále jsou společná pro názvy vlastností, polí i proměnných.

  8. Vyhněte se opakování názvu třídy v názvech jejích vlastností. Např. třída Objednavka by neměla mít vlastnost PopisObjednavky nebo ObjednavkaId. Použijte "holý" název Popis a Id.

  9. V případě odkazu na Id jiné třídy ("primární klíč"), bude název odkazované třídy předcházet názvu vlastnosti Id:

    public class Osoba
    {
        public int Id { get; set; }
    }
    
    public class Uzivatel
    {
        public int OsobaId { get; set; }
    
        public String Jmeno { get; set; }
    }
    
    V tomto případě by bylo zřejmě vhodnější, aby třída Uzivatel nabízela přístup k objektu typu Osoba
    public Osoba Osoba
    

  10. Použijte název třídy v názvech vlastností jiných tříd, které na ni odkazují nebo vlastnost odpovídá vlastnosti odkazované třídy. Všimněte si vlastnosti Kod u třídy Mena a vlastnosti KodMeny u třídy Objednavka. Obě mají v systému stejný význam.

    public class Mena 
    {
        public string Kod { get; set; }
        public string Nazev { get; set; }
    }
    
    public class Objednavka 
    {
        public string KodMeny { get; set; }
        public decimal Castka { get; set; }
    }
    

  11. Pokud je to vhodné, použijte upřesňující významovou příponu. Např. CastkaVMeneDokladu, DobaTrvaniVMilisekundach, DenVTydnu.

  12. Pro booleovské vlastnosti použijte vhodnou předponu, např. JePlatna, MaNarokNaOdmenu.

  13. U zkratek ponechejte pouze první písmeno velké, např. Html, Id, Ico, KategorieDph.

  14. Nepoužívejte v názvech datové typy. Místo PolozkaObjednavkyList použijte PolozkyObjednavky, místo VystaveniDate použijte DatumVystaveni.

  15. Pokud používáte víceslovné názvy, měly by být přiměřeně dlouhé a srozumitelné. Snažte se vyjádřit stručně. Pokud je to však nezbytné, nestyďte se použít delší název.

  16. Stejně jako u názvů tříd je vhodné používat názvy z domény problému. Vycházet byste měli z doménového slovníku, který navrhne doménový analytik.

  17. Vyhněte se používání různých synonym pro logicky stejnou vlastnost různých tříd.

Používáte pravidlo, které zde není uvedeno? Máte připomínku k některému doporučení? Neváhejte vložit komentář.

čtvrtek 24. listopadu 2011

Rhino Mocks & spol. aneb imitujte rozhraní

Frameworky plné falše, náhražek, imitací a podvrhů ...

... a proto je máme tak rádi a proto je ke svému vývojářskému životu potřebujeme :-)

Nebojte se, jsme stále ještě ve světě férového programování. Tento příspěvek je krátkým motivačním úvodem do světa mock, fake, stub a dalších typů objektů, bez kterých se neobejde žádný vývojář, který to myslí vážně s psaním jednotkových (unit) a integračních testů.

Mock objekty a k čemu jsou dobré

Mock objekt vznikne jako fiktivní instance rozhraní nebo třídy. Této objektové imitaci pak můžete přiřadit chování, které očekáváte. Můžete také stnovit pravidla použití, které mohou být prověřeny na konci testu.

Předpokládám, že nejčastěji využíváte nebo budete využívat možnost "falešné" implementace rozhraní. V tomto případě oceníte techniku programování proti rozhraní (namísto programování proti implementaci). Je bežné, že třídy mají závislosti na jiných rozhraních. Pokud takovou třídu chcete pokrýt jednotkovými nebo integračními testy, musíte umět tyto závislosti vyřešit. Níže je uveden příklad který zastupuje tisícovku slov.

Většina mockovacích frameworků nabízí také validaci pořadí a počtu volání metod nebo přístupu k property mock objektu. Touto technikou můžete prověřit volání metod, které jsou pro potřeby testu důležité.

Rhino Mocks, Moq, NMock2, Isolator, TypeMock nebo Moles

Mockovacích frameworků pro .Net je několik. Liší se možnostmi toho, které třídy lze mockovat, intuitivností zápisu, možnostmi validačních pravidel, podporovanými verzemi .Net frameworků a dalšími aspekty. Přehledné srovnání je k dispozici na webu PHP vs .Net.

Nejrošířenějším volně dostupným frameworkem je Rhino Mocks. Zápis pravidel mock objektů je natolik intuitivní a variabilní, že pokryje většinu Vašich potřeb. Já osobně používám právě Rhino Mocks. Otázkou je, zda-li autor bude i nadále funkcionalitu rozvíjet. Pokud se nepletu, tak poslední update je někdy z roku 2009.

Ještě mám osobní zkušenost s NMock2. Pro mě je nevyhovující z toho důvodu, že názvy metod a vlastností se zapisují textově. Nejen že takový zápis zdržuje, nedá se využít IntelliSense, ale navíc není bezpečný pro refaktoring.

Mockování pro začátečníky

Mějme rozhraní zadefinovaná takto:

namespace Firma.Bll
{
    /// <summary>
    /// Rozhraní pro práci s kurzy.
    /// </summary>
    public interface IKurzyBll
    {
        /// <summary>
        /// Vrátí hodnotu kurzu měny.
        /// </summary>
        /// <param name="kodMeny">Kód měny.</param>
        /// <param name="datumPlatnosti">Datum platnosti.</param>
        /// <returns>Hodnota kurzu bez omezení přesnosti.</returns>
        decimal VratitHodnotuKurzuMeny(string kodMeny, DateTime datumPlatnosti);
    }

    /// <summary>
    /// Rozhraní pro práci s měnami.
    /// </summary>
    public interface IMenyBll
    {
        /// <summary>
        /// Převede částku v měně na částku domácí měny.
        /// </summary>
        /// <param name="kodMeny">Kód měny.</param>
        /// <param name="datumPlatnosti">Datum platnosti.</param>
        /// <param name="castkaVMene">Částka v měně.</param>
        /// <returns>Částka převedená na domácí měnu s přesností na dvě desetinná místa.</returns>
        decimal PrevestCastkuNaDomaciMenu(string kodMeny, DateTime datumPlatnosti, decimal castkaVMene);
    }
}

Třída implementující rozhraní IMenyBll vyžaduje při vzniku předání implementace IKurzyBll:

namespace Firma.Bll.Impl
{
    /// <summary>
    /// Implementace práce s měnami.
    /// </summary>
    public class MenyBll : IMenyBll
    {
        private IKurzyBll KurzyBll { get; set; }
          
        /// <summary>
        /// Pomocí constructor injection je vložena závislost na IKurzyBll.
        /// </summary>
        /// <param name="kurzyBll">Práce s kurzy.</param>
        public MenyBll(IKurzyBll kurzyBll)
        {
            KurzyBll = kurzyBll;
        }
 
        public decimal PrevestCastkuNaDomaciMenu(string kodMeny, DateTime datumPlatnosti, decimal castkaVMene)
        {
            decimal hodnotaKurzu = KurzyBll.VratitHodnotuKurzuMeny(kodMeny, datumPlatnosti);
            decimal castkaVDomaciMene = Math.Round(castkaVMene * hodnotaKurzu, 2, MidpointRounding.AwayFromZero);

            return castkaVDomaciMene;
        }
    }
}

A teď přichází chvilka slávy pro Rhino Mocks. Správnost implementace IKurzyBll nás v případě testování třídy MenyBll příliš nezajímá. Ale přesto ji potřebujeme. Bez ní implementaci MenyBll neotestujeme. V produkčním běhu aplikace může být IKurzyBll implementováno nad webovou službou, databázovou tabulkou nebo jiným způsobem. Pro účely testu by však bylo náročné takovou implementaci připravit. Jednoduchým řešením je vytvoření mock objektu, který naučíme vracet kurz podle potřeby našeho testu. Celá myšlenka by měla být zřejmá z kódu testovací metody:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace Firma.Bll.Impl.Test
{
    /// <summary>
    /// Unit testy pro třídu MenyBll.
    /// </summary>
    [TestClass]
    public class MenyBllTest
    {
        private const decimal HodnotaKurzuMenaEur = 23.530m;

        [TestMethod]
        public void PrevodCastkyEurTest()
        {
            // Repozitář pro správu mock objektů.
            MockRepository mocks = new MockRepository();

            // Vytvoření mock objektu pro rozhraní IKurzyBll.
            IKurzyBll kurzyBllMock = mocks.StrictMock<IKurzyBll>();

            string kodMeny = "EUR";
            DateTime datumPlatnosti = DateTime.Today;

            // Definice očekávaného chování.
            // Při volání VratitHodnotuKurzuMeny() s parametry "EUR" a dnešní datum 
            // vrací hodnotu 23.530m. Počet volání této metody není omezen.
            Expect.Call(kurzyBllMock.VratitHodnotuKurzuMeny(kodMeny, datumPlatnosti)).
                Return(HodnotaKurzuMenaEur).Repeat.Any();

            // Realizuje definici všech mock objektů.
            mocks.ReplayAll();

            // Vytvoření instance testované třídy s podvržením mock implementace IKurzyBll.
            MenyBll menyBll = new MenyBll(kurzyBllMock);

            decimal castkaEur = 10.50m;
            decimal ocekavanaCastkaCzk = 247.07m;
            decimal vracenaCastkaCzk = menyBll.PrevestCastkuNaDomaciMenu(kodMeny, datumPlatnosti, castkaEur);

            // Pokud je očekávaná částka různá od skutečně vrácené částky, 
            // dojde k výjimce a test skončí chybou.
            Assert.AreEqual(ocekavanaCastkaCzk, vracenaCastkaCzk);
        }
    }
}

Pokud patříte k vyznavačům TDD (vývoj řízeny testy), pak byste zřejmě postupovali tak, že ještě před vlastním implementováním metody PrevestCastkuNaDomaciMenu() napíšete tento a případné další testy. Tzn. zadefinujete očekávané cílové chování metody formou unit testů. Následná korektní implementace zajistí, že všechny testy začnou procházet.

Několik odkazů pro rychlejší rozjezd

čtvrtek 17. listopadu 2011

NConfig - řešení pro lokální konfigurace

Proč se nám může NConfig hodit

Přijdete ráno do práce, zvolíte ve Visual Studiu Get Latest Version nad celým projektem (řešením) a jdete si uvařit kafe. Pokračujete v implementaci nové funkcionality a spouštíte testy nad databází. Začaly se však objevovat chyby, které jsou hodně podezřelé. Vypadá to jako by v příslušné databázi nebyly struktury, které jste si vytvořili nově v rámci vývoje. Pátráte, jak je to možné a ztrácíte drahocené minuty. Když už začínáte být trochu zoufalí, uvědomíte si, že jste si aktualizovali lokální workspace. Podíváte se do konfiguračního souboru a zjistíte, že někdo změnil směrování na databázi! V historii změn zjistíte, že to byl Peter. V duchu si zanadáváte a zároveň si uvědomíte, že už jste párkrát udělali kolegům to samé. Omylem jste dali vrácení změn na server (Check-in) ve společném konfiguračním souboru (Web.config, App.config). Pokud však máte systémový přístup k řešení problémů, pokusíte se toto neustálé přepisování nějak elegantně vyřešit.

A možná by Vám mohl pomoci NConfig!

Jak získat NConfig

NConfig je .Net knihovna, jejíž použití ve Vašich projektech není nijak licenčně omezeno. Projekt je vyvíjen na serveru GitHub na adrese https://github.com/Yegoroff/NConfig.

Já jsem postupoval tak, že jsem si přes odkaz Download stáhnul celý adresář jako zip soubor. Následně jsem jej rozbalil, otevřel solution NConfig.sln a sestavil z projektu NConfig výslednou dll knihovnu ve verzi pro framework 4.0 (k dispozici je i projekt pro framework 3.5). Dále jsem již pracoval pouze s dll knihovnou.

Co NConfig umí

NConfig umí slučovat konfigurace z více konfigračních souborů. Umí také za běhu vybrat konfigurační soubor podle názvu aktuálního počítače. Tato funkcionalita je řešením pro výše uvedený motivační případ.

// Sloučí nastavení defaultního konfiguračního souboru a 
// Configs\Custom.config, resp. Configs\{NazevPocitace}.Custom.config
NConfigurator.UsingFiles(@"Configs\Custom.config").SetAsSystemDefault();

Autor tvrdí, že NConfig lze využít v aplikacích typu ASP.Net, ASP.Net MVC, WinServies, WinForms, WPF a konzolová aplikace.

Ukázka použití

Požadujeme, aby si vývojáři Peter a Steve mohli nastavit navzájem nezávislé lokální konfigurace pro připojení k databázím tak, aby nezasahovali do společného konfiguračního souboru.

Vytvoříme si jednoduchou konzolovou aplikaci a nareferencujeme NConfig.dll. Přidáme standardní App.config. Uživatelské konfigurační soubory umístíme do složky Configs. Ve složce vytvoříme defaultní Custom.config a konfigurační soubory pro Petera a Steva - PeterComputer.Custom.config a SteveComputer.Custom.config. Viz. obrázek. Nezapomeňte nastavit pro konfigurační soubory ve složce Configs vlastnost Copy To Output Directory na true.

Soubor App.config vypadá takto:

<?xml version="1.0"?>
<configuration>
 <appSettings>
  <add key="Database" value="AppDatabase"/>
  <add key="User" value="AppUser"/>
 </appSettings>
</configuration>

Soubor Custom.config vypadá takto:

<?xml version="1.0"?>
<configuration>
 <appSettings>
  <add key="User" value="CustomUser"/>
 </appSettings>
</configuration>

A například soubor PeterComputer.Custom.config vypadá takto:

<?xml version="1.0"?>
<configuration>
 <appSettings>
  <add key="Database" value="PeterComputerDatabase"/>
  <add key="User" value="PeterComputerUser"/>
 </appSettings>
</configuration>

Třída Program konzolové aplikace využije volání metody UsingFiles() třídy NConfig.NConfigurator, která sloučí původní App.config s Configs\Custom.config, případně s konfiguračními soubory podle spuštěného počítače:

class Program
{
    static void Main(string[] args)
    {
        // SwitchOnCustomConfig();

        Console.WriteLine(String.Format("User='{0}'", ConfigurationManager.AppSettings["User"]));
        Console.WriteLine(String.Format("Database='{0}'", ConfigurationManager.AppSettings["Database"]));
    }

    static void SwitchOnCustomConfig()
    {
        NConfigurator.UsingFiles(@"Configs\Custom.config").SetAsSystemDefault();
    }
}

Přehled scénářů:

Scénář Konfigurační soubor Hodnota klíče Database Hodnota klíče User
Není zapnutá podpora volitelných konfiguračních souborů - nevolá se metoda SwitchOnCustomConfig(). App.config AppDatabase AppUser
Je zapnutá podpora volitelných konfiguračních souborů - volá se metoda SwitchOnCustomConfig() a aplikace není spuštěna ani na počítači Petera ani Steva. Custom.config AppDatabase CustomUser
Je zapnutá podpora volitelných konfiguračních souborů - volá se metoda SwitchOnCustomConfig() a aplikace je spuštěna na počítači Petera. PeterComputer.Custom.config PeterComputerDatabase PeterComputerUser

neděle 13. listopadu 2011

Pravidla pro pojmenování objektů v C# - část 1

Problematika je pro svoji obsáhlost rozdělena do více příspěvků:

  1. Úvod, jazyk, názvy tříd
  2. Názvy vlastností (properties), polí (fields) a proměnných (variables)
  3. Názvy metod a argumentů
  4. Další pravidla, názvy balíčků, testovací třídy

Úvod

U programového kódu se předpokládá, že splňuje syntaktickou a sémantickou správnost. Tyto dva požadavky jsou nutné pro vlastní fungování výsledné aplikace. Aby jste kód mohli efektivně udržovat a rozvíjet, je neméně důležitá dostatečná čitelnost kódu a správné názvosloví. Možná se Vám již někdy stalo, že jste se vrátili k Vašemu staršímu kódu a dlouze jste se snažili vyčíst, co jste takovým zápisem vlastně sledovali. Chvíli sami sebe přesvědčujete, že toto nemůže být Váš kód, takhle nepřehledně byste to přeci nikdy nenapsali. Nakouknete do historie v repository a zjistíte, že jste to byli opravdu Vy. Trochu se zastydíte a slíbíte si, že příště si na kódu dáte více záležet.

Pokud se dostanete ke kódu převzatému od nějakého "kouzelníka" (v některých firmách se těmto lidem nesprávně říká guru), může se situace ještě více zdramatizovat. Mnohdy se stává, že některé části kódu pro Vás zůstanou zapovězeny navždy a Vy se smíříte s tím, že kód sice něco dělá, ale netušíte jak. Zasáhnout do takového kódu vyžaduje dostatek osobní odvahy a pokud nemáte dostatečné pokrytí kódu testy, můžete úpravou nadělat nevědomky pěknou paseku.

Jak se naučíte psát čitelný a dobře spravovatelný kód? Pouze praxí, znalostí níže uvedených pravidel, učením se z vlastních i cizích chyb, skupinovým posuzováním kódu (code review). V tomto miniseriálu o názvosloví se Vám pokusím ukázat několik pravidel, které doporučuje odborná literatura, a které se mi osvědčily ve vlastní praxi.

Jazyk

Na začátku projektu se musíte rozhodnout jaký jazyk použijete. Nejedná se o jazyk programový, ale lingvistický. Svět programování mluví anglicky, ale přítomnost mateřské češtiny nemusí být na škodu. Většina z nás má lepší vyjadřovací schopnosti v češtině, angličtina je však mnohdy výstižnější a nedává nám příliš velký manévrovací (dezinformační) prostor. Záleží také na jazykovém složení Vašeho týmu. Pokud děláte lokální projekty s homogenním českým týmem (mohou být přimícháni i slovenští kolegové), můžete využít češtinu. V případě jazykově heterogenního týmu na výběr nemáte a budete zřejmě komunikovat a pojmenovávat programové objekty v angličtině. Pokud se Vám ovšem nepodaří husarský kousek, kdy naučíte třeba němce spisovné češtině ;-)

Můžete se také rozhodnout, že některé nejnižší vrstvy programového systému budou čistě anglické a vyšší vrstvy naopak české. Například jedna podvrstva datové vrstvy komunikující s nějakým persistentním frameworkem bude anglická a vrstvy od aplikační logiky (business logic layer) výše (vrstva služeb, prezentační vrstva) budou české. Především ve vrstvě aplikační logiky můžete s výhodou využít češtiny a její (pro české vývojáře) přirozené srozumitelnosti. Samozřejmě za dodržení určitých pravidel a jednotnosti. Toto kombinování jazyků však nedoporučuji.

Určitě se však vyhněte tomu, že v rámci jedné třídy budete mít namíchány názvy z více jazyků. Výjimku tvoří metody a property, které dědí Vaše třída. Např. třída pojmenovaná jako Objednavka může přepisovat metodu GetHashCode() nebo Equals(). Podle stejné logiky nemíchejte jazyky v rámci jednoho jmenného prostoru nebo ještě lépe v rámci celé assembly.

Jazyk použitý pro názvosloví programových objektů by měl korespondovat s jazykem použitým pro objekty v persistentním úložišti (v databázi). Jednoduše řečeno by mělo platit, že tabulky a sloupce jsou pojmenované ve stejném jazyce jako odpovídající třídy a vlastnosti.

V dalším textu budu používat názvosloví v češtině a budu předpokládat, že většina pravidel se dá aplikovat i na angličtinu.

Názvy tříd

  1. Používejte PascalCase notaci. V C# se nepíše polozkaObjednavky, ale PolozkaObjednavky. Omlouvám se tomu, koho jsem takovou samozřejmostí urazil ;-)

  2. Používejte jednotné číslo. Instancí Vaší třídy je jeden objekt. Název třídy Objednavky proto nedává smysl. Správně je Objednavka.

  3. Název by měl být významový a jednoznačný. Pokud v systému zadefinujete třídy Objednavka, DataObjednavky a ObjednavkaInfo zaděláváte si na problémy. Na první pohled není zřejmé, v čem se třídy liší. Pokud všechny reprezentují objektovou entitu odpovídající objednávce v reálném světě, jedná se o chybnou duplicitu v objektovém návrhu. Pokud tomu tak není a např. třída DataObjednavky umí nějakou obchodní logiku, např. vrací určité statistiky nad objednávkami, pak je jistě název takové třídy zvolen chybně.

  4. Používejte názvy z domény řešeného problému. Od doménového experta by návrhář systému (alias implementátor) měl dostat doménový slovník, který definuje pojmy a pravidla, které má aplikace pokrýt. Každá doména má svoji terminologii, která je standardizovaná a zúčastnění jí rozumí. Musíte těmto pojmům rozumět také a držet se dané terminologie v názvech tříd a vlastností.

  5. Používejte vhodné a standardizované přípony. Je běžné, že se v systému objevuje více tříd související s danou entitou. V takových případech je vhodné standardizovat určité přípony a zadefinovat jejich jednoznačný význam. Podívejme se na příklad objednávky a s ní souvisejících tříd:

    • Objednavka [datová vrstva] - třída reprezentující objednávku a její vlastnosti (mapovány na databázové sloupce tabulky s objednávkami).
    • ObjednavkaDao [datová vrstva] - třída odpovědná za CRUD (Create, Read, Update, Delete) operace, např. načtení objednávky podle jejího Id.
    • ObjednavkaBll [vrstva obchodní logiky] - třída zajišťující realizaci obchodních pravidel pro práci s objednávkami, např. vykrytí objednávky.
    • ObjednavkaDto [vrstva služeb] - zjednodušená třída (jejíž objekty jdou serializovat) pro přenos přes vrstvu služeb u vícevrstvých aplikací.
    • ObjednavkaSluzba [vrsta služeb] - třída nabízející logiku práce s objednávkami na úrovni komunikační vrstvy.
    • ObjednavkaObsluha [prezentační vrstva] - třída plnící funkci presenteru v MVP nebo controlleru v MVC návrhovém vzoru pro prezentační vrstvy.
    • ObjednavkaPohled [prezentační vrstva] - třída zajišťující vykreslení objednávky v závislosti na vybrané prezentační technologii.

  6. Nepoužívejte v názvech názvy objektových typů. Pokud třída řeší překlad českých slov na německá vnitřně pomocí hešovací tabulky, nepoužívejte název CeskoNemeckySlovnikHashTable, ale pouze CeskoNemeckySlovnik. Klienta třídy nezajímá její vnitřní implementace. Je pro něj důležitá pouze veřejná část třídy.

  7. Nezkracujte na úkor čitelnosti. Pokud je to vhodné použijte víceslovný název třídy, který přesněji vymezí význam třídy. V době pomůcek pro efektivitu psaní kódu typu IntelliSense se dlouhých názvů bát nemusíte.

    Pozor na situace, kdy se nedá význam třídy popsat jednoduše. V těchto případech se může jednat o blikající kontrolku chybného návrhu třídy, konkrétně o porušení pravidla jedné zodpovědnosti. Např. třída AdresaABankovniUcetZamestnance zřejmě slučuje dvě věci (adresu a bankovní účet), které by měly být odděleny do dvou samostatných tříd.

  8. Nebojte se refaktorovat název. Nemusíte ideální název třídy vymyslet hned napoprvé. Ale pokud časem přijdete na výstižnější název, nebojte se jej refaktorovat. Kód musíte neustále vylepšovat.

  9. Nepoužívejte slovesa v názvech. Třída ZamestnanecMajiciDohodu má možná literárně hodnotný název, ale do názvu třídy něco takového nepatří. V tomto případě se zřejmě jedná navíc o chybný objektový návrh a chybnou dědičnost.

úterý 6. září 2011

Jak psát lepší kód s využitím StyleCopu

StyleCop - stylový policajt

StyleCop patří do rodiny nástrojů pro provádění statické analýzy kódu. StyleCop prověřuje kód a to bez nutnosti tento kód spouštět - proto statická analýza. StyleCop umí prověřovat (validovat) pravidla z několika oblastí - dokumentační pravidla, pravidla rozvržení, pravidla pro udržovatelnost, pravidla pojmenování, pravidla pro řazení, pravidla pro čitelnost, pravidla pro řádkování a mezery. Můžete si vytvořit i vlastní. Která pravidla nakonec budete chtít vynucovat záleží na Vámi zvoleném způsobu integrace tohoto nástroje do vývojového procesu.

StyleCop je zdarma ke stažení na CodePlexu, viz. stylecop.codeplex.com. Jako platforma použití se předpokládá Visual Studio 2008 nebo 2010 v edici minimálně Professional. V dokumentaci lze dohledat návod, jak docílit použití spolu s Visual Studiem 2005 nebo s Express edicí. StyleCop je v edicích Premium a Ultimate nativní součástí distribuce. Podobné integrace můžete dosáhnout dle návodu popsaného níže.

Ukázka použití StyleCopu

Nastal čas, aby nám StyleCop ukázal co umí. Fiktivní třída AntiStyleCopClass se tváří poměrně nenápadně. Je syntakticky správně a působí poměrně logicky strukturovaná. Posuďte sami:

using System;
using System.Text;
using System.Collections.Generic;

namespace StyleCopTest
{
    /// <summary>
    /// Třída, která se StyleCopu nebude líbit 
    ///</summary>
    class AntiStyleCopClass                                    /* line 10*/
    {
        private const string ODDELOVAC_TEXTU = " ";
        protected string oddelovacTextu = ODDELOVAC_TEXTU;

        /// <summary>
        /// Bezparametrický konstruktor.
        /// </summary>
        public AntiStyleCopClass() { }

        /// <summary>                                          /* line 20*/
        /// Konstruktor s definicí oddělovače textu.
        /// </summary>
        public AntiStyleCopClass(string oddelovacTextu)
        {
            this.oddelovacTextu=oddelovacTextu;
        }


        private void SoukromaMetoda(string argument1, string argument2,string argument3) {
                                                               /* line 30*/
            if ((argument1 == "") || argument1 == null) { return; }
            if (argument2 == String.Empty || argument2 == null )
            // Rychle opustit metodu
            {
                return;
            };
            if (String.IsNullOrEmpty(argument3)) { return; } else { Console.WriteLine(argument1 + argument2 + argument3); }
        }
        /// <summary>
        /// Veřejná metoda třídy.                              /* line 40*/
        /// </summary>
        /// <param name="argument1">Argument1.</param>
        /// <param name="parametr2">Argument2</param>
        public void VerejnaMetoda(string argument1, string argument2)
        {
            if (argument1 == null || argument2 == null) throw new ArgumentNullException("Takové argumenty Ti nesežeru.");

            // Vyvolání soukromé metody

            SoukromaMetoda(argument1,                          /* line 50*/
                oddelovacTextu, argument2
                );   

            //Console.WriteLine("Zpracování ok.");
        }

        /// <summary>
        /// Oddělovač textu.
        /// </summary>
                                                               /* line 60*/
        public string OddelovacTextu
        {
            get {return oddelovacTextu;}
            set
            {
                oddelovacTextu = value;
            }
        }

    }                                                          /* line 70*/

    /// <summary>
    /// Takový malý appendix.
    /// </summary>
    public class JednaMalaTrida
    {
    }
}                                                            

Oko zkušeného vývojáře však zbystří a začne klopýtat po méně či více závažných proviněních proti formátovacím a kódovacím standardům jazyka C# a dobrým programátorským mravům. Necháme-li úřadovat StyleCop, dostáváme v tomto případě 46 porušení validačních pravidel. Všimněte si, každé pravidlo má jednoznačný identifikační kód a vysvětlující text porušení.

Line 2: SA1210: Using directives must be sorted alphabetically by the namespaces.
Line 9: SA1004: The documentation header line must start with a single space.
Line 10: SA1629: The documentation text within the summary tag must end with a period.
Line 10: SA1400: The class must have an access modifier.
Line 10: SA1202: All internal classes must be placed after all public classes.
Line 12: SA1310: Field names must not contain underscores.
Line 12: SA1202: All private fields must be placed after all protected fields.
Line 13: SA1600: The field must have a documentation header.
Line 13: SA1401: Fields must be declared with private access. Use properties to expose fields.
Line 18: SA1502: The constructor must not be placed on a single line. The opening and closing curly brackets must each be placed on their own line.
Line 23: SA1611: The documentation header must contain param tags matching the element's parameter list.
Line 25: SA1003: The spacing around the symbol '=' is invalid.
Line 28: SA1507: The code must not contain multiple blank lines in a row.
Line 29: SA1500: If a statement spans multiple lines, the opening curly bracket must be placed on its own line.
Line 29: SA1505: An opening curly bracket must not be followed by a blank line.
Line 29: SA1001: Invalid spacing around the comma.
Line 31: SA1501: A statement containing curly brackets must not be placed on a single line. The opening and closing curly brackets must each be placed on their own line.
Line 31: SA1122: Use string.Empty rather than "".
Line 32: SA1009: Invalid spacing around the closing parenthesis.
Line 33: SA1515: A single-line comment must be preceded by a blank line or another single-line comment, or must be the first item in its scope. To ignore this error when commenting out a line of code, begin the comment with '////' rather than '//'.
Line 33: SA1108: A comment may not be placed within the bracketed statement.
Line 36: SA1106: The code contains an extra semicolon.
Line 37: SA1501: A statement containing curly brackets must not be placed on a single line. The opening and closing curly brackets must each be placed on their own line.
Line 37: SA1107: A line may only contain a single statement.
Line 39: SA1514: A documentation header must be preceded by a blank line or must be the first item in its scope.
Line 44: SA1202: All private methods must be placed after all public methods.
Line 44: SA1612: The param tags in the documentation header must match the element's parameter list.
Line 44: SA1629: The documentation text within the param tag must end with a period.
Line 44: SA1632: The documentation text within the param tag must be at least 10 characters in length. Documentation failing to meet this guideline most likely does not follow a proper grammatical structure required for documentation text.
Line 44: SA1516: Adjacent elements must be separated by a blank line.
Line 46: SA1503: The body of the if statement must be wrapped in opening and closing curly brackets.
Line 48: SA1512: A single-line comment must not be followed by a blank line. To ignore this error when commenting out a line of code, begin the comment with '////' rather than '//'.
Line 50: SA1117: All method parameters must be placed on the same line, or each parameter must be placed on a separate line.
Line 50: SA1116: If the method parameters are on separate lines, the first parameter must begin on the line beneath the name of the method.
Line 51: SA1115: The parameter must begin on the line after the previous parameter.
Line 52: SA1111: The closing parenthesis or bracket must be placed on the same line as the last parameter in the parameter list.
Line 52: SA1009: Invalid spacing around the closing parenthesis.
Line 54: SA1005: The comment must start with a single space. To ignore this error when commenting out a line of code, begin the comment with '////' rather than '//'.
Line 57: SA1506: A documentation header line must not be followed by a blank line.
Line 61: SA1201: All methods must be placed after all properties.
Line 63: SA1504: An accessor can only be placed on a single line if all of the accessors in the property are placed on a single line.
Line 63: SA1012: Invalid spacing around the opening curly bracket.
Line 63: SA1002: Invalid spacing around the semicolon.
Line 63: SA1013: Invalid spacing around the closing curly bracket.
Line 70: SA1508: A closing curly bracket must not be preceded by a blank line.
Line 75: SA1402: A C# document may only contain a single class at the root level unless all of the classes are partial and are of the same type.

Tento výsledek by nás neměl znechutit, ale spíše motivovat. Chceme přeci vytvářet lepší kód?! Soustředíme se, postupně si pozorně pročítáme každou chybu a sjednáváme nápravu. Ve výsledku dostáváme třídu LovelyStyleCopClass, na které nás StyleCop již nenachytá:

using System;
using System.Collections.Generic;
using System.Text;

namespace StyleCopTest
{
    /// <summary>
    /// Třída, která se StyleCopu musí líbit.
    /// </summary>
    public class LovelyStyleCopClass
    {
        private const string OddelovacTextuDefaultni = " ";
        private string oddelovacTextu = OddelovacTextuDefaultni;

        /// <summary>
        /// Bezparametrický konstruktor.
        /// </summary>
        public LovelyStyleCopClass() 
        {
        }

        /// <summary>
        /// Konstruktor s definicí oddělovače textu.
        /// </summary>
        /// <param name="oddelovacTextu">Oddělovač textu.</param>
        public LovelyStyleCopClass(string oddelovacTextu)
        {
            this.oddelovacTextu = oddelovacTextu;
        }

        /// <summary>
        /// Oddělovač textu.
        /// </summary>
        public string OddelovacTextu
        {
            get
            {
                return oddelovacTextu;
            }

            set
            {
                oddelovacTextu = value;
            }
        }

        /// <summary>
        /// Veřejná metoda třídy.
        /// </summary>
        /// <param name="argument1">Argument1.</param>
        /// <param name="argument2">Argument2.</param>
        public void VerejnaMetoda(string argument1, string argument2)
        {
            // Vyvolání soukromé metody
            SoukromaMetoda(argument1, oddelovacTextu, argument2);   
        }

        private void SoukromaMetoda(string argument1, string argument2, string argument3)
        {
            if (argument1 == String.Empty || argument1 == null)
            {
                return;
            }

            if (argument2 == String.Empty || argument2 == null)
            {
                // Rychle opustit metodu
                return;
            }

            if (String.IsNullOrEmpty(argument3))
            {
                return;
            }
            else
            {
                Console.WriteLine(argument1 + argument2 + argument3);
            }
        }
    }
}

Způsoby integrace StyleCopu do vývojového procesu

Způsob použití je odvislý od fáze, ve které chcete statickou analýzu kódu provádět:

  • Ruční spouštění. V tomto případě si musíte StyleCop nainstalovat lokálně. Po instalaci se StyleCop zaintegruje do vývojového prostředí Visual Studia a v Solution Exploreru Vám přibude nová kontextová volba StyleCop. Validaci můžete spustit nad jedním nebo více cs soubory, nad projektem nebo libovolným podstromem řešení. Problémový výsledek validace se projeví přidáním varování (warning) v okně s chybami kompilace (Error List). Kontextová nabídka na varováních, která přidal StyleCop obsahuje volbu Show Error Help. Ta zobrazí pro každé pravidlo pěkně vypracovanou dokumentaci, ve které se mimojiné dozvíte, proč je porušení pravidla problémové a jak jeho porušení vyřešit. Tento přístup je vhodný pro seznámení se se StyleCopem a v situaci, kdy integrujete StyleCop do vývoje projektu, který již běží.
  • Spouštění v rámci kompilace projektu. Tento přístup je logický, neboť kompilace projektu je okamžik, kdy Visual Studio (konkrétně MSBuild) vyhodnocuje nejen syntaktickou správnost, ale také další pravidla. Přidání StyleCopu znamená rozšířit .csproj konfigurační soubor o vyvolání targetu, který je součástí distribuce StyleCopu:
    ...
    <!-- Kompilace projektu - standardní řádek v každém kompilovatelném projektu -->
    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 
    <!-- Integrace StyleCopu - nově přidaný řádek -->  
    <Import Project="c:\Program Files\MSBuild\Microsoft\StyleCop\v4.4\Microsoft.StyleCop.Targets" /> 
    ...
    
    Modifikací tohoto přístupu je varianta, kdy umístíte všechny potřebné knihovny StyleCopu včetně .Targets souboru do repository. Pak budete v .csproj souborech odkazovat relativní cestou. Tímto způsobem zajistíte pro ostatní členy týmu bezinstalační integraci StyleCopu. Počítejte s tím, že přidání validace do kompilace projektu Vám nepatrně prodlouží dobu kompilace.
  • Spouštění v rámci denního buildu. Můžete nechat StyleCop prověřovat Váš kód během denního serverového buildu, který je určen pro kontinuální zajištění kvality kódu. V rámci tohoto buildu můžete provádět různé techniky statického i dynamického validování. V tomto případě je nutné přehledným způsobem zobrazit případná porušení například na vývojovém týmovém portálu. Tento způsob spouštění poskytuje důležité informace pro vedoucího vývojáře a pro tým zajišťující kvalitu vývoje.

Výše uvedené přístupy lze samozřejmě kombinovat a dle mého názoru je to i užitečné. Pro vývojáře je nejlepší, aby byl na porušení upozorněn co nejdříve. Tedy nejpozději při kompilaci projektu.

Bez pomocných nástrojů to půjde tězce

Pokud si stanovíte přísná pravidla na vytvářený kód, počítejte s tím, že režie na vytváření kódu se zvýší. Než si na nová pravidla zvyknete, bude to chvíli trvat. Brzy se však naučíte dodržovat konvence s takovou samozřejmostí, že StyleCop ani nebude mít mnoho práce. Navýšení pracnosti je však pouze relativní. Kód je součástí projektu dlouhé roky a tak je nutné odlišovat primární náklady související s prvotním vytvářením kódu a sekundární náklady spojené s jeho údržbou a pozdější modifikací. A právě v redukci sekundárních nákladů je StyleCop silný. Každá pozdější práce se správně napsaným kódem je efektivnější a začíná se Vám úročit dodržování pravidel StyleCopu.

Dodržování některých pravidel je bez pomocných nástrojů hodně pracné. Např. pravidlo SA1202: ElementsMustBeOrderedByAccess požaduje, aby pořadí členů třídy bylo setříděno podle přístupnosti (public, internal, protected internal, protected, private). Ruční hlídání tohoto pravidla by mohlo být pracné. Existuje však nástroj Regionerate, který Vám zautomatizuje správné setřídění členů.

Při dodržování mnoha dokumentačních pravidel Vám ulehčí práci doplněk GhostDoc.

Určitě se vyplatí používat další doplňky typu CodeRush. Např. jedno z pravidel SA1210: UsingDirectivesMustBeOrderedAlphabeticallyByNamespace požaduje, aby using direktivy byly setříděny podle abecedy. Ruční editace nepředstavitelná, s CodeRush je to hračka. Stačí podržet kurzor myši nad problémovou oblastí a máte navrženu úpravu. Pro CodeRush existuje doplněk, který nastaví chování CodeRush podle pravidel vynucovaných StyleCopem.

Pro editaci .csproj souborů používám doplněk PowerCommands. Ten umí také třídit usingy, viz. předchozí odstavec.

Která pravidla zvolit?

Parametrizaci pravidel má StyleCop uloženou v souboru Settings.StyleCop. Pro práci s konfiguračním souborem je dodáván vizuální editor. Vyberte si pravidla vhodná pro Váš projekt a uložte konfigurační soubor do rootu Vašeho adresářového stromu se zdrojovými kódy. StyleCop při spuštění prochází nadadresáře a hledá konfigurační soubor, kterým se následně řídí. Můžete některé podstromy s kódem nakonfigurovat přísněji, jiné benevolentněji.

Níže uvádím výpis pravidel, které používáme v našich projektech. Můžete se nechat inspirovat.

Dokumentační pravidla (Documentation Rules)
SA1600: ElementsMustBeDocumented
SA1601: PartialElementsMustBeDocumented
SA1602: EnumerationItemsMustBeDocumented
SA1603: DocumentationMustContainValidXml
SA1604: ElementDocumentationMustHaveSummary
SA1605: PartialElementDocumentationMustHaveSummary
SA1606: ElementDocumentationMustHaveSummaryText
SA1607: PartialElementDocumentationMustHaveSummaryText
SA1608: ElementDocumentationMustNotHaveDefaultSummary
SA1610: PropertyDocumentationMustHaveValueText
SA1611: ElementParametersMustBeDocumented
SA1612: ElementParameterDocumentationMustMatchElementParameters
SA1613: ElementParameterDocumentationMustDeclareParameterName
SA1614: ElementParameterDocumentationMustHaveText
SA1615: ElementReturnValueMustBeDocumented
SA1616: ElementReturnValueDocumentationMustHaveValue
SA1617: VoidReturnValueMustNotBeDocumented
SA1618: GenericTypeParametersMustBeDocumented
SA1619: GenericTypeParametersMustBeDocumentedPartialClass
SA1620: GenericTypeParameterDocumentationMustMatchTypeParameters
SA1621: GenericTypeParameterDocumentationMustDeclareParameterName
SA1622: GenericTypeParameterDocumentationMustHaveText
SA1624: PropertySummaryDocumentationMustOmitSetAccessorWithRestricedAccess
SA1625: ElementDocumentationMustNotBeCopiedAndPasted
SA1626: SingleLineCommentsMustNotUseDocumentationStyleSlashes
SA1627: DocumentationTextMustNotBeEmpty
SA1628: DocumentationTextMustBeginWithACapitalLetter
SA1629: DocumentationTextMustEndWithAPeriod
SA1631: DocumentationTextMustMeetCharacterPercentage
SA1632: DocumentationTextMustMeetMinimumCharacterLength
SA1636: FileHeaderMustContainFileName
SA1643: DestructorSummaryDocumentationMustBeginWithStandardText
SA1644: DocumentationHeadersMustNotContainBlankLines
SA1645: IncludedDocumentationFileDoesNotExist
SA1646: IncludedDocumentationXPathDoesNotExist
SA1647: IncludeNodeDoesNotContainValidFileAndPath

Pravidla rozvržení (Layout Rules)
SA1500: CurlyBracketsForMultiLineStatementsMustNotShareLine
SA1501: StatementMustNotBeOnSingleLine
SA1502: ElementMustNotBeOnSingleLine
SA1503: CurlyBracketsMustNotBeOmitted
SA1504: AllAccessorMustBeMultiLineOrSingleLine
SA1505: OpeningCurlyBracketsMustNotBeFollowedByBlankLine
SA1506: ElementDocumentationHeadersMustNotBeFollowedByBlankLine
SA1507: CodeMustNotContainMultipleBlankLinesInARow
SA1508: ClosingCurlyBracketsMustNotBePrecededByBlankLine
SA1509: OpeningCurlyBracketsMustNotBePrecedededByBlankLine
SA1510: ChainedStatementBlocksMustNotBePrecededByBlankLine
SA1511: WhileDoFooterMustNotBePrecededByBlankLine
SA1512: SingleLineCommentsMustNotBeFollowedByBlankLine
SA1513: ClosingCurlyBracketMustBeFollowedByBlankLine
SA1514: ElementDocumentationHeaderMustBePrecededByBlankLine
SA1515: SingleLineCommentMustBePrecededByBlankLine
SA1516: ElementsMustBeSeparatedByBlankLine

Pravidla pro udržovatelnost (Maintainability Rules)
SA1119: StatementMustNotUseUnnecessaryParenthesis
SA1400: AccessModifierMustBeDeclared
SA1401: FieldsMustBePrivate
SA1402: FileMayOnlyContainASingleClass
SA1403: FileMayOnlyContainASingleNamespace
SA1404: CodeAnalysisSuppressionMustHaveJustification
SA1405: DebugAssertMustProvideMessageText
SA1406: DebugFailMustProvideMessageText
SA1407: ArithmeticExpressionsMustDeclarePrecedence
SA1408: ConditionalExpressionsMustDeclarePrecendence
SA1409: RemoveUnnecessaryCode
SA1410: RemoveDelegateParenthesisWhenPossible

Pravidla názvosloví (Naming Rules)
SA1300: ElementMustBeginWithUpperCaseLetter
SA1301: ElementMustBeginWithLowerCaseLetter
SA1302: InterfaceNamesMustBeginWithI
SA1303: ConstFieldNamesMustBeginWithUpperCaseLetter
SA1304: NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter
SA1305: FieldNamesMustNotUseHungarianNotation
SA1306: FieldNamesMustBeginWithLowerCaseLetter
SA1307: AccessibleFieldsMustBeginWithUpperCaseLetter
SA1308: VariableNamesMustNotBePrefixed
SA1309: FieldNamesMustNotBeginWithUnderscore
SA1310: FieldNamesMustNotContainUnderscore

Pravidla pořadí (Ordering Rules)
SA1201: ElementsMustAppearInTheCorrectOrder
SA1202: ElementsMustBeOrderedByAccess
SA1203: ConstantsMustAppearBeforeFields
SA1204: StaticElementsMustAppearBeforeInstanceElements
SA1206: DeclarationKeywordsMustFollowOrder
SA1207: ProtectedMustComeBeforeInternal
SA1208: SystemUsingDirectivesMustBePlacedBeforeOtherUsingDirectives
SA1209: UsingAliasDirectivesMustBePlacedAfterOtherUsingDirectives
SA1210: UsingDirectivesMustBeOrderedAlphabeticallyByNamespace
SA1211: UsingAliasDirectivesMustBeOrderedAlphabeticallyByAliasName
SA1212: PropertyAccessorsMustFollowOrder
SA1213: EventAccessorsMustFollowOrder

Pravidla pro čitelnost (Readability Rules)
SA1100: DoNotPrefixCallsWithBaseUnlessLocalImplementationExists
SA1102: QueryClauseMustFollowPreviousClause
SA1103: QueryClausesMustBeOnSeparateLinesOrAllOnOneLine
SA1104: QueryClauseMustBeginOnNewLineWhenPreviousClauseSpansMultipleLines
SA1105: QueryClausesSpanningMultipleLinesMustBeginOnOwnLine
SA1106: CodeMustNotContainEmptyStatements
SA1107: CodeMustNotContainMultipleStatementsOnOneLine
SA1108: BlockStatementsMustNotContainEmbeddedComments
SA1109: BlockStatementsMustNotContainEmbeddedRegions
SA1110: OpeningParenthesisMustBeOnDeclarationLine
SA1111: ClosingParenthesisMustBeOnLineOfOpeningParenthesis
SA1112: ClosingParenthesisMustBeOnLineOfOpeningParenthesis
SA1113: CommaMustBeOnSameLineAsPreviousParameter
SA1114: ParameterListMustFollowDeclaration
SA1115: ParameterMustFollowComma
SA1116: SplitParametersMustStartOnLineAfterDeclaration
SA1117: ParametersMustBeOnSameLineOrSeparateLines
SA1118: ParameterMustNotSpanMultipleLines
SA1120: CommentsMustContainText
SA1122: UseStringEmptyForEmptyStrings
SA1123: DoNotPlaceRegionsWithinElements

Pravidla pro řádkování a mezery (Spacing Rules)
SA1000: KeywordsMustBeSpacedCorrectly
SA1001: CommasMustBeSpacedCorrectly
SA1002: SemicolonsMustBeSpacedCorrectly
SA1003: SymbolsMustBeSpacedCorrectly
SA1004: DocumentationLinesMustBeginWithSingleSpace
SA1005: SingleLineCommentsMustBeginWithSingeSpace
SA1006: PreprocessorKeywordsMustNotBePrecededBySpace
SA1007: OperatorKeywordMustBeFollowedBySpace
SA1008: OpeningParenthesisMustBeSpacedCorrectly
SA1009: ClosingParenthesisMustBeSpacedCorrectly
SA1010: OpeningSquareBracketsMustBeSpacedCorrectly
SA1011: ClosingSquareBracketsMustBeSpacedCorrectly
SA1012: OpeningCurlyBracketsMustBeSpacedCorrectly
SA1013: ClosingCurlyBracketsMustBeSpacedCorrectly
SA1014: OpeningGenericBracketsMustBeSpacedCorrectly
SA1015: ClosingGenericBracketsMustBeSpacedCorrectly
SA1016: OpeningAttributeBracketsMustBeSpacedCorrectly
SA1017: ClosingAttributeBracketsMustBeSpacedCorrectly
SA1018: NullableTypeSymbolsMustNotBePrecededBySpace
SA1019: MemberAccessSymbolsMustBeSpacedCorrectly
SA1020: IncrementDecrementSymbolsMustBeSpacedCorrectly
SA1021: NegativeSignsMustBeSpacedCorrectly
SA1022: PositiveSignsMustBeSpacedCorrectly
SA1023: DereferenceAndAccessOfSymbolsMustBeSpacedCorrectly
SA1024: ColonsMustBeSpacedCorrectly
SA1025: CodeMustNotContainMultipleWhitespaceInARow
SA1026: CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation
SA1027: TabsMustNotBeUsed

StyleCop nabízí podporu pro přidávání vlastních validačních pravidel. Vytvořit vlastní pravidlo není příliš složité. Příklad by se možná mohl objevit na tomto blogu jako článek v budoucnu. Pokud bude poptávka :)

Závěrem

Pokud Vás článek zaujal, můžete připsat komentář. Můžete se také zeptat na zkušenosti z praxe i na další aspekty použití StyleCopu.

Používáte jinou statickou analýzu? Jakou? Jak jste s ní spokojeni?

pondělí 27. června 2011

Zbavte se nemrtvého kódu

Všichni to známe. Kód, který se dlouho vyhříval na výsluní programátorovy přízně se stal jednoho dne nechtěným. Byl nahrazen pěknější a odladěnější implementací. Programátorovi však bylo líto přímo odstranit výsledek svého dřívějšího snažení a proto kód pouze zakomentoval. Co kdyby se někdy v budoucnu konalo zmrtvýchvstání některé části kódu? Tím však pouze oddálil jeho nezvratný osud a připustil, aby se mrtvý kód zjevoval jako přízrak stojící v temných zákoutích definicí tříd a strašil náhodné zvědavce prohlížející tyto třídy.

Mrtvý (nebo spíš nemrtvý) kód bývá součástí každého projektu. I u nás se objevil a to ve větším než malém množství. V okamžiku, kdy jsme zaktivovali statickou analýzu kódu, nám začaly nabíhat varování o nesprávně zakomentovaném kódu. Byl to pro nás impuls, abychom se nad přítomností takového kódu zamysleli. A výsledný verdikt je jednoznačný - přítomnost nemrtvého kódu je v produkční vývojové větvi nepřípustná.

Důvody, proč je nemrtvý kód škodlivý:

  • Snižuje čitelnost kódu. Ať už se jedná o zakomentovaný samostatný řádek nebo o celou metodu třídy, vždy narušuje čistotu kódu a ztěžuje Vám orientaci v kódu. Známá poučka říká, že poměr času strávený čtením a orientací v kódu a vlastního psaní kódu je 5 : 1. Proto každý nadbytečný řádek kódu je škodlivý.
  • Zvyšuje míru nejistoty. Po nějaké době i autor zakomentování kódu ztrácí přehled o důvodech, proč kód zakomentoval a především proč jej přímo neodstranil. Není si jistý, zda-li neplánoval znovupoužití kódu a proto nenachází odvahu se takovéhoto kódu zbavit. Nemrtvý kód začne v projektu po čase zapáchat a není to nic příjemného ani pro ostatní členy týmu, kteří do Vaší třídy zavítají.
  • Komplikuje prohledávání kódu. Hledaný výraz se objeví i ve výsledcích vyhledávání, které jsou v nemrtvém kódu. To je minimálně otravné.
  • Komplikuje slučování změn ve vývojových větvích a umisťování změn na server. Nemrtvý kód může být zdrojem dalších konfliktů, které budete muset zbytečně řešit.

Abyste mohli beze strachu odstraňovat nepotřebný kód, měli byste používat verzovací systém. Může se stát, že si odmažete část implementace, ke které se potřebujete vrátit. Právě v těchto okamžicích Vám začne být Váš verzovací systém ještě sympatičtější. Prostě o žádný kód napsaný dříve a umístěný na server nepříjdete. Pokud však nepříjdete přímo o celý server.

Pokud byste přesto chtěli tyto kódové zombíky pěstovat a potřebovali byste poradit klávesovou zkratku pro rychlé zakomentování, můžete se podívat na příspěvek jednoho nebohého kodéra. Začíná svůj text slovy "Kód neodstraňuji, nikdy nevíte, kdy bude znovu potřeba" ... a následně to v komentářích pěkně schytal ... ;-)

úterý 26. dubna 2011

Vícevrstvé architektury aplikací

Vrstvy ve světě programových systémů

Vrstvení (layering) je jednou ze základních technik, používanou návrháři systému, pro rozložení složitosti komplexních programových systémů. Je pravděpodobné, že pokud čtete tento příspěvek, používáte jeden z několika webových prohlížečů. Prohlížeč je pro Vás jednou z aplikací, která řeší požadovanou funkcionalitu. Pokud se však pokusíte dekomponovat jednotlivé využívané softwarové vrstvy, začnete se zanořovat takto (zjednodušeně):

  • aplikační vrstva (webový prohlížeč Chrome, Firefox, IE, ...)
  • vrstva operačního systému (Windows, Linux, Mac OS, Android, iOS, ...)
  • vrstva ovladačů zařízení (ovladač klávesnice, grafické karty, zvukové karty, ...)
  • vrstva instrukcí CPU, GPU, ... (instrukce na nejnižší hardwarové úrovni)

Webový prohlížeč načítá webový obsah z webových serverů. Využívá k tomu TCP/IP komunikační protokol, který má ve zjednodušené definici tyto čtyři vrstvy:

  • aplikační vrstva (programy nebo služby, které využívají přenos dat po síti ke konkrétním službám pro uživatele - FTP, HTTP, DNS, ...)
  • transportní vrstva (poskytuje transportní služby a je implementována v koncových zařízeních (počítačích))
  • síťová vrstva (zajišťuje síťovou adresaci, směrování a předávání datagramů, je implementována ve všech prvcích sítě - směrovače i počítače)
  • vrstva síťového rozhraní (umožňuje přístup k fyzickému přenosovému médiu, implementace je závislá na typu sítě)

Vrstvy lze uspořádat podle úrovně, na které se vyskytují. V našich příkladech je nejvyšší vrstvou aplikační vrstva (webový prohlížeč) a nejnižší vrstva instrukcí CPU. Toto uspořádání je důležité z pohledu závislostí jednotlivých vrstev. Vždy platí pravidlo, že vyšší vrstva využívá služeb pouze nižších vrstev a obvykle pouze o jednu úroveň nižší. Nižší vrstva tedy nic neví o vyšších vrstvách, pouze nabízí rozhraní, které je známé vyšší vrstvě.

Výhody a nevýhody použití vrstev

Rozdělení systémů do vrstev má několik významných výhod:

  • Zjednodušení složitých systémů. Stačí pochopit rozhraní a pravidla použití vrstvy, kterou chcete využívat a dokážete využít celý (jinak složitý) podsystém. Např. pokud se naučíte pracovat s FTP službou, začnete využívat celý TCP/IP komunikační protokol. Pokud byste se chtěli naučit celý TCP/IP protokol, dostanete se na úroveň optických vláken a jiných fyzických přenosových médií na nejnižší vrstvě, kterou nikdy přímo využívat nebudete. Takové znalosti jsou samozřejmě vítané, ale ne nezbytně nutné pro práci s FTP.
  • Možnost záměny implementace jedné z vrstev. Například na nejnižší síťové vrstvě TCP/IP protokolu mohou být data přenášena přes technologie PPP, Enhernet, ISDN, DSL nebo další. Každá technologie má svou implementaci a poskytuje služby, které daná vrstva poskytuje. Vyšší vrstvy záměnu implementace nižší vrstvy nepoznají a funkcionalita zůstane zachována.
  • Minimalizace závislostí mezi vrstvami. Souvisí se záměnou implementace vrstvy, viz. předchozí výhoda.
  • Standardizace funkcionality. Příkladem může být náš oblíbený TCP/IP protokol, který je v současnosti standardem pro síťovou komunikaci. Je využíván přes většinu operačních systémů a síťových zařízení. Pro vývojáře programových systémů je přítomnost standardizovaných vrstev dobrá zpráva. Jsou snižovány náklady na vývoj a jsou k dispozici komponenty, které umí standardizované vrstvy využívat.
  • Efektivnější testovatelnost. Pokud složitější funkcionalitu rozdělíte do menších částí (vrstev), můžete efektivněji pokrýt funkcionalitu testy. Můžete také některou z vrstev nahradit falešným mock objektem, tzn. testujete-li například vyšší vrstvu a potřebujete vnutit určité chování nižší vrstvě, tuto nižší vrstvu "namockujete".

Používání vrstev může samozřejmě přinášet i nevýhody:

  • Kaskádové změny. Jedná se o problém typický pro všechny závislé entity. Pokud dojde v nižší vrstvě ke změně, která vynutí změnu veřejného rozhraní této vrstvy, musí se změnit také vrstva vyšší. Konkrétně místo, kde se volá funkcionalita změněné nižší vrstvy. U podnikových aplikací je tato situace typická v případě přidání databázového sloupce. Musí se upravit datová vrstva, doménová logika i přidat zobrazení nového sloupce (viz. Základní aplikační vrstvy podnikových aplikací).
  • Snížení výkonu systému. Přidání každé další vrstvy může způsobit snížení celkového výkonu systému. Každá vrstva má vlastní reprezentaci datových entit. Během předávání datových entit mezi vrstvami musí být tyto entity transformovány. Transformace vyžaduje určitý výpočetní výkon a dojde tedy ke zpomalení. Zpomalení způsobená transformací mohou být kompenzována optimalizací na úrovni jednotlivých vrstev.

Evoluce aplikačních vrstev podnikových systémů

Než se dopracujeme k doporučovanému rozdělení aplikace do logických vrstev, podívejme se krátce na historický proces evoluce podnikových aplikačních systémů. Pro některé z nás to bude ještě stále aktuální téma.

V devadesátých letech byla populární architektura klient-server systémů. Jednalo se o dvouvrstvou architekturu, ve které klient obsahoval kromě uživatelského rozhraní i aplikační logiku a na serveru byla pouze relační databáze. Jako vývojová prostředí pro klientské aplikace se používaly VisualBasic, PowerBuilder a Delphi. V těchto prostředích bylo snadné a rychlé napsat aplikace intenzivně pracující s databázovými daty. Byly k dispozici vizuální uživatelské prvky (např. Datawindows v PowerBuilderu), které obsahovaly přímé propojení na databázi a pomocí SQL manipulovaly s relačními daty.

Pokud aplikace obsahovala pouze zobrazování a jednoduchou aktualizaci dat, bylo řešení přes databázové UI prvky poměrně efektivní. Postupně však docházelo ke změnám požadavků kladeným na tyto aplikace. Začala se více implementovat doménová logika a její pravidla při aktualizaci dat, validační pravidla a výpočty. Bylo běžné, že se kód doménové logiky přidával do UI prvků. Znovupoužitelnost kódu míchající doménovou a prezentační logiku začala být problematická a docházelo k duplikování kódu. Zásahy a změny v kódu se stávaly problematickými.

Jako alternativa k uložení doménové logiky se začaly používat uložené databázové procedury. Možnosti vložených procedur jsou však oproti programovacím jazykům značně omezené. Vývoj, ladění a testování databázových procedur není příliš efektivní. Navíc podnikové aplikace jsou schopny pracovat nad více typy databázových serverů (Oracle, MS SQL Server, Informix). To znamená, že jedna procedura se musí napsat, odladit a udržovat ve více verzích pro cílový databázový systém.

Odpovědí na výše popsané problémy architektury klient-server se stal nástup objektově orientovaného návrhu. Objektový přístup oddělil doménovou logiku od UI do samostatné vrstvy a zadefinoval třívrstvou architekturu (viz. další kapitola).

Martin Fowler označil nástup webových technologií za seizmický šok. Začalo být moderní i účelné distribuovat podnikové aplikace přes webový prohlížeč. V případě klient-server aplikací nebylo snadné jejich část zpřístupnit přes webový prohlížeč (UI a doménová logika byly úzce propojeny). Naproti tomu u vhodně navržených třívrstvých aplikací bylo možné zaměnit prezentační UI vrstvu za prezentační vrstvu tenkého webového klienta. To byl rozhodující argument, který posadil třívrstvou architekturu na koně a ta se následně v efektivitě rychlým tempem vzdalovala pokulhávající dvouvrstvé klient-server architektuře.

Základní aplikační vrstvy podnikových systémů

V předchozím textu jsem uvedl argumenty, proč se na podnikovou aplikaci dívat jako na soubor logických vrstev. Uvedli jsme si také důvody, proč je třívrstvá aplikace považována za efektivní způsob návrhu. Podívejme se nyní podrobněji na jednotlivé vrstvy:

Vrstva Zodpovědnost
Prezentační Zajišťuje služby týkající se uživatelského rozhraní, zobrazování informací, zachycení uživatelských událostí (myš, klávesnice, hlas), zachycení HTTP požadavků, vyvolání přes příkazový řádek, dávkové API.
Doménová Logika, která odpovídá povaze řešené problematiky. Má předobraz v reálném světě.
Datová Komunikace s databází (obecně perzistentní úložiště), systémy pro zasílání zpráv, správa transakcí a další balíčky.

Datová vrstva zajišťuje komunikaci s databází, řídí připojení a odpojení od databázového zdroje, spravuje databázové relace (sessions), zajišťuje podporu transakcí, poskytuje DAO (Data Access Object) objekty realizující CRUD (Create Read Update Delete) funkcionalitu pro jednotlivé třídy objektů. Zajišťuje dále mapování databázových struktur na objekty příslušných tříd. S výhodou lze využít některý z perzistentních frameworků typu NHibernate (v Java světě Hibernate), který řeší záležitosti typu Unit of Work, Identity Map a Lazy Load, které byste jinak museli implementovat sami.

Doménová vrstva nebo také vrstva obchodní logiky (business logic). Tato vrstva obsahuje třídy reprezentující entity řešené domény a třídy zajišťující doménová pravidla. Provádí výpočty a datové manipulace na základě vstupů a uložených dat.

Prezentační vrstva zajišťuje služby týkající se uživatelského rozhraní. Uživatelské rozhraní (UI - User Interface) je závislé na platformě, na které se aplikace provozuje. Může se jednat o WinForm aplikaci, webového klienta, Silverlight, Android klienta, konzolovou aplikaci, dávkové spouštění, apod. Uživatelské rozhraní je tvořeno ovládacími prvky, které nabízejí uživatelskou přívětivost závislou na cílové platformě. Podle požadavků na přívětivost UI je vybrána cílová platforma. Obvykle platí to, že čím bohatší ovládací prvky, tím větší provázanost na konkrétní platformu. Např. webové rozhraní je co do ovládacích prvků "chudobné", ale zobrazitelné na všech platformách, na kterých je k dispozici webový prohlížeč.

Je běžné, že aplikace má jednu datovou vrstvu, jednu doménovou vrstvu a jednu nebo více prezentačních vrstev. Podniková aplikace může nabízet pro uživatele, kteří navádí data a zpracovávají denní agendy, komfortnější rozhraní ve formě uživatelsky bohatého (rich) klienta. Jiná skupina uživatelů může prohlížet výstupy (sestavy) přes webové rozhraní. K dispozici může také být manažerská nádstavba přístupná přes chytré telefony jako Android nebo iPad aplikace. Aplikace může také nabízet programové API, například ve formě webových služeb, pro využití jinými podnikovými systémy.

Třívrstvá architektura je vhodná pro aplikace různých velikostí a složitostí. I u jednoduchých aplikací sestávajících např. z načtení údajů z příkazové řádky, z přepočtu a následného výpisu na obrazovku, je vhodné oddělit logiku odpovídající vrstvám do samostatných metod v rámci jedné třídy. U aplikací složitějších je vhodné oddělení logiky vrstev do samostatných tříd. U komplexnějších aplikací do samostatných balíčků nebo skupin balíčků. Jinak řečeno, žádná aplikace není natolik malá, aby při jejím návrhu nestálo za to uvažovat o rozdělení funkcionality do vrstev.

Třívrstvost je pouze základní logický koncept dnešních architektur. Určitě je možné některou z vrstev rozdělit na více specializovaných vrstev. Můžete také například mezi doménovou vrstvu a prezenční vrstvu vložit vrstvu služeb, která zajistí komunikaci mezi oběma vrstvami a navíc Vám připraví rozhraní pro zpřístupnění Vaší doménové logiky programovým systémům třetích stran.

Fyzické umístění vrstev

Třívrstvá architektura popsaná v předchozí kapitole je logickým pohledem na systém. Neméně důležité rozhodnutí při návrhu systému se týká použití fyzických prvků architektury pro provozování jednotlivých vrstev.

Nejjednodušší formou fyzického modelu je umístění všech vrstev na jeden server a klientský přístup přes webové rozhraní ("tenký" klient). Klient přistupuje k aplikaci přes webové GUI a každá uživatelská interakce může vynutit odeslání požadavku na server, který požadavek zpracuje a zašle odpověď. Tato architekura vyžaduje trvalé připojení k webovému serveru a proto není vhodná pro aplikace, u kterých je požadováno offline použití a následná synchronizace.

Pokud je na klientské straně vyžadováno uživatelsky "bohatší" GUI (rich client), je nutno přesunout minimálně část prezentační vrstvy na klientský počítač. V některých případech je přesunuta i doménová vrstva a také část nebo celá datová vrstva. Takový klient je označován jako "tlustý" nebo "tučný". Distribuce tučného klienta je obvykle problémovější než serverová instalace. Na každé klientské stanici musí být navíc připraveno prostředí vyžadované pro spuštění aplikace - databázový klient, .Net FW, JRE, apod.

Programátorské nešvary při (ne)použití architektury vrstev

  • Umístění funkcionality do nesprávné vrstvy. Častý nešvar, který se obvykle projevuje tak, že do prezenční vrstvy umístíte pravidlo nebo logiku z doménové vrstvy. Např. mějme požadavek, aby ve výpisu přehledu hospodaření za jednotlivé roky byly červeně vyznačeny ty hodnoty položky, které jsou oproti loňsku alespoň o 10% lepší. Chybným postupem je nastavit formátování položky přímo v grafickém ovládacím prvku. Správně je na doménové vrstvě zadefinovat vyhodnocení tohoto pravidla, které zavolá prezenční vrstva a výsledek aplikuje na příslušnou vlastnost (barvu) položky.
  • Nedodržení závislostí pouze z vyšší vrstvy na nižší. Víme, že prezentační vrstva může být závislá na doménové a ta zase pouze na datové. Pokud se stane, že datová vrstva vytvoří závislost na doménové, může nejen dojít k zacyklení závislostí, ale především se stává takovýto systém problematicky použitelným.
  • Prezentační vrstva je závislá na datové. Některé ovládací prvky mohou přímo využívat datový zdroj, který je určen např. SQL příkazem. Taková funkcionalita začne být problematická v situaci, kdy chcete na výběr aplikovat doménovou logiku, např. autorizovat přístup uživatele k datům. Vhodnější je adresovat nějakou třídu v doménové vrstvě, která potřebné omezení aplikuje a vrátí požadovaný výsledek.
  • Nerespektování vrstev. Ať už z neznalosti nebo lenosti se programátoři pravidlem rozdělení do vrstev neřídí vůbec. Systém pak připomíná dort pejska a kočičky, ve kterém je všechno smícháno dohromady. Udržovatelnost systému a navigace v kódu je problematická.

Informační zdroje

  • Martin Fowler: Patterns of Enterprise Application Architecture

pondělí 14. března 2011

Princip obrácení závislostí - oddělte rozhraní od implementace

  • Špatný návrh systému
  • Princip obrácení závislostí - definice
  • Příklad aplikace DIP
  • DIP a Separated Interface Pattern
  • DIP a Dependency Injection
  • DIP a související problematiky
  • Další zdroje a ukázky
  • Špatný návrh systému

    Abychom pochopili důležitost principu popisovaného v tomto příspěvku, pokusme se nejdříve zadefinovat charakteristiky špatně navrženého systému. V případě, že rozpoznáme typické chyby v návrhu, budeme schopni těmto chybám lépe předcházet. Vyjdeme z definice Roberta C. Martina, který poukazuje na tři základní charakteristiky chybného návrhu:

    • Nepružnost (rigidity). Je obtížné provést změnu, neboť ta se projeví na spoustě jiných míst v systému. Jednoduchou změnu není možné provést bez nutnosti upravit závislé části systému. To může vynutit kaskádový efekt změn. Důsledky změny je pak těžké odhadnout a je tedy těžké odhadnout i skutečné náklady na změnu. Pokud systém netrpí nepružností je flexibilní.
    • Křehkost (fragility). Při provedení změny se nepředpokládaně projeví problémy na jiných místech systému. Změny se mohou projevit i na místech, které nemají přímou souvislost s měněnou oblastí. Uživatelé a manažeři systému nejsou schopni predikovat kvalitu systému. Opravy takových problémů mohou zanést další problémy. Pokud systém netrpí křehkostí je robustní.
    • Imobilita (immobility). Je obtížné část systému osamostatnit a použít v jiném systému. Důvodem je vysoká závislost a provázanost mezi částmi systému. Návrháři jsou zastrašeni množstvím práce, které obnáší osamostatnění části systému a uchylují se k duplicitám a znovuvyvíjení podobných částí. Pokud systém netrpí imobilitou je znovupoužitelný.

    Co je příčinou takto definovaného chybného návrhu? Je to vysoký stupeň závislostí mezi částmi (komponentami) systému. Pokud se nám podaří snížit stupeň provázanosti jednotlivých částí systému, náš systém se stane flexibilnějším, robustnějším a znovupoužitelnějším. Princip obrácení závislostí nám dává doporučení, pomocí kterých je tento cíl lépe dosažitelný.

    Princip obrácení závislostí - definice

    Princip obrácení závislostí (DIP - Dependency Inversion Principle) je pátým základním principem objektové návrhové metodologie SOLID. Jeho definice zní:

    A. Moduly vyšší úrovně by neměly být závislé na modulech nižších úrovní. Obojí by měly být závislé na abstrakci.
    B. Abstrakce by neměla záviset na detailech. Detaily by měly záviset na abstrakcích.

    V "tradiční" (naivní) architektuře mohou být komponenty na různých vrstvách ve vztahu závislostí jak je uvedeno na obrázku. Komponenta A je na vyšší úrovni než komponenta B a je na ní závislá. Komponenta B je závislá na komponentě C. Transitivita v tomto případě neplatí, tzn. že komponenta A nezávisí na komponentě C.

    DIP nám radí odstranit přímou závislost komponent tím, že použijeme abstrakci (v tomto případě rozhraní). Konkrétně komponenta A zadefinuje rozhraní, které požaduje pro svoji funkcionalitu ("Požadované rozhraní komponentou A"). Toto rozhraní je ve veřejné části komponenty A. Komponenta A nic neví o komponentě B. Komponenta B zná z balíčku komponenty A pouze veřejné rozhraní "Požadované rozhraní komponentou A" a toto rozhraní realizuje. Tím se stane komponenta B "kompatibilní" pro komponentu A, aniž by se implementace obou komponent "znaly". Rozhraní "Požadované rozhraní komponentou A" reprezentuje funkční dohodu mezi komponentou A a komponentou B a obecně mezi dalšími komponentami, které rozhraní realizují. Situace by měla být zřejmá z obrázku:

    Někdy se můžete setkat s UML notací (viz. další obrázek), ve které jsou zachycena "vystavená" rozhraní komponent. Komponenta A vyžaduje rozhraní (otevřený půlkruh) "Požadované rozhraní komponentou A". Komponenta B stejné rozhraní nabízí (uzavřený kruh). Proto jsou schopny vzájemně komunikovat.

    Z předchozích obrázků je zřejmé, proč se princip nazývá principem "obrácení závislostí". Komponenta A byla původně závislá na komponentě B. Po aplikování principu již komponenta A nezávisí na komponentě B, ale ta závisí na veřejném rozhraní komponenty A. Závislost se obrátila.

    Příklad aplikace DIP

    Je požadován systém, který bude kopírovat znaky zadané z klávesnice na tiskárnu. Program by mohl vypadat třeba takto:

    namespace KopirovaniZnaku
    {
        public class SpravceKopirovani
        {
            void KopirovatZnaky()
            {
                char znak;
                while ((znak = NacistZKlavesnice()) >= 0)
                {
                    VypsatNaTiskarnu(znak);
                }
            }
            
            ...
        }
    }
    

    Základem je metoda KopirovatZnaky(), která postupně načítá znaky z klávesnice a vypisuje je na tiskárnu. Implementace privátních metod NacistZKlavesnice() a VypsatNaTiskarnu() není důležitá. Důležitá je však závislost třídy SpravceKopirovani na implementaci čtení z klávesnice a zápisu na tiskárnu.

    Problém našeho řešení nastane v momentě, kdy se objeví nový požadavek, aby bylo možné znaky vypisovat nejen na tiskárnu, ale také na monitor. V tento okamžik musíme přidat rozhodování na místo v programu, kde se implementuje výpis výstupu. Přidáme také výčtový typ VystupniZarizeni pro reprezentaci typu výstupního zařízení.

    namespace KopirovaniZnaku
    {
        public enum VystupniZarizeni
        {
            Tiskarna,
            Monitor
        }
    
        public class SpravceKopirovani
        {
            void KopirovatZnaky(VystupniZarizeni vystup)
            {
                char znak;
                while ((znak = NacistZKlavesnice()) >= 0)
                {
                    switch (vystup)
                    {
                        case VystupniZarizeni.Tiskarna:
                            VypsatNaTiskarnu(znak);
                            break;
                        case VystupniZarizeni.Monitor:
                            VypsatNaMonitor(znak);
                            break;
                    }
                }
            }
            
            ...
        }
    }
    

    Program se nám komplikuje a začínáme tušit, že narůstá počet přímých závislostí mezi třídou SpravceKopirovani a implementací čtení a zápisu pro různá vstupně-výstupní zařízení. Vzpomeňme si na princip obrácení závislostí a odstraňme tyto přímé vazby. Zadefinujeme rozhraní IVstupniZarizeni, které nám bude reprezentovat vstupní zařízení a bude nabízet metodu pro čtení znaku. Podobné rozhraní bude potřeba pro výstupní zařízení (IVystupniZarizeni), které bude nabízet metodu pro zápis znaku.

    namespace KopirovaniZnaku
    {
        interface IVstupniZarizeni 
        {
            char NacistZnak();
        }
    
        interface IVystupniZarizeni 
        {
            void VypsatZnak(char znak);
        }
    
        public class SpravceKopirovani
        {
            void KopirovatZnaky(IVstupniZarizeni vstup, IVystupniZarizeni vystup)
            {
                char znak;
                while ((znak = vstup.NacistZnak()) >= 0)
                {
                    vystup.VypsatZnak(znak);
                }
            }
        }
    }
    
    namespace Klavesnice
    {
        public class Klavesnice : KopirovaniZnaku.IVstupniZarizeni
        {
            public char NacistZnak()
            {
                // Implementace načítání znaků z klávesnice
            }
        }
    }
    
    namespace Tiskarna
    {
        public class Tiskarna : KopirovaniZnaku.IVystupniZarizeni
        {
            public void VypsatZnak(char znak)
            {
                // Implementace výpisu znaku na tiskárnu
            }
        }
    }
    

    Vytvořili jsme programové komponenty KopirovaniZnaku, Klavesnice a Tiskarna. Ty spolu komunikují přes rozhraní IVstupniZarizeni a IVystupniZarizeni, která zadefinovala komponenta KopirovaniZnaku. Tato komponenta je na vyšší úrovni než komponenty Klavesnice a Tiskarna. Podle principu DIP nemáme v systému žádnou přímou závislost mezi implementacemi. Závislost je pouze na rozhraní. Systém nyní zřejmě splňuje požadavky na dobrý návrh, viz. úvodní kapitola příspěvku.

    DIP a Separated Interface Pattern

    Z principu DIP plyne, že bychom se měli ve fázi návrhu systému soustředit spíše na definici rozhraní mezi komponentami než na úvahy o vlastní implementaci. A vlastní rozhraní pak logicky oddělit od implementačních detailů. Tato programovací technika bývá označována jako "Programování vůči rozhraní, ne vůči implementaci".

    Jeden z návrhových vzorů je mírnou modifikací původního DIP a řeší způsob uložení veřejného rozhraní komponenty. Vzor se nazývá Separated Interface Pattern a doporučuje oddělit veřejné rozhraní komponenty a její vlastní implementaci do samostatných balíčků. Toto oddělení přináší výhody při referencování mezi balíčky. V případě DIP bylo rozhraní součástí balíčku komponenty vyšší úrovně. Bylo proto úzce spjato s klientskou stranou rozhraní, která nesla za rozhraní zodpovědnost. V případě SIP je rozhraní odděleno i od této komponenty a tím je zajištěno, že za vývoj rozhraní není zodpovědná pouze klientská strana.

    Na obrázku je vidět, že komponenta A závisí na rozhraní komponenty B. Rozhraní komponenty B je uloženo v samostatném balíčku. Implementace komponenty B je v samostatném balíčku a realizuje toto rozhraní.

    DIP a Dependency Injection

    Dependency Injection (DI) je množina návrhových technik souvisejících s DIP. Řeší zodpovědnost za zajištění závislostí komponent na externích zdrojích. Cílem Dependency Injection je oddělit problematiku získání závislostí od vlastních komponent.

    Jedna z technik DI, nazývaná Constructor Injection, definuje závislosti mezi komponentami s využitím předávání závislých komponent pomocí argumentů konstruktorů. Tzn. závislosti jsou ustanoveny v době vzniku komponenty.

    Pro uplatnění principů DI se používají různé frameworky označované jako Inversion of Control frameworks.

    DIP a související problematiky

    Jak je patrné z předchozího textu, princip DIP vnáší do návrhu systému prvky flexibility a snižuje vzájemné propojení komponent. Díky těmto vlastnostem mohou být systémy otevřené pro použití zásuvných modulů - plug-inů (návrhový vzor Plug-in). Jedná se o externí implementace (třetích stran), které respektují a realizují požadované rozhraní.

    Můžete se také seznámit s návrhovým vzorem Service Locator, který je určen pro registraci a lokalizaci služeb za běhu programu. Není zodpovědný za vznik instance služby, proto se často kombinuje se vzory typu Factory Pattern a (nebo) Dependency Injection.

    Příjemným důsledkem oddělení rozhraní a implementace je možnost použití mockovacích frameworků při testování komponent. S využitím rozhraní můžete snadno vytvořit příslušnou mock implementaci. Vytvoříte falešnou implementaci, která provádí to, co v dané situaci vyžadujete. Více o použití mocků v některém z budoucích příspěvků.

    Další zdroje a ukázky

    Máte nápad, připomínku, našli jste chybu? Přidejte prosím komentář k tomuto článku.

    pátek 4. března 2011

    Tři, dva, jedna, Android, start

    Patříte mezi fanoušky platformy Android a chcete si vyzkoušet napsat nějakou aplikaci? Zajímá Vás, co všechno k vývoji aplikací na platformě Android potřebujete? Hledáte stručný úvod do problematiky? Pak určitě pokračujte ve čtení.

    Firma Google je se svojí platformou pro mobilní zařízení velice úspěšná. Denně se celosvětově aktivuje více než 300 000 nových zařízení s Androidem. Jedná se aktuálně o nejrychleji rostoucí platformu na trhu mobilních zařízení. Potenciál trhu s Android aplikacemi je tedy značný. Možná, že se nakonec prosadí aplikace, kterou napíšete právě Vy!

    Základy architektury platformy Android

    Android je softwarová platforma sestávající z operačního systému, middleware vstvy a vlastních koncových aplikací. Operační systém je založen na Linuxu verze 2.6 a zajišťuje základní systémové služby jako zabezpečení, správu paměti, správu procesů, řízení sítě a další. Middleware vrstva nabízí služby a knihovny, které využívají koncové aplikace.

    Aplikace jsou napsané v Javě a jsou spouštěny v Android Runtime. Každá aplikace běží v samostatném procesu a má k dispozici vlastní instanci Dalvik Virtual Machine. Tím je zaručena vzájemná autonomita a ochrana při současném běhu více různých aplikací.

    Popis architektury na stránkách Androidu.

    Co potřebuji k vývoji aplikací?

    • Java Development Kit (JDK). Vývojový framework pro Java aplikace do firmy Sun, kterou nedávno koupil Oracle. Je to základní množina nástrojů a knihoven pro vývoj Java aplikací. JDK instalujete jako první. Aktuální verze je 1.6.0_24 (Stažení JDK). Pozor, ať si nenainstalujete Java Runtime Environment (JRE). Jedná se o framework určený pouze ke spouštění Java aplikací.
    • IDE Eclipse. Pro Android je doporučeno integrované vývojové prostředí Eclipse ve verzi 3.6. (Helios) a vyšší. Samozřejmě lze použít i jiná IDE, ale pro Eclipse existuje nejlepší podpora ze strany autorů Android platformy. Stáhněte si verzi Eclipse v edici Classic a vyšší. Viz. http://www.eclipse.org/downloads/.
    • Android SDK. Jedná se o sadu nástrojů a middleware knihoven pro podporu vývoje Android aplikací. Nejprve nainstalujete správce Android SDK, ke stažení zde. Správce Vám pak pomůže s instalací jednotlivých komponent SDK. Platforma Android se stále vyvíjí a od počáteční verze 1.1 se přes (významné) verze 1.6, 2.1, 2.2, 2.3 probojovala až k aktuální verzi 3.0. Můžete si nainstalovat pouze některé z verzí API nebo všechny.
    • ADT Plugin pro Eclipse. Zásuvný modul pro vývoj Android aplikací v Eclipse. Díky tomuto rozšíření máte v Eclipse k dispozici podporu pro Android projekty, pro vytváření UI, pro spouštění a debuggování ve virtuálních nebo skutečných Android zařízeních. Plugin se instaluje v Eclipse jako ostatní rozšíření, více na stránce s popisem instalace.
    • Testovací zařízení. Budete potřebovat na něčem vyvíjené aplikace spouštět pro účely otestování a ladění. K dispozici máte podporu virtuálních zařízení (AVD - Android Virtual Devices). Jedná se o virtuální emulátory, u kterých můžete nastavit verzi API, rozlišení v pixelech a další vlastnosti, které mají skutečná mobilní zařízení. AVD můžete mít v systému několik. AVD jsou spravovány Správcem Android SDK. Aplikaci vyvíjenou z Eclipse můžete samozřejě spouštět oproti skutečnému zařízení, které je připojeno k počítači přes USB port. V tomto případě musíte doinstalovat USB ovladač, který je specifický pro příslušný typ zařízení (HTC, Sony, ...).

    Popis přípravy vývojového počítače je k dispozici na webu Android Developers.

    Čím začít?

    Určitě si vyzkoušejte vytvořit jednoduchou Hello World aplikaci podle tohoto návodu. Seznámíte se se základními pojmy a principy vývoje Android aplikací.

    Až se trochu rozkoukáte a pozdravíte svět Androidu, pokračujte v dalších tutoriálech. Autoři Vás za ruku provedou problematikami návrhu UI, lokalizací aplikací, testováním i složitější aplikací na vkládání poznámek.

    Jak pokračovat?

    Pokud jste na tom podobně jako já, budete si muset zopakovat syntaxi jazyka Java. Sice jsem v Javě kdysi napsal jednu (stále ještě živou) aplikaci, ale od té doby jsem výrazně zdotnetil. Každopádně dobrá znalost syntaxe a principů jazyka Java je pro vývoj Android aplikací nezbytná.

    Budete se muset detailně seznámit s API Androidu a na příkladech je pochopit a osahat si je v praxi.

    Bude se Vám také hodit obecnější znalost principů programování a navrhování aplikací. Aplikace pro Android mají sice svá specifika, ale spousta obecných principů se samozřejmě dá aplikovat i zde. Můžete se nechat inspirovat příspěvky na tomto blogu ;-).

    Navštěvujte a aktivně přispívejte do vývojářských diskuzí a fór (i fórkem). I na českém Androidím rybníku to začíná ožívat. K dispozici je portál www.svetandroida.cz a spřátelené diskuzní fórum www.androidforum.cz.

    Podařilo se Vám naistalovat prostředí pro vývoj Android aplikací? Na jaké konfiguraci vyvíjíte? Máte nějaké tipy nebo rady?