Moderní programování v C#

Programování pro platformu .NET mi vždy přišlo (např. ve srovnání s J2EE) tak trošku jako divočina. O návrhových vzorech, testování a pružnějších technikách vedení projektů vědělo jen pár vyvolených (nezřídka kdy přicházejí právě ze světa Javy), běžnému programátorovi pojem „návrhový vzor“ evokoval tak maximálně „singleton“. A podle toho aplikace mnohdy vypadaly – netestova(tel)né, obtížně rozšiřitelné, monolity. Klidně se přiznám, že i já jsem byl takový programátor. Zkrátka, nebylo mezi vývojáři v .NETu povědomí o správném návrhu aplikací. Naštěstí se v poslední době začíná blýskat na lepší časy. Nejen blogeři píší o různých způsobech návrhu aplikací, ale i sám Microsoft odvedl na tomto poli kus práce – zpopularizoval návrhové vzory pro prezentační vrstvu díky ASP.NET MVC a MVVM, implementoval podporu pro POCO v Entity Frameworku, dotáhl do použitelné podoby kontejner Unity
V tomto článku bych rád nastínil, které principy vedly v poslední době k rapidnímu zvýšení kvality mých aplikací. Konkrétně se zaměřím na SOLID, TDD, IoC/DI a AOP.

SOLID

SOLID je akronym pěti principů, které je záhodno mít na paměti při návrhu a implementaci aplikace.

Single responsibility principle

Každý objekt by měl být zodpovědný pouze za jednu věc.
Pěkným příkladem z praxe je oddělení business logiky od způsobu uložení. Pěkným protipříkladem je naprasení business logiky do třídy ActiveRecord (třída reprezentující jeden řádek tabulky).

Open/closed principle

Otevřenost pro rozšíření, ale uzavřenost pro modifikaci.
Do praxe toto přenáším tak, že se snažím navrhovat rozhraní tak, aby byla dostatečně obecná. První půlka Raroušova článku by Vám měla objasnit, co mám na mysli – zjednodušeně řečeno mít místo hromady specifických metod jednu generickou.

Liskov substitution principle

Instance by měly být nahraditelné instancemi poděděných typů, aniž by to ovlivnilo vlastnosti programu (hlavně korektnost).
Tento princip se tedy týká dědičnosti, které osobně moc nefandím – IMHO je kompozice v mnoha případech lepší. Ale tento princip má zajímavý „důsledek“ v návrhu staticky typovaných jazyků – kovariance návratových typů metod a kontravariance typů parametrů metod.

Interface segregation principle

Více menších specializovaných rozhraní je lepší než jedno velké generické rozhraní.
Do jisté míry souvisí se SRP – nemíchat víc věcí dohromady. A jak uvidíme dále, jde tento princip na ruku DIP, IoC/DI a TDD.

Dependency inversion principle

Závislost by měla být na abstrakcích, ne na implementacích.
Když nějaká třída potřebuje využít nějakou jinou třídu (v rámci zachování SRP), neudělá to tak, že by si onu třídu vytvořila a použila. Místo toho jen deklaruje, že bude potřebovat využívat dané rozhraní (tj. závisí na abstrakci) a o dodání správné implementace se postará nějaká vyšší moc, typicky „dependency injection“ (viz níže).

SOLIDní aplikace pak vypadá tak, že každá třída implementuje nějaké rozhraní a sama používá jiná rozhraní. Žádná třída nezná žádnou jinou třídu – ví jen o rozhraních.

Test Driven Development

O programování řízeném testy toho již bylo napsáno mnoho. To hlavní, co mi tato metodika dala, je to, že nejdřív napíšu testy a pak implementuji funkcionalitu do té doby, než testy projdou. Pro mě osobně bylo důležité především přijmutí první části předcházející věty za svou, tj. nejprve psát kód, který pracuje proti vysněnému rozhraní. Tento přístup k tvorbě rozhraní (API) považuji za lepší než jen tak vařit z vody.
Samozřejmě i testy samy o sobě jsou fajn. Jaké typy testů ale psát? Unit testy, integrační testy, akceptační testy, testy UI, …? Univerzální odpověď Vám nedám a IMHO ani neexistuje. Každý projekt je specifický a pro každý mohou být vhodné jiné typy testů a jiná míra pokrytí testy.
Osobně nejsem zastáncem striktních unit testů za každou cenu. Pokud testovaná třída využívá nějakou externí službu, kterou lze virtualizovat/emulovat, tak virtualizuji/emuluji (typicky použití lokální testovací databáze). Pokud je toto nemožné nebo komplikované, uchyluji se k mockování, tj. vytvoření fake implementace rozhraní.
Pokud netestujete, zdá se Vám to jako ztráta času, a tento způsob návrhu API se Vám zdá poněkud nešťastným, pak je tu minimálně ještě jeden důvod, proč psát testy – člověk může snadno poznat, že si jednou malou změnou v kódu úplně rozbil funkčnost celého projektu.
Chcete dodávat nové verze produktu svým uživatelům co nejdříve a nejčastěji? Pak se bez testů (s velkou mírou pokrytí) asi také neobejdete. Pak Vás také budou asi zajímat pojmy jako Continuous Integration, Continuous Deployment či Continuous Delivery.

Inversion Of Control / Dependency Injection

O tomto tématu jsem již blogoval a byla o něm zmínka i v odstavci o SOLIDu. Dependency injection je jedna z možností, jak implementovat DIP. V praxi to vypadá tak, že třída potřebuje nějaká rozhraní ke své funkčnosti. To třída deklaruje tím, že tato rozhraní použije jako parametry konstruktoru (constructor injection) nebo jako properties (property setter injection).

public class Car : ICar
{
  public Car(IEngine engine, IWheels wheels)
  {
     // ...
  }
}

Když ale chceme někde pracovat s autem, jak vytvoříme instanci třídy Car? To je ale špatná otázka! Správně bychom se měli ptát, jak získáme instanci třídy implementují rozhraní ICar? No, to jsme si již řekli – jednoduše v dané třídě budeme mít konstruktor, kde jeden z parametrů bude typu ICar. To, jaká konkrétní implementace rozhraní se použije, určujeme samozřejmě my, a to v konfiguraci kontejneru.

Jediné místo, kde budeme kontejner explicitně používat k získání instance nějaké třídy, je vstupní bod aplikace. V případě desktopové aplikace to bude něco jako container.Resolve<Form1>(), v případě ASP.NET MVC zaregistrujeme vlastní controller factory, která bude vytvářet controllery pomocí kontejneru atd.

Aspektově orientované programování

O tomto tématu jsem už také blogoval. Stručně řečeno nám aspekty umožní injectovat vlastní kód do metod. Toto injectování můžete provést buď již v době kompilace (pak bych sáhl po vynikajicím PostSharpu) nebo až při běhu aplikace.
Injectování za běhu aplikace můžeme nechat opět na kontejneru – např. Unity nebo Castle Windsor s tím nemají problém.
Pak to funguje tak, že kontejner vrátí nějakou vlastní implementaci daného rozhraní, které obsahuje náš aspektový kód + působí jako Proxy pro původní implementaci.

Závěr

V článku jsem se pokusil shrnout postupy a techniky, které mi v poslední době pomohly zvýšit kvalitu mých aplikací.
Nutno ale podotknout, že nemá smysl lámat věci přes koleno – někdy (především u malých aplikací) může bezhlavá aplikace výše popsaných principů vést ke zbytečné práci – jednoduše by to byl „overkill“.

http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js
http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js

if (SyntaxHighlighter) {
SyntaxHighlighter.all();
}

20 thoughts on “Moderní programování v C#

  1. Dobré shrnutí, pár poznámek z vlastní praxe:

    * OCP je velmi důležitý princip, ačkoliv není tak populární jako třeba DI nebo SRP. Jsem tedy rád, žes ho připomenul.

    * Rozhraní ve smyslu C# jsou trochu problematická z pohledu příštích verzí API, zajímavá diskuze o nich je třeba ve Framework Design Guidelines. Je dobré to mít na paměti při aplikování ISP.

    * LSP mi vždycky mezi ostatními přišel spíš jen jako detail 🙂

    Dík za článek.

    To se mi líbí

  2. Ahoj chtěl bych se zaučít učit C# a především potom ASP.NET MVC pro realizaci web aplikací.

    Mohl bys mě nasměrovat na vhodné knihy či tutoriály abych se hned při studování syntax C#, vlastnostech a .NET MVC vlastnostech vydal správným směrem a hned na začátku se neučil řešit věci prasácky.

    PS: Vím, že to je běh na dlouhou trať.
    Děkuji

    To se mi líbí

  3. Tos vzal hopem ty aspekty 🙂 Imho jsou aspekty v C# implementovány attributy. Ať si hlavu lámu sebevíc, nemůžu najít use case pro jinou implementaci. Napadá tě nějaký?

    To se mi líbí

  4. Aspekty v C# jsou implementovány nijak 🙂 Atributy jsou jen obecná metadata.
    Jednou z možností, jak přinést AOP do C#, je změna výsledného CIL kódu po kompilaci, jak to dělá třeba PostSharp.
    Jinou možností je právě implementace přes kontejner.
    PostSharpí cesta IMHO nejde aplikovat tehdy, když pracujeme s assemblies třetích stran, tj. neprovádíme kompilaci veškerého kódu aplikace. Např. nějaká knihovna implementuje nějaké rozhraní, chceme využívat implementaci z téhle knihovny, ale chceme ji trošku pozměnit. Samozřejmě bysme mohli třídu podědit (pokud není sealed a metody jsou virtuální) nebo ručně vytvořit Proxy, ale AOP přes kontejner je IMHO také cesta…

    To se mi líbí

  5. Pěkný článek, jen pár poznámek:
    1) U „Interface seggregation“ principu bych zmínil, že se hodi používat postup „extension interface“, i když možná byl v očích (kvazi)vývojářů zprofanován COM světem – http://www.laputan.org/pub/sag/extension-interface.pdf

    Ukázka z mého Posterous API

    IRawRequestResponsePublisher publisher =

    PosterousApplication.Current.GetInterface();

    Debug.Assert(publisher != null);
    publisher.WebRequestCreated += ((_, e) => e.Value.Timeout = WEB_TIMEOUT);

    Příklad mám popsán zde http://jdem.cz/dnhg4 – všechna rozhraní v API jsou přímo či nepřímo potomkem IExtensionInterface.

    U C# ke stabilitě a jednoznačné odpovědnosti rozhraní dnes také přispívá možnost definovat extenzní metody, kdy lze jen importem jmenného prostoru s extenzní metodou (škoda, že nelze importovat jen třídu s extenzními metodami) přidávat v různých částech projektu instancím podporujícím stejné rozhraní další odpovědnosti.

    2) Open-Closed princip popisuje nedosažitelný ideální stav, kterému se můžeme jen blížit v závislosti na kontextu vyvíjené aplikace a aktuální funkci kódu. Každá refaktorizace kódu je nutně v napětí s s tímto principem – už jen proto, že dlouho nerefaktorizovaný kód (prý) zahnívá jako neudržovaný rybníček, jak zaznělo v nějaké „agile“ metafoře od strýčka Boba.:) A každá změna od zákazníka, o které nutně dnes nic nevíme, je opět v napětí s tímto principem. Ač se to možná nezdá, protože Open-Closed princip je součástí různých „agilních“ a SOLIDních knih, vášnivá aplikace tohoto principu je kupodivu ukázkou toho agilními chlapíky nenáviděného zbytečného předběžného systémového designu „pro budoucnost“, v níž zákazníkovy požadavky ještě neznáme.

    U tohoto principu nejde určitě jen o použití generických metod (viz odkaz v článku).

    Generické i negenerické třídy a metody lze pro dosažení kombinovat třeba se vzorem Template method nebo se vzorem Strategy – daní je ale zavedení dědičnosti a nutnost úzkostlivě dodržovat ten trochu v článku s despektem pojednaný princip substituce typu a podtypu – „Liskov substituční princip“.;)

    To se mi líbí

  6. Dan: Můžeš začít třeba v MSDN třídou ContextBoundObject. A ž ti přestane stačit, podívej se na Augim doporučovaný PostSharp.
    MS ve svých Enterprise library používá tuším pro aspekty stále vlastní podivnou terminologii – „Policy injection“.

    To se mi líbí

  7. Nemam rad clanky tohoto typu z jednoho prosteho duvodu. Ale postupne.

    Krome AOP, ktery vyzaduje runtime s podporou metadat, jsou vyse uvedene principy dlouhodobe zname a nevznikly v prostredi Java ale mnohem drive. Kompetentni programatorarchitect je znal a pouzival jiz v jazycich C. Jen se pro tyto techniky nepouzivaly aktualni „hype“ terminy. AOP je za poslednich nekolik let opravdu nova inovace ke ktere se Java komunita muze hrde a opravnene hlasit; vse ostatni tu jiz bylo mnohem drive.

    V porovnani z .NET ma Java jednu zasadni silnou stranku – dostupnost instrastruktury pro vyvoj aplikaci. Tato infrastruktura (zejmena aplikacni server, messaging, caching, apod) se v .NET zacina objevovat take ale v tomto smeru je Java mnohem dale.

    Duvod, proc se mi tento clanek nelibi je ten, ze clanek se snazi popsat neco zcela bezneho. Je to neco, jako by se snazil instalater na svem blogu ohromit kolegy tim, ze jim bude vysvetlovat k cemu je hasak nebo led-lampa. V IT si uz konecne musime jasne rict, ze vyvoj software ma jasna pravidla, neni to zadne voodoo a ze k tomu, aby nekdo mohl navrhnout kus software spravnym a funkcnim zpusobem musi tento jedinec projit jakymsi „ucnakem“ tak, jak je to zcela bezne v jinych inzenyrskych discipilinach.

    Predstavte si instalatera, ktery prijde k Vam domu opravit rozbity odtok a po hodine vykrikne „Hura, konecne jsem se naucil pouzivat hasak!“. Tak na mne pusobi clanky tohoto typu. Toto jsou zakladni veci jak navrhovat strukturu software – tohle by se melo ucit hned na zacatku.

    To se mi líbí

  8. Slezte z toho piedestalu Donalde, vidím vás! 😉 Být to můj blog, s chutí bych váš komentář o ničem smazal. A to je asi tak vše, co sem vám chtěl říct.

    To se mi líbí

  9. Petr Lazecky: Zbytečně dlouhým příspěvkem si jen popsal to, že se domníváš, že podobné techniky už dlouho znáš a že by bylo dobré, kdyby je znal každý. To je samozřejmě čirá utopie, a stejně tak informace o tom, co ty umíš nemá příliš velký přínos pro komunitu (narozdíl od tohoto Augiho článku), ale budiž ti přáno.

    P.S.: Kdybych u nějakého instalatéra četl pojednání o „led-lampě“, řekl bych si, že je to skutečně nějaký nedouk, když se nejspíš domnívá, že to je lampička s LED světýlky (jinak letlampa). Pokud podobným způsobem znáš i další pojmy už z vývoje v C tak potěš koště, a jsem rád za jednoznačné „hype“ termíny 🙂

    To se mi líbí

  10. René: Díky za doplnění. Na O/C mám asi podobný názor jako Ty – také je to pro mě těžko dosažitelný ideální stav. Liskové substituční princip jsem pojednal s despektem asi proto, protože mi přijde úplně samozřejmý 🙂
    Za požrání znaků se omlouvám, to WordPress 😉

    To se mi líbí

  11. Petr:
    1) AOP dle mého názoru nevyžaduje podporu atributů (pokud jsi těmi myslel ony metadata). Viz např. Interception v Unity.

    2) Nikde jsem netvrdil, že výše uvedené principy vznikly v prostředí Java. Jen tvrdím, že mi přijde, že vývojáři v prostředí Java mají větší povědomí o návrhových vzorech apod. A myslím, že je to právě tím, že mají k dispozici onu infrastrukturu, o které se zmiňuješ (a kterou jim závidím a rád bych ji také jednou viděl v .NETu :)), která je „vede“.

    3) Dovolím si částečně nesouhlasit – článek se nesnaží popsat to, co je běžné. Článek se snaží popsat to, co by mělo být běžné.
    Možná je to mým relativním mládím, možná prostředím, ve kterém se pohybuji – ale rozhodně na mě .NET komunita v ČR nepůsobí tak, že by výše popsané principy byly samozřejmé a v základní výbavě každého vývojáře.
    A to .NET komunita je jen (ta lepší) špička ledovce – stále je tu dost vývojářů (spíš programátorů) v .NETu, kteří do práce chodí jako do Kolbenky (nic proti nim – každý nemusí být nadšenec) a k profesnímu životu jim stačí to, co se naučili před X lety.

    Souhlasím s tím, že tyhle základní principy by se měly učit od začátku. Ale kde a kdo to člověku to řekne, že by právě tohle měl znát? Ač jsem nebyl kvantový student, na VŠ jsem se to nedozvěděl, v odborné literatuře se tato témata moc často neobjevovala, v fórech se o nich nediskutovalo.
    Ve všech směrech se ale začíná situace zlepšovat a i já se snažím tímto článkem situaci v komunitě zlepšit. Ač se nepovažuji za supr-dupr odborníka na dané téma, mnoha faktických chyb jsem se v článku snad nedopustil, a tak si myslím, že může některým lidem pomoci. A proto si myslím, že tento článek má smysl 🙂

    To se mi líbí

  12. Augi: Vidím, že WordPress je podobné zvíře jako ten děsivý dasBlog.
    Ad komentář k Petr Lazeckému a komentáře k jeho komentáři:
    1) Petře, podle mě Augi shrnul to, co by měli vývojáři znát, ale vím, že spíš neznají. Kdyby na pohovory chodili lidi, co znají všechny tyhle principy, byl bych šťastný. Realita je taková, že spíš potkáš třeba ASP.NET vývojáře s 3-roční praxí, který nikdy neslyšel ani o MembershipProvideru. Vývojáři v C++ běžně po několikaleté praxi řeší include h souborů metodou pokus-omyl, ale to určitě znáš.
    Analogie s instalatérem kulhá, protože pro vývoj skutečně neexistují přesné kuchařky a návody, jak řešit problém a i v článku zmíněné principy jsou spíš důležitá vodítka. Kdo z vývojářů skutečně počítá závislosti komponent (míru nestability) přes vzorec Ce/Ce + Ca, sleduje poměr abstraktních/konkrétních členů, počítá D = |A+I-1| atd. Pravdou je, že druhý extrém – vývojář, který tvrdí, že vývoj SW je umění, je většinou neschopné (a v kódu bohužel z řetězu urvané všehoschopné) čuně, které počítá s tím, že může své formuláříky ladit na 10x, když zákazník zjistí, že při 100 000 záznamech aplikace kolabuj, ea které si pod slovem umění vybaví jen tvůrčí a nezvládnutelný chaos, v němž řádky kódu simulují funkční aplikaci jak tahy štětcem K. Gotta umění.:)

    2) Argumenty ad hominem k Petrovi – Petr je výborný vývojář se skvělými znalostmi různých technologií a omlacovat mu o hlavu překlep led-let lampa mi přijde divné. Kdo mě znáte, tak víte, že většinou moc nechválím, ale Petra považuju za typ člověka, kterého s chutí vezmete na každý projekt.;)

    Podle mě za 30 let bude dávat v nějakém webovém archivu větší smysl Petrův příspěvek než zbytek našich komentářů. Potomci se budou jen divit, jak jsme mohli s takovými kovbojskými pravidly a vágními principy tvořit SW. Asi jako když dnes nechápeme, jak mohl praporek před autem figurovat jako povinná výbava a nástroj regulace dopravy.:)

    To se mi líbí

  13. add Petr: Prečo mám pocit v poslednej dobe, že v .net komunite vládne znechutenie zo všetkého určeného začiatočníkom, namiesto toho aby sme boli vďačný, že bola zvolená téma a jazyk určený širšej verejnosti. Napríklad u nás na Slovensku mam stále silnejší pocit, že aktívna vrstva v .net komunite začína starnúť a práve takýto prístup môže omladinu zabiť nadobro. Samozrejme nechem tým povedať, že Augi je začiatočnik, alebo nejak znižovať vašu odbornú kvalitu (svojho času ste bol celkom blog aktívny), len tak nejak zabúdame, že aj my sme niekde, nejako začínali.

    To se mi líbí

  14. Rene: Rýpnutí na překlep bylo na odlehčení situace a při tak rigidním odmítání „hype“ pojmenování standardů podle mě docela trefné. Nikdo tady nezpochybňuje vývojářské kvality Petra (a omlouvat tím jeho lidské mi přijde zbytečné), ale jen ty komunikační – myslel to asi dobře (že by mělo být standardem, aby vývojáři tohle uměli v základu), ale přetvořit to ve filipiku proti autorům, kteří se to snaží popsat, a ještě do toho zamontovat to, že on to všechno zná a tak jej podobné články znechucují, bylo velmi nešťastné a hlavně zbytečné. Komunitě to nic nepřineslo, a Petr pak působí jako by byl na internetu týden. Denně se přeci setkáváme s desítkami věcí, které někdo „objeví“, a my je známe už bůhvíjak dlouho (z poslední doby mě pobavilo na twitteru nadšené retweetování informace o doplňování diakritiky do českého textu (ohákování) mezi copywritery, které podle mě musel znát každý kdo pracuje s textem už aspoň 10 let, ale ve výsledku jsem se pouze pousmál a neměl potřeba na to reagovat… což porušuji až tímto pouze kvůli názornému příkladu :))

    Abych napsal i něco k tématu – studium informatiky na VŠ mi i při existenci předmětů jako Softwarové inženýrství a programování v C++ mnoho nepřineslo, v OOP a nových technologiích se musím dovzdělávat sám, maximálně ještě zúročuji univerzální Teoretickou informatiku, Logiku, přehled z Paradigmat programování apod. Takže každou aktivitu typu blogpostů Augiho jen vítám a rád si přečtu také reakce dalších zkušených vývojářů k tématu. Reakce, že by to měl člověk znát automaticky a podobné články proto nemají vznikat fakt neberu…

    To se mi líbí

Napsat komentář

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.