Git je boží. To je jasné i pětiletému dítěti, které followuje ty správné accounty na twitteru. Jenže když pracujete v zavedené firmě, celkem jistě máte nějaký jiný (starý, ošklivý, morálně přežitý) systém pro správu zdrojáků. V našem případě je to Subversion. Vyplatí se nám přejít na Git?
Jak naprogramovat blog za 18 minut
| 7 comments | MVC
Dnes jsme s lidma z práce měli přednáškový blok na hradecké univerzitě, bylo to moje poprvé na akademické půdě! Během mé části vznikla jednoduchá webová demo aplikace postavená na ASP.NET MVC 3, využívající Entity Framework 4.1 Code-First pro uložení dat, Razor jako view engine a Markdown syntaxi pro formátování textu. Protože vymezený čas byl dost krátký, mám pár poznámek, na které nedošlo.
StructureAssertions – knihovna pro testování závislostí
| 2 comments | clean code, Design, NuGet, Reflection
V minulém postu jsem nastínil způsob, jak lze kontrolovat zakázané závislosti mezi třídami v aplikaci. Trochu jsem si zaexperimentoval a vytvořil knihovnu StructureAssertions a NuGet balíček, který si můžete nainstalovat do testovacího projektu a jednoduše závislosti otestovat.
Jak uhlídat závislosti ve velké aplikaci
UPDATE: Knihovnu pro testování závislostí si nyní můžete nainstalovat jako NuGet balíček. Více v následujícím postu.
U každé větší softwarové aplikace hrozí, že kód začne časem degradovat. Jedním z typů degradace jsou závislosti, které porušují původní architektonický návrh. Modelový příklad: v ASP.NET MVC aplikaci platí, že model nesmí referencovat žádný controller. Jak ale takové pravidlo vynutit, když jsou modely i controllery v jednom Visual Studio Projectu?
Jak přidat do projektu nový soubor při instalaci NuGet balíčku
Poprvé jsem byl postaven před nutnost napsat instalační skript pro NuGet balíček. Chtěl jsem úplně triviální věc – přidat do projektu link na několik souborů a nastavit jim vlastnost "Copy to Output Directory" na "Copy if newer". Nešlo to úplně tak hladce a proto si zde o tom ublogávám.
Jak jsme jeli na CodeRetreat do Berlína
Minulý týden jsme si udělali s kolegou @bajtosem, jeho ženou a novým kamarádem @mictechem príma výlet do Berlína. Hlavním cílem bylo zúčastnit se akce Code Retreat Berlin 2011. Samozřejmě došlo i na networking, socializing, hangouting, gaststätting, beer-evaluating a sightseeing (neboli sezení v hospodě a motání se s kocovinou po městě druhý den), o tom ale třeba někdy příště. Dnes bych se chtěl s vámi podělit, o čem je Code Retreat a co mi to dalo.
Přednáška o ASP.NET MVC 2
| 1 comments | MVC
Innovator of the Year
| 1 comments |
Připojte se k nám
| 4 comments | jobs
Spike aneb ověření, jestli to vůbec půjde
Jak jsem přešel na Linux a MonoDevelop
| 3 comments | Blog Engine, Linux
...a jeden navrch
| 2 comments |
Čtyři smartfouni
| 2 comments |
Co by měl každý programátor vědět
| 18 comments |
Podle mého jsou určité základy, které musí každý programátor o svém jazyce vědět. Priorita operátorů podle mně patří mezi ně. Argument, že v jazyce XX nebo YY to bylo jinak, je podle mně zcestný. Programovací jazyky jsou natolik jednoduché, že programátor nemůže mít problém si těch pár věcí zapamatovat. Netvrdím, že musíte z hlavy vědět vše o nějakém obskurním jazyku, který používáte jednou za uherský rok, ale pokud se někdo už několik let živí psaním kódu v C#, musí takové věci znát bez zaváhání.
Co všechno by jste tedy měli vědět o svém hlavním programovacím jazyku?
- Priorita operátorů (!, ++, ||, &&, ?:)
- Jak zapsat hodnotu různých datových typů (0xAF, 123 a 123L, 0.12M a 0.12D)
- Základní konverze na string a zpět (například donedávna jsem považoval za samozřejmost, že každý ví jak konvertovat "FFEEDD" na int, ale byl jsem vyveden z omylu)
- Speciální jazykové konstrukce jako anonymní delegáti a lambda funkce
Tento seznam určitě není kompletní, pokud vás napadne něco dalšího, napište to do komentářů.
Pokud tyto základní věci neovládáte, riskujete dva velké problémy: Budete se pomalu orientovat v kódu ("jakého typu je var a = new[] { 1, 10, 100, 1000 };?"), a při psaní budete používat zbytečné ornamenty (if(((age>60)||(disabled==true))&&(balance>limit))). Druhý problém je o dost horší než první, protože při čtení zpomalujete jen sami sebe. Pokud ale kvůli své neznalosti použijete o 8 závorek navíc, ztěžujete práci ostatním, protože znepřehledňujete kód. Je sice pravda, že ostatní pak nemusí používat svn blame, protože už znají váš styl:-) Být poznat podle stylu kódu ale není nic o co by jste měli stát. A taky budete hodně nadávat na StyleCop:-)))
Pokud neovládáte ani naprosto základní věci, jak chcete přesvědčit ostatní o tom, že jste dobří programátoři?
Co všechno musíte vyřešit při psaní WPF aplikace
| 0 comments |
- INotifyPropertyChanged
- Validace
- Lokalizace
- Undo/Redo
INotifyPropertyChanged
Aby View vědělo kdy se má updatovat, je nutné u všech ViewModel objektů implementovat rozhraní INotifyPropertyChanged. Naštěstí je velmi jednoduché, ale budete muset psát spoustu opakovaného kódu:
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name);
}
}
Vyhnout se mu můžete buď generováním kostry ViewModelu pomocí DSL jazyka, nebo pomocí AOP (kolegové používají PostSharp), kde stejná property může vypadat takto:
[Notify]
public string Name { get; set; }
Lokalizace
Existuje několik přístupů k lokalizaci WPF aplikace, které se liší způsobem uložení resource stringů, možností výběru lokalizovaných vlastností a možností přepínat jazyk "za běhu", bez nutnosti restartu aplikace. Řešení které doporučuje Microsoft (přes binární XAML, neboli BAML) potřebuje externí nástroj LocBaml.exe, editovat jazykové verze musíte v CSV a při přepnutí jazyka za běhu se nelokalizují již otevřená okna. Které vlastnosti budou lokalizované se dá určit až při lokalizaci, takže překladatel se může rozhodnout, že např. sloupec v tabulce bude mít jinou velikost nebo label se zarovná na jinou stranu. To může být výhoda i problém, záleží na vašich potřebách. Další dva způsoby naleznete v článku Localizing WPF Applications using Locbaml, oba ale neumožňují přepínat jazyk za běhu.
Zajímavé řešení je popsáno v WPF Localization - On-the-fly Language Selection. To už umožňuje skutečné přepnutí jazyka bez nutnosti restartu aplikace. Je založené na vlastním Converteru, který provádí lokalizaci, díky čemuž lze používat poziční parametry v resource stringu ("{0}") a jejich hodnoty dokonce bindovat. Trik umožňující okamžité přepnutí jazyka spočívá v nahrazení lokalizovaných hodnot MultiBinding objektem, kde první vazba je na LanguageContext.Instance.Dictionary, která při změně jazyka posílá událost PropertyChanged. To způsobí znovunačtení všech hodnot. Tomuto řešení bych vytknul pouze způsob uložení resourců (vlastní XML), ale velmi lehce si můžete napsat vlastní LanguageDictionary a číst hodnoty např. z resx souborů.
Validace
Při prvním pohledu na WPF se zdá, že validace je vyřešená věc, ale pokud chcete použít vzor MVVM, není to tak jednoduché. Ve ViewModelu tedy implementujeme IDataErrorInfo a projdeme všechny deklarace Binding v XAMLu a přidáme ValidatesOnDataErrors=true.
Jenže kromě business validací je nutné také validovat vstup od uživatele. To jsou opravdu dvě různé věci: Mějme třídu Person s property Age typu int. "Business" validace je, že Age nesmí být záporné a musí být menší než 150. Když je hodnota mimo tento rozsah, vrátíme přes IDataErrorInfo chybovou hlášku. Jenže co když uživatel zadá "qweqweqwr"? Výchozí chování Bindingu je výjimku prostě spolknout. Můžeme nastavit ValidatesOnExceptions na true, ale jednak nemůžeme změnit validační hlášku, a také nechceme žádné výjimky. Výjimky jsou pro výjimečné případy, např. když je vyhlášen výjimečný stav. Spoléhání na výjimky může aplikaci znatelně přibrzdit.
WPF Binding zpracovává vstup přes řetěz validátor - konvertér - property setter. Možným řešením by bylo vkládat vlastní validátory do deklarace Bindingů. To ale odporuje našemu cíli, dostat všechnu logiku do ViewModelu. Navíc až budeme objekt ukládat (uživatel zmáčkne Ctrl+S), nedozvěděli bychom se, že na formuláři jsou nevalidní data. Informaci o validitě si přece chceme přečíst z ViewModelu. Nejen kvůli tomu odpadá i další možnost: generovat validátory podle metadat ViewModelu. Ani to nezkoušejte, nejde to. Jak zjistil Miro, Binding je jen MarkupExtension, který vytvoří BindingExpression, který se teprve stará o update hodnot, jenže většina jeho důležitých vlastností je internal.
Jediným mně známým řešením je proto deklarovat ve ViewModelu property Age jako string a provádět konverzi až tam. Když se konverze povede, zapíšete property do Modelu, kde už je typu int a kde se provede business validace. Je to víc práce ale zase má ViewModel informaci o tom, jestli je formulář validní. Implementace je popsaná v článku Using a ViewModel to Provide Meaningful Validation Error Messages.
Aby to nebylo tak lehké, představte si že chcete z ViewModelu vrátit hlášku "Věk musí být mezi {0} a {1}". Obstarožní interface IDataErrorInfo umožňuje vracet pouze string, takže ve ViewModelu provedeme String.Format a vrátíme už hotový text. Jenže to komplikuje lokalizaci. Kvůli přepnutí jazyka za běhu bychom potřebovali vracet klíč do resourců a pole parametrů, aby lokalizátor mohl aktualizovat hlášku. Ideální by tedy bylo napsat si vlastní interface, lepší a hezčí než IDataErrorInfo (to by věru nebylo těžké), protože do ValidationError objektu můžeme ukládat daleko více informací než jen string. Pomocí Reflectoru kolega Miro zjistil, že Binding při nastavení ValidatesOnDataErrors=true vkládá do pole validátorů objekt DataErrorValidationRule. Kdybychom tedy napsali vlastní ValidationRule, mohli bychom přečíst errory pomocí vlastního interface a vytvořit takový ValidationError objekt, který by byl lokalizátor schopen za běhu překládat. Jenže opět narážíme na to, že property BindingExpression.ItemSource, která obsahuje zdroj data bindingu (ViewModel), je internal! Zbývá nám tedy buď oželit okamžitou lokalizaci validačních hlášek, přinutit všechny BindingExpression objekty provést refresh, nebo si do stringu serializovat template s parametry a v lokalizátoru je deserializovat, vše mi přijde ošklivé.
Undo/Redo
WPF má sice vestavěnou podporu pro undo/redo v TextBoxu, ale to nám samozřejmě nestačí, potřebujeme undo/redo v celé aplikaci (nebo lépe, v celém editovaném projektu, který tvoří undo scope - projektů můžeme mít otevřených víc naráz). Zde se nabízí klasické řešení pomocí UndoItem objektu a undo a redo stacku, popsané třeba zde. Je ovšem nutné vyřešit otázku jak nalézt undo scope (projekt), když naše ViewModel objekty tvoří košatě rozvětvený strom.
Také musíme implementovat undo transakce. Kvůli tomu, aby bylo uživatelské rozhraní živější, jsme totiž ve všech Binding deklaracích nastavili UpdateSourceTrigger=PropertyChanged. Díky tomu je uživatelův vstup okamžitě validován, ale protože se přes ViewModel posílá každý jednotlivý znak který uživatel zadá do TextBoxu, bylo by undo dost nepoužitelné. Potřebujeme tedy zahájit undo transakci při vstupu do TextBoxu a ukončit ji při jeho opuštění (nebo stisku enter). To nás nejen odsuzuje k handlování událostí nebo vlastnímu TextBoxu, ale má to i jeden háček: Při kliknutí myší do menu nebo na toolbar TextBox neztratí focus. Tím pádem se při Save/Load neukončí undo transakce a nezahájí se nová, na což je potřeba myslet a modelovat root undo scope (projekt) tak, aby ho bylo možné při Load celý zahodit a vytvořit nový (i pak si ale TextBox drží focus a nezahájí novou transakci).
Závěr
Jak je vidět, komplexitu dobré aplikace není radno podceňovat ani s tak vyspělým frameworkem, jako je WPF. Pokud narazíte na další problémy nebo na jiná řešení, dejte vědět.