<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">nblog</title><subtitle type="html">Inżynieria oprogramowania - programowanie ekstremalne (XP), programowanie sterowane testami (TDD), wzorce projektowe (DPa), zasady projektowe (DPr) itd...</subtitle><id>http://zine.net.pl/blogs/nuwanda/atom.aspx</id><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/default.aspx" /><generator uri="http://communityserver.org" version="2.1.61129.2">Community Server</generator><updated>2008-03-03T21:07:00Z</updated><link rel="self" href="http://feeds.feedburner.com/nuwanda/nblog" type="application/atom+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><entry><title>VS2010 – block selection</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2009/05/26/vs2010-block-selection.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2009/05/26/vs2010-block-selection.aspx</id><published>2009-05-26T14:35:00Z</published><updated>2009-05-26T14:35:00Z</updated><content type="html">&lt;p&gt;Na &lt;a href="http://blogs.msdn.com/vseditor/"&gt;blogu&lt;/a&gt;, który dotyczy samego edytora w Visual Studio natknąłem się na ciekawy wpis o zaznaczaniu bloków, którego co prawda nie ma w becie pierwszej wydanej niedawno, ale podobno ma się znaleźć w wersji finalnej. Zapowiada się obiecująco.&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  
&lt;embed src="http://images.video.msn.com/flash/soapbox1_1.swf" id="na161q6d" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" pluginspage="http://macromedia.com/go/getflashplayer" flashvars="c=v&amp;amp;v=3e57917d-1b92-4188-b898-25a1d64a408e&amp;amp;ifs=true&amp;amp;fr=shared&amp;amp;mkt=en-US" width="432" height="364"&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=3640" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Visual Studio" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Visual+Studio/default.aspx" /></entry><entry><title>Na CodeCampie byłem, sesję zrobiłem.</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2009/05/23/na-codecampie-bylem-sesje-zrobilem.aspx" /><link rel="enclosure" type="application/octet-stream" length="327379" href="http://zine.net.pl/blogs/nuwanda/attachment/3611.ashx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2009/05/23/na-codecampie-bylem-sesje-zrobilem.aspx</id><published>2009-05-23T20:43:00Z</published><updated>2009-05-23T20:43:00Z</updated><content type="html">&lt;p&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/ccwawa09_400x140_15022829.png"&gt;&lt;img style="border:0px none;display:block;float:none;margin-left:auto;margin-right:auto;" title="ccwawa09_400x140" alt="ccwawa09_400x140" src="http://zine.net.pl/blogs/nuwanda/ccwawa09_400x140_thumb_1668DBAC.png" width="244" border="0" height="88"&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Dzisiejszy dzień spędziłem na konferencji Code Camp w Warszawie. Po raz pierwszy byłem w roli prelegenta. Poprzeczka została ustawiona bardzo wysoko już na pierwszej sesji, którą prowadził Tomasz Kopacz.&lt;/p&gt;  &lt;p&gt;Ja miałem przyjemność poprowadzić trzecią z kolei sesję. Zaprezentowałem publiczności projekt, który od jakiegoś czasu rozwijam. Jest to wtyczka do ReSharpera, o której pisałem w &lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2009/03/20/wyjatkowe-wydanie-exceptional-release.aspx"&gt;poprzedniej notce&lt;/a&gt;. Miałem w końcu okazję powiedzieć nieco więcej o samej idei wyjątków kontrolowanych, którą za pośrednictwem przygotowanej wtyczki wprowadzam do języka C#. Było trochę o historii, trochę o problemach rozwiązań już istniejących. Z sesji jestem zadowolony. Ocenę pozostawiam uczestnikom.&lt;/p&gt;  &lt;p&gt;Tuż po moim wystąpieniu na blogu Mariusza Koprowskiego, mojego kolegi z Łódzkiej Grupy Profesjonalistów IT &amp;amp; .Net, pojawiła się kompozycja zdjęć wykonana w technologii Photosynth. Można ją obejrzeć &lt;a href="http://www.koprowski.pro/post/2009/05/23/CodeCamp-Warszawa-prawie-na-zywo.aspx"&gt;tu&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Wielkie podziękowania należą się organizatorom. Konferencja była w moim odczuciu bardzo udana.&lt;/p&gt;  &lt;p&gt;Dla zainteresowanych jeszcze raz podaję stronę projektu: &lt;a title="http://exceptionalplugin.codeplex.com/" href="http://exceptionalplugin.codeplex.com/"&gt;http://exceptionalplugin.codeplex.com/&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;W załączniku do notki znajdują się też slajdy.&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=3611" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Społeczności" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Spo_4201_eczno_5B01_ci/default.aspx" /><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /><category term="Wtyczki" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wtyczki/default.aspx" /><category term="Exceptional" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Exceptional/default.aspx" /><category term="codecamp" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/codecamp/default.aspx" /></entry><entry><title>Wyjątkowe wydanie (exceptional release)</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2009/03/20/wyjatkowe-wydanie-exceptional-release.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2009/03/20/wyjatkowe-wydanie-exceptional-release.aspx</id><published>2009-03-20T16:55:00Z</published><updated>2009-03-20T16:55:00Z</updated><content type="html">&lt;p&gt;Jak można było się domyślić moje zainteresowanie ReSharperem oraz dodatkami do niego nie wzięło się znikąd. Jakiś czas temu wpadła mi do głowy myśl, aby napisać dodatek, który pomagałby mi w pisaniu niezawodnego kodu. &lt;/p&gt;  &lt;p&gt;Na co dzień pracuję przy dużym projekcie, składającym się z wielu modułów. Pisząc własny moduł często zmuszony jestem korzystać z API innych modułów. Wszystko jest pięknie, dopóki nie zaczynają się pojawiać tzw. unhandled exceptions. W Javie mamy checked exceptions. Dzieki nim mamy pewność, ze żadnego wyjątku nie przegapiliśmy. Stwierdziłem, że R# będzie dobrą podstawą do zaimplementowania takiego mechanizmu dla C#. Co więcej pozwoli zaimplementować także rozwiązania pojawiających się problemów.&lt;/p&gt;  &lt;p&gt;W moim zamyśle do deklaracji rzucanych wyjątków służy dokumentacja xml, która dostarcza znacznika &amp;lt;exception /&amp;gt;. W nim możemy udokumentować pojedynczy wyjątek rzucany z dokumentowanego elementu. Zadaniem dodatku jest przeanalizowanie wyjątków wyrzucanych z tymi udokumentowanymi. Na tym algorytmie bazuje pierwsza wersja, którą właśnie udostępniłem.&lt;/p&gt;  &lt;p&gt;Zapraszam na stronę projektu, gdzie znajdują się szczegóły.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://exceptionalplugin.codeplex.com/"&gt;http://exceptionalplugin.codeplex.com/&lt;/a&gt;&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=3133" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="C#" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/C_2300_/default.aspx" /><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /><category term="Wtyczki" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wtyczki/default.aspx" /><category term="Exceptional" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Exceptional/default.aspx" /></entry><entry><title>Wtyczki do ReSharper 4.5 – Odc. 4 – R#4.5 Beta i errata do trzeciego odcinka</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2009/03/17/wtyczki-do-resharper-4-5-odc-4-r-4-5-beta-i-errata-do-trzeciego-odcinka.aspx" /><link rel="enclosure" type="application/octet-stream" length="4464" href="http://zine.net.pl/blogs/nuwanda/attachment/3104.ashx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2009/03/17/wtyczki-do-resharper-4-5-odc-4-r-4-5-beta-i-errata-do-trzeciego-odcinka.aspx</id><published>2009-03-17T13:13:00Z</published><updated>2009-03-17T13:13:00Z</updated><content type="html">&lt;p align="justify"&gt;Dokładnie tego samego dnia, w którym opublikowałem &lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2009/03/13/wtyczki-do-resharper-4-x-odc-3-analiza-kodu-i-podswietlanie.aspx"&gt;trzecią część&lt;/a&gt; tej serii ukazała się oficjalna wersja &lt;a href="http://blogs.jetbrains.com/dotnet/2009/03/resharper-45-beta-released/"&gt;beta ReSharpera 4.5&lt;/a&gt;. Z jednej strony ucieszyłem się, że w końcu będę mógł zobaczyć kolejne wydanie (nocnych buildów nie instalowałem z racji ich małej stabilności). Z drugiej jednak strony trochę się zmartwiłem, bo prawdopodobnie odcinek trzeci właśnie mocno stracił na aktualności. Na blogu &lt;a href="http://resharper.blogspot.com/2009/03/resharper-45-what-were-we-doing.html"&gt;ReSharper Horizons&lt;/a&gt; przeczytałem, że &lt;/p&gt;  &lt;p align="justify"&gt;&lt;i&gt;„… we decided that performance, memory usage and building a foundation for further improvements were of a higher priority than API compatibility. Without that massive code cleanup we would have been spending more and more resources to make our product better for you, and without much success. Please accept our apologies, plug-in developers!”&lt;/i&gt;. &lt;/p&gt;  &lt;p align="justify"&gt;Nie będę na zaistniałą sytuację narzekał, bo zgadzam się z ich założeniami. Ciekawiło mnie jednak jak bardzo moja wiedza na temat wewnętrznej struktury R# się przedawniła.&lt;/p&gt;  &lt;p align="justify"&gt;Odnosząc się do poprzedniego odcinka zmian na szczęście nie będzie wiele. Zacznijmy tak jak poprzednio od implementacji Deamon Stage.&lt;/p&gt;  &lt;div class="wlWriterEditableSmartContent" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:fdd23293-3fa5-4d72-894c-e9f203db39c6" style="margin:0px;padding:0px;display:inline;float:none;"&gt;&lt;pre class="brush: csharp; gutter: true; first-line: 1; tab-size: 4; toolbar: true;"&gt;[DaemonStage]&lt;br&gt;public class SharpedDaemonStage : CSharpDaemonStageBase&lt;br&gt;{&lt;br&gt;    public override IDaemonStageProcess CreateProcess(IDaemonProcess process, DaemonProcessKind processKind)&lt;br&gt;    {&lt;br&gt;        if (process == null) return null;&lt;br&gt;        if (IsSupported(process.ProjectFile) == false) return null;&lt;br&gt;&lt;br&gt;        return new SharpedDaemonStageProcess(process);&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public override ErrorStripeRequest NeedsErrorStripe(IProjectFile projectFile)&lt;br&gt;    {&lt;br&gt;        return ErrorStripeRequest.STRIPE_AND_ERRORS;&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p align="justify"&gt;Jedyna zmiana, jaką można zauważyć, to drugi parametr metody &lt;b&gt;CreateProcess&lt;/b&gt;. Z tego co wyczytałem w &lt;a href="http://www.jetbrains.net/confluence/display/ReSharper/ReSharper+PlugIn+4.0%284.1%29+to+4.5+Migration+Guide"&gt;krótkim opisie zmian&lt;/a&gt; parametr ten może posłużyć np. do wyłączenia naszego etapu z procesu analizy całego solution, ale w większości przypadków można go zignorować.&lt;/p&gt;

&lt;p align="justify"&gt;Drugim elementem był Daemon Stage Process.&lt;/p&gt;

&lt;div class="wlWriterEditableSmartContent" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:36a0afd0-3ec5-4d3c-a4cf-7e49b36a0fa7" style="margin:0px;padding:0px;display:inline;float:none;"&gt;&lt;pre class="brush: csharp; gutter: true; first-line: 1; tab-size: 4; toolbar: true;"&gt;internal class SharpedDaemonStageProcess : CSharpDaemonStageProcessBase&lt;br&gt;{&lt;br&gt;    public SharpedDaemonStageProcess(IDaemonProcess daemonProcess) &lt;br&gt;        : base(daemonProcess)&lt;br&gt;    {&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public override void Execute(System.Action&amp;lt;DaemonStageResult&amp;gt; commiter)&lt;br&gt;    {&lt;br&gt;        HighlightInFile(file =&amp;gt; file.ProcessDescendants(this), commiter); &lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public override void VisitThrowStatement(IThrowStatement throwStatementParam)&lt;br&gt;    {&lt;br&gt;        this.AddHighlighting(new ThrowHighlighting(throwStatementParam));&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p align="justify"&gt;Tu też zmiany są niewielkie. Trochę zmieniło się wywoływanie procesu analizy drzewa pliku. Samo wywołanie metody &lt;b&gt;ProcessDescendants&lt;/b&gt; zostało takie samo. Na podstawie tego, co powiedział mi wuj Reflector to widać refaktoryzację procesu podświetlania. Teraz nie dostajemy bezpośrednio pliku, na którym mamy działać, ale zostało to ukryte w metodzie HighlightInFile, która wywołuje naszego delegata z odpowiednim plikiem.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;div class="wlWriterEditableSmartContent" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:2242ddc5-4d12-4409-ba25-292ba6ba2cf1" style="margin:0px;padding:0px;display:inline;float:none;"&gt;&lt;pre class="brush: csharp; gutter: true; first-line: 1; tab-size: 4; toolbar: true;"&gt;protected void HighlightInFile(Action&amp;lt;ICSharpFile&amp;gt; fileHighlighter, Action&amp;lt;DaemonStageResult&amp;gt; commiter)&lt;br&gt;{&lt;br&gt;    fileHighlighter(this.File);&lt;br&gt;    commiter(new DaemonStageResult(this.myHighlightingInfos));&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p align="justify"&gt;Ostatnim elementem była klasa podświetlenia. Tam też nie dostrzeżemy wielu zmian. 
  &lt;br&gt;

  &lt;br&gt;&lt;/p&gt;

&lt;div class="wlWriterEditableSmartContent" id="scid:DFDE9937-D816-47f4-A306-7B60D5CE5AC0:be543aef-21a7-491d-8547-9f449f4fb55f" style="margin:0px;padding:0px;display:inline;float:none;"&gt;&lt;pre class="brush: csharp; gutter: true; first-line: 1; tab-size: 4; toolbar: true;"&gt;[StaticSeverityHighlighting(Severity.WARNING)]&lt;br&gt;public class ThrowHighlighting : CSharpHighlightingBase, IHighlighting&lt;br&gt;{&lt;br&gt;    private IThrowStatement ThrowStatement { get; set; }&lt;br&gt;&lt;br&gt;    public ThrowHighlighting(IThrowStatement throwStatement)&lt;br&gt;    {&lt;br&gt;        ThrowStatement = throwStatement;&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public override bool IsValid()&lt;br&gt;    {&lt;br&gt;        return true;&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public override DocumentRange Range&lt;br&gt;    {&lt;br&gt;        get { return this.ThrowStatement.ToTreeNode().ThrowKeyword.GetDocumentRange(); }&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public string ErrorStripeToolTip&lt;br&gt;    {&lt;br&gt;        get { return "This is throw statement! (on a strip)"; }&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public int NavigationOffsetPatch&lt;br&gt;    {&lt;br&gt;        get { return 0; }&lt;br&gt;    }&lt;br&gt;&lt;br&gt;    public string ToolTip&lt;br&gt;    {&lt;br&gt;        get { return "This is throw statement! (tool tip)"; }&lt;br&gt;    }&lt;br&gt;}&lt;br&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p align="justify"&gt;Jedyną różnicą jest fakt, że zostaliśmy zobligowani do zaimplementowania jeszcze jednej metody – &lt;b&gt;IsValid&lt;/b&gt;. Z tego, co się orientuję to za pomocą wyniku działania tej metody możemy powiedzieć R#owi, że nasze podświetlenie jest z jakichś powodów nieaktualne, niepoprawne.&lt;/p&gt;

&lt;p align="justify"&gt;Jak widać zmian w naszym przykładzie nie było zbyt wiele, ale można się było tego spodziewać, jako że nie wykorzystujemy jakoś bardzo najważniejszych elementów R#. To dopiero przed nami. Z bezpośrednio zauważalnych zmian widać, ze liczba zestawów (ang. assembly) chyba się podwoiła. Poza tym rzeczywiście przerobili część podstawowych elementów systemu, ale teraz nie ma sensu wchodzić w szczegóły. JetBrains udostępniło &lt;a href="http://www.jetbrains.net/confluence/display/ReSharper/ReSharper+PlugIn+4.0%284.1%29+to+4.5+Migration+Guide"&gt;migration guide&lt;/a&gt;, którym pokrótce omówili najważniejsze zmiany.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;
    &lt;div align="justify"&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/23/wtyczki-do-resharper-4-x-odc-1-wprowadzenie.aspx"&gt;Wprowadzenie&lt;/a&gt;&lt;/div&gt;
  &lt;/li&gt;&lt;li&gt;
    &lt;div align="justify"&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/29/wtyczki-do-resharper-4-x-odc-2-przygotowanie-rodowiska.aspx"&gt;Przygotowanie środowiska&lt;/a&gt;&lt;/div&gt;
  &lt;/li&gt;&lt;li&gt;
    &lt;div align="justify"&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2009/03/13/wtyczki-do-resharper-4-x-odc-3-analiza-kodu-i-podswietlanie.aspx"&gt;Analiza kodu i podświetlanie&lt;br&gt;&lt;/a&gt;&lt;/div&gt;
  &lt;/li&gt;&lt;li&gt;&lt;b&gt;R#4.5 Beta i errata do trzeciego odcinka (ten tekst)&lt;/b&gt;&lt;br&gt;
    &lt;/li&gt;&lt;li&gt;&lt;div align="justify"&gt;...&lt;/div&gt;
  &lt;/li&gt;&lt;/ol&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=3104" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /><category term="Wtyczki" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wtyczki/default.aspx" /></entry><entry><title>Wtyczki do ReSharper 4.x – Odc. 3 – Analiza kodu i podświetlanie</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2009/03/13/wtyczki-do-resharper-4-x-odc-3-analiza-kodu-i-podswietlanie.aspx" /><link rel="enclosure" type="application/octet-stream" length="4888" href="http://zine.net.pl/blogs/nuwanda/attachment/3063.ashx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2009/03/13/wtyczki-do-resharper-4-x-odc-3-analiza-kodu-i-podswietlanie.aspx</id><published>2009-03-13T09:42:00Z</published><updated>2009-03-13T09:42:00Z</updated><content type="html">&lt;a href="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.deamon.running_193DCA96.png"&gt;&lt;img title="zine.net.pl.resharper.plugins.03.deamon.running" style="border-width:0px;margin:10px;display:inline;" alt="zine.net.pl.resharper.plugins.03.deamon.running" src="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.deamon.running_thumb_7F0282D3.png" width="72" align="left" border="0" height="116"&gt;&lt;/a&gt;   &lt;p align="justify"&gt;Jednym z głównych zadań ReSharpera jest analiza kodu i dostarczanie sugestii oraz rozwiązań dla znalezionych problemów. Otwierając plik z kodem naszego programu widzimy jak R# go analizuje - widoczna jest taka strzałka na pasku (ang. stripe) jak po lewej. W tym momencie działa w tle osobny wątek, który nazywa się ‘Daemon’. To w tym wątku wykonywane są wszystkie analizy. Proces analizowania pliku składa się z etapów (ang. stage). R# pozwala na dołączanie własnych etapów do tego procesu i w tym odcinku pokażę właśnie jak to zrobić.&amp;nbsp; &lt;/p&gt;  &lt;p align="justify"&gt;Zadanie jest proste i sprowadza się do zaimplementowania dwóch klas – Daemon Stage oraz Daemon Stage Process. Pierwsza z nich jest odpowiedzialna za utworzenie drugiej, mówiąc wprost pierwsza jest punktem wejścia, dzięki któremu R# będzie mógł dołączyć nasz etap do całej analizy. Zacznijmy od implementacji Daemon Stage.&lt;/p&gt;  &lt;div align="justify"&gt;   &lt;div style="border:1px solid gray;margin:20px 0px 10px;padding:4px;overflow:auto;font-size:8pt;width:97.5%;cursor:text;max-height:200px;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;     &lt;div style="border-style:none;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;       &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   1:&lt;/span&gt; [DaemonStage]&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   2:&lt;/span&gt; &lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; SharpedDaemonStage : CSharpDaemonStageBase&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   3:&lt;/span&gt; {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   4:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;override&lt;/span&gt; IDaemonStageProcess CreateProcess(IDaemonProcess process)&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   5:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   6:&lt;/span&gt;         &lt;span&gt;if&lt;/span&gt; (process == &lt;span&gt;null&lt;/span&gt;) &lt;span&gt;return&lt;/span&gt; &lt;span&gt;null&lt;/span&gt;;&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   7:&lt;/span&gt;         &lt;span&gt;if&lt;/span&gt; (IsSupported(process.ProjectFile) == &lt;span&gt;false&lt;/span&gt;) &lt;span&gt;return&lt;/span&gt; &lt;span&gt;null&lt;/span&gt;;&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   8:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   9:&lt;/span&gt;         &lt;span&gt;return&lt;/span&gt; &lt;span&gt;new&lt;/span&gt; SharpedDaemonStageProcess(process);&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  10:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  11:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  12:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;override&lt;/span&gt; ErrorStripeRequest NeedsErrorStripe(IProjectFile projectFile)&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  13:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  14:&lt;/span&gt;         &lt;span&gt;return&lt;/span&gt; ErrorStripeRequest.STRIPE_AND_ERRORS;&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  15:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  16:&lt;/span&gt; }&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p align="justify"&gt;Poprawna implementacja Daemon Stage wymaga dwóch elementów. Po pierwsze zaimplementować należy interfejs &lt;b&gt;IDaemonStage&lt;/b&gt; oraz klasę oznaczyć atrybutem &lt;b&gt;DaemonStageAttribute&lt;/b&gt;. W powyższym przykładzie poszliśmy trochę dalej i wykorzystaliśmy klasę bazową &lt;b&gt;CSharpDaemonStageBase&lt;/b&gt;, która dostarcza nam metodę pomocniczą &lt;b&gt;IsSupported&lt;/b&gt;, sprawdzającą czy analizowany plik jest plikiem C# (ciekawskich odsyłam do Reflectora). Tak na marginesie to bez Reflectora praca nad jakąkolwiek wtyczką byłaby niemożliwa i jest to narzędzie, z którego korzystam przez 80% pisania wtyczki. Wracając do powyższej implementacji jasnym jest już chyba, że zadaniem metody &lt;b&gt;CreateProcess&lt;/b&gt; jest dostarczenie instancji naszego procesu, który będzie analizował plik. Zaimplementujemy go za chwilę.&lt;/p&gt;

&lt;p align="justify"&gt;Zatrzymajmy się jeszcze przy drugiej metodzie – &lt;b&gt;NeedsErrorStripe&lt;/b&gt;. Jej zadaniem jest określenie podstawowych właściwości procesu. Do wyboru mamy trzy wartości: &lt;b&gt;None&lt;/b&gt;, &lt;b&gt;Stripe&lt;/b&gt; oraz &lt;b&gt;Stripe_and_Errors&lt;/b&gt;. Pierwsza z nich oznacza, ze proces w ogóle nie będzie korzystał z ***. Druga&amp;nbsp; – pasek jest potrzebny, ale proces nie będzie produkował żadnych ostrzeżeń i błędów. Natomiast ostatnia – pasek jest potrzebny i proces będzie produkował ostrzeżenia i błędy.&lt;/p&gt;

&lt;p align="justify"&gt;Teraz zaimplementujemy sam proces. &lt;/p&gt;

&lt;div align="justify"&gt;
  &lt;div style="border:1px solid gray;margin:20px 0px 10px;padding:4px;overflow:auto;font-size:8pt;width:97.5%;cursor:text;max-height:200px;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;
    &lt;div style="border-style:none;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;
      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   1:&lt;/span&gt; &lt;span&gt;internal&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; SharpedDaemonStageProcess : CSharpDaemonStageProcessBase&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   2:&lt;/span&gt; {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   3:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; SharpedDaemonStageProcess(IDaemonProcess daemonProcess) &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   4:&lt;/span&gt;         : &lt;span&gt;base&lt;/span&gt;(daemonProcess)&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   5:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   6:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   7:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   8:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;override&lt;/span&gt; &lt;span&gt;void&lt;/span&gt; ProcessFile(ICSharpFile file)&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   9:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  10:&lt;/span&gt;         file.ProcessDescendants(&lt;span&gt;this&lt;/span&gt;);&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  11:&lt;/span&gt;         &lt;span&gt;this&lt;/span&gt;.FullyRehighlighted = &lt;span&gt;true&lt;/span&gt;;&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  12:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  13:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  14:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;override&lt;/span&gt; &lt;span&gt;void&lt;/span&gt; VisitThrowStatement(IThrowStatement throwStatementParam)&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  15:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  16:&lt;/span&gt;         &lt;span&gt;this&lt;/span&gt;.AddHighlighting(&lt;span&gt;new&lt;/span&gt; ThrowHighlighting(throwStatementParam));&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  17:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  18:&lt;/span&gt; }&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p align="justify"&gt;Ograniczyłem się tu do zaprezentowania samego mechanizmu podświetlania, nie zaś analizy. Jedynym zadaniem powyższej implementacji jest podkreślenie wszystkich słów kluczowych throw.&lt;/p&gt;

&lt;p align="justify"&gt;Zanim jednak przejdę do omówienia tej implementacji chciałbym pokrótce omówić to jak R# działa (przynajmniej jak ja to rozumiem). Otóż z tego co wiem, to R# posiada swój własny parser języka C# (to tłumaczy trochę czas jaki potrzebują na adaptację do nowej wersji języka). Wynikiem działania tego parsera jest drzewo PSI, które możemy następnie analizować. Proces analizy w R# został oparty o wzorzec Wizytatora (ang. Visitor)&lt;a href="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.process.base_57C8399E.png"&gt;&lt;img title="zine.net.pl.resharper.plugins.03.process.base" style="border-width:0px;margin:10px;display:inline;" alt="zine.net.pl.resharper.plugins.03.process.base" src="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.process.base_thumb_5351B8D7.png" width="345" align="right" border="0" height="71"&gt;&lt;/a&gt;. Dlatego chcąc przeanalizować strukturę drzewa PSI należy uruchomić własnego wizytatora na tym drzewie. W powyższym przykładzie dziedziczymy po klasie &lt;b&gt;CSharpDaemonStageProcessBase&lt;/b&gt;. Jest to klasa bazowa dla etapów działających dla języka C#. Na obrazku po pawej mamy hierarchię reprezentującą tę klasę. Jak łatwo zauważyć dziedziczy ona po klasie bazowej ElementVisitor, zatem jest wizytatorem elementów drzewa (każdy węzeł w drzewie implementuje interfejs &lt;b&gt;IElement&lt;/b&gt;). Następnie mamy &lt;b&gt;IDaemonStageProcess&lt;/b&gt;, a więc będzie też etapem. Nawiązując jeszcze do klasy &lt;b&gt;ElementVisitor&lt;/b&gt; trzeba zaznaczyć, że definiuje ona szereg metod pozwalających odwiedzać poszczególne elementy drzewa. &lt;img title="zine.net.pl.resharper.plugins.03.element.visitor" style="border-width:0px;margin:10px auto;display:block;float:none;" alt="zine.net.pl.resharper.plugins.03.element.visitor" src="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.element.visitor_thumb_52E585E2.png" width="664" border="0" height="115"&gt;&lt;/p&gt;

&lt;p align="justify"&gt;Analizę zaczynamy wywołując metodę &lt;b&gt;ProcessDescendants&lt;/b&gt; na przekazanym nam pliku. W trakcie analizy nasz etap może dołączać swoje podświetlenia za pomocą metody &lt;b&gt;AddHighlighting&lt;/b&gt;. Po zakończeniu etap przekazuje wszystkie podświetlenia oraz zakres dokumentu jaki został przeanalizowany (robi to za nas klasa bazowa). Zwykle jest to cały plik, dlatego także w naszym przypadku ustawiamy właściwość &lt;b&gt;FullyRehighlighted&lt;/b&gt; na true. Możliwe są też bardziej zaawansowane scenariusze, w których analizie może podlegać tylko pewien fragment drzewa. Pozwala to uzyskać lepszą wydajność, jedka ja jeszcze nie znam szczegółów. &lt;a href="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.throw.statement_787424A5.png"&gt;&lt;img title="zine.net.pl.resharper.plugins.03.throw.statement" style="border-width:0px;margin:10px;display:inline;" alt="zine.net.pl.resharper.plugins.03.throw.statement" src="http://zine.net.pl/blogs/nuwanda/zine.net.pl.resharper.plugins.03.throw.statement_thumb_2831CD25.png" width="381" align="right" border="0" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p align="justify"&gt;Przed przystąpieniem do analizy drzewa PSI należy poznać jego strukturę. Jest to drzewo obiektowo reprezentujące strukturę kodu, zatem każdemu elementowi odpowiada pewna klasa. W naszym przykładzie chcemy analizować słowa kluczowe throw, a więc będziemy analizowali throw statements. Obrazek obok prezentuje tą właśnie klasę oraz całą hierarchię dziedziczenia. W tym miejscu należy podkreślić dwie ważne prawidłowości, które dotyczą wszystkich elementów. Każdy z nich implementuje dwa interfejsy. W naszym przypadku będą to &lt;b&gt;IThrowStatement&lt;/b&gt; oraz &lt;b&gt;IThrowStatementNode&lt;/b&gt;. Pierwszy z nich reprezentuje logiczny element, w tym przypadku rzucenie wyjątku za pomocą słowa kluczowego throw. Drugi z nich reprezentuje ten sam obiekt, ale w realiach drzewa PSI. Co więcej zauważmy, że &lt;b&gt;IThrowStatementNode&lt;/b&gt; dziedziczy po &lt;b&gt;IThrowStatement&lt;/b&gt;. Dla mnie na początku było to bardzo mylące i nie do końca łapałem strukturę. Szczególnie, gdy implementując wizytatora dostajemy np. interfejs &lt;b&gt;IThrowStatement&lt;/b&gt; i patrząc na jego zawartość poprzez intellisense nie mamy elementów, których byśmy się spodziewali. Na szczęście każdy tego typu element możemy rzutować na odpowiadający mu typ &lt;b&gt;*Node&lt;/b&gt;, albo najzwyczajniej wywołać na nim metodę &lt;b&gt;ToTreeNode&lt;/b&gt;.&lt;/p&gt;

&lt;p align="justify"&gt;O ile interfejs logiczny dostarcza nam elementów składowych np. dla throw mamy właściwość &lt;b&gt;Exception&lt;/b&gt; dającą nam dostęp do wyrażenia reprezentującego rzucany wyjątek, o tyle interfejs drzewkowy daje nam dostęp do poszczególnych elementów takich jak słowo kluczowe throw, wyjątek czy średnik.&lt;/p&gt;

&lt;p align="justify"&gt;Wracając do naszego przykładu, w którym podkreślamy słowa kluczowe throw widać, że implementujemy metodę &lt;b&gt;VisitThrowStatement&lt;/b&gt;. Ta metoda zostanie wywołana dla każdego słowa kluczowego throw znajdującego się w analizowanym pliku. Nie przeprowadzamy tu żadnej analizy, po prostu dodajemy podświetlenie.&lt;/p&gt;

&lt;p align="justify"&gt;Podświetlenia (ang. highlightings) definiowane są jako dedykowane klasy dziedziczące po &lt;b&gt;CSharpHighlightingBase&lt;/b&gt; i implementujące interfejs &lt;b&gt;IHighlighting&lt;/b&gt;. Poniżej przykładowa implementacja.&lt;/p&gt;

&lt;div align="justify"&gt;
  &lt;div style="border:1px solid gray;margin:20px 0px 10px;padding:4px;overflow:auto;font-size:8pt;width:97.5%;cursor:text;max-height:200px;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;
    &lt;div style="border-style:none;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;
      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   1:&lt;/span&gt; [StaticSeverityHighlighting(Severity.WARNING)]&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   2:&lt;/span&gt; &lt;span&gt;public&lt;/span&gt; &lt;span&gt;class&lt;/span&gt; ThrowHighlighting : CSharpHighlightingBase, IHighlighting&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   3:&lt;/span&gt; {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   4:&lt;/span&gt;     &lt;span&gt;private&lt;/span&gt; IThrowStatement ThrowStatement { get; set; }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   5:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   6:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; ThrowHighlighting(IThrowStatement throwStatement)&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   7:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;   8:&lt;/span&gt;         ThrowStatement = throwStatement;&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;   9:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  10:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  11:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;override&lt;/span&gt; DocumentRange Range&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  12:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  13:&lt;/span&gt;         get { &lt;span&gt;return&lt;/span&gt; &lt;span&gt;this&lt;/span&gt;.ThrowStatement.ToTreeNode().ThrowKeyword.GetDocumentRange(); }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  14:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  15:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  16:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;string&lt;/span&gt; ErrorStripeToolTip&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  17:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  18:&lt;/span&gt;         get { &lt;span&gt;return&lt;/span&gt; &lt;span&gt;"This is throw statement! (on a strip)"&lt;/span&gt;; }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  19:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  20:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  21:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;int&lt;/span&gt; NavigationOffsetPatch&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  22:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  23:&lt;/span&gt;         get { &lt;span&gt;return&lt;/span&gt; 0; }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  24:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  25:&lt;/span&gt;&amp;nbsp; &lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  26:&lt;/span&gt;     &lt;span&gt;public&lt;/span&gt; &lt;span&gt;string&lt;/span&gt; ToolTip&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  27:&lt;/span&gt;     {&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  28:&lt;/span&gt;         get { &lt;span&gt;return&lt;/span&gt; &lt;span&gt;"This is throw statement! (tool tip)"&lt;/span&gt;; }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;background-color:white;"&gt;&lt;span&gt;  29:&lt;/span&gt;     }&lt;/pre&gt;

      &lt;pre style="border-style:none;margin:0em;padding:0px;overflow:visible;font-size:8pt;width:100%;color:black;line-height:12pt;font-family:consolas,'Courier New',courier,monospace;"&gt;&lt;span&gt;  30:&lt;/span&gt; }&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p align="justify"&gt;Przeanalizujmy teraz ten przykład. W konstruktorze przyjmujemy &lt;b&gt;IThrowStatement&lt;/b&gt; bo to będzie dla nas źródło danych. Następnie z klasy bazowej mamy do zaimplementowania abstrakcyjną właściwość &lt;b&gt;Range&lt;/b&gt;. Właściowść ta powinna zwrócić obiekty typu &lt;b&gt;DocumentRange&lt;/b&gt; reprezentujący zakres na drzewie, który powinien zostać podświetlony. Każdy element drzewa ma metodę &lt;b&gt;GetDocumentRange()&lt;/b&gt;, która zwraca jego zakres na drzewie. Z tego co się orientuję to jest jeszcze zakres tekstowy, odpowiadający pozycji w edytorze i można go pobrać z właściwości &lt;b&gt;TextRange&lt;/b&gt; obiektu &lt;b&gt;DocumentRange&lt;/b&gt;. Jako, że w przykładzie chcemy podświetlać słowa kluczowe throw, &lt;b&gt;DocumentRange&lt;/b&gt; pobieramy z właściwości &lt;b&gt;ThrowKeyword&lt;/b&gt;.&lt;/p&gt;

&lt;p align="justify"&gt;W kolejnym kroku mamy za zadanie zwrócić odpowiedni opis dla naszego podświetlenia. Mamy trzy miejsca, w których opis będzie widoczny. Jest to boczny pasek ze znacznikami i po najechaniu znacznika reprezentującego dane podświetlenie wyświetli się zawartość &lt;b&gt;ErrorStripeToolTip&lt;/b&gt;. Natomiast po najechaniu myszką na podświetlenie lub ustawienie tam kursora spowoduje wyświetlenie właściwości &lt;b&gt;ToolTip&lt;/b&gt;.&lt;/p&gt;

&lt;p align="justify"&gt;Ostatnim elementem i chyba najrzadziej używanym jest &lt;b&gt;NavigationOffsetPatch&lt;/b&gt;. Jest to offset (tekstowy), o który zostanie przesunięty kursor podczas nawigowania do tego podświetlenia. Co ciekawe zauważyłem, że offset ten zostanie zaaplikowany jak będziemy nawigować za pomocą komend np. „Go to next highlighting” natomiast nie jak klikniemy na pasek reprezentujący podświetlenie. Przykładowo w naszym przykładzie zwrócenie wartości 5 zaowocowałoby ustawieniem kursora za słowem kluczowym throw. Ciekawskich zachęcam do eksperymentów.&lt;/p&gt;

&lt;p align="justify"&gt;Ostatnim elementem jest atrybut znajdujący się nad klasą – &lt;b&gt;StaticSeverityHighlighting&lt;/b&gt;. Atrybut ten definiuje nam podświetlenie, którego severity jest statyczne i nie można go modyfikować w opcjach. Konfigurowalnych podświetleń jeszcze nie rozczaiłem.&lt;/p&gt;

&lt;p align="justify"&gt;Takim oto sposobem mamy gotowe rozwiązanie podświetlające wszystkie słowa kluczowe throw. Nie jest to co prawda bardzo użyteczna analiza jednak mam nadzieję, że przykład dostatecznie pokazał Wam mechanizm podświetlania w R#. W złączniku znajduje się cały ten dodatek, który możecie sobie sami uruchomić i trochę z nim poeksperymentować. Pamiętajcie, aby w opcjach debugowania projektu ustawić odpowiednią ścieżkę do pliku z wtyczką tak jak to zostało opisane w części drugiej.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;div align="justify"&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/23/wtyczki-do-resharper-4-x-odc-1-wprowadzenie.aspx"&gt;Wprowadzenie&lt;/a&gt;&lt;/div&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;div align="justify"&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/29/wtyczki-do-resharper-4-x-odc-2-przygotowanie-rodowiska.aspx"&gt;Przygotowanie środowiska&lt;/a&gt;&lt;/div&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;div align="justify"&gt;&lt;strong&gt;Analiza kodu i podświetlanie (ten tekst)&lt;/strong&gt;&lt;/div&gt;
  &lt;/li&gt;

  &lt;li&gt;
    &lt;div align="justify"&gt;...&lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=3063" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /><category term="Wtyczki" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wtyczki/default.aspx" /></entry><entry><title>M-V-P Twoim przyjacielem</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/10/12/m-v-p-twoim-przyjacielem.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/10/12/m-v-p-twoim-przyjacielem.aspx</id><published>2008-10-12T13:48:00Z</published><updated>2008-10-12T13:48:00Z</updated><content type="html">&lt;p&gt;Będąc na studiach zacząłem pracę programisty. Głowę miałem wypakowaną teorią, a w duszy grała chęć zastosowania tego wszystkiego w praktyce. Każdy kto czytał o &lt;a href="http://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional/dp/0201633612"&gt;wzorcach projektowych GoF&lt;/a&gt; wie jak bardzo zmienia ona postrzeganie i jak bardzo zachęca nas do wykorzystywania tychże wzorców w praktyce. Niestety od nadmiaru wzorców głowa boli. Pewne mało skomplikowane aplikacje zupełnie ich nie potrzebują, a wprowadzenie ich tylko niepotrzebnie zaciemnia obraz sytuacji. Zatem wybór wzorca i to, czy rzeczywiście jego zastosowanie ma sens, poprzeć trzeba pewnym uzasadnieniem.&lt;/p&gt;  &lt;p&gt;Zaczynając pracę trafiłem do zespołu, który buduje aplikację w oparciu o Windows Forms, znaczy się to co tygryski lubią najbardziej. Już na samą myśl o WF w głowie zapala mi się lampka - MVP. Wcześniej nie miałem okazji wypróbować go w boju. W małych projektach studenckich stosowałem, ale z rzeczywistych jego możliwości nigdy nie korzystałem. Co więcej, nie do końca zdawałem sobie sprawy z jego potęgi!&lt;/p&gt;  &lt;p&gt;W tekście tym chciałem Wam opowiedzieć o tym jak na co dzień wykorzystuję ten wzorzec i jakie wymierne korzyści dzięki niemu uzyskałem.&lt;/p&gt;  &lt;h3&gt;Grunt to dobry podział&lt;/h3&gt;  &lt;p&gt;Dla wielu implementacja wzorca MVP sprowadza się do tego, że będą mogli podmieniać widoki. Casami ludzie mówiąc o zaletach MVP wskazują na to, że można tak napisać aplikację wykorzystując ten wzorzec, że będzie można wymiennie stosować Windows Forms oraz ASP.Net. Jakoś nie widziałem, żeby się to komuś udało. Nie twierdzę że nie jest to możliwe, ale czy rzeczywiście o to chodzi?&lt;/p&gt;  &lt;p&gt;Spójrzmy może na ten wzorzec z innej strony. Wprowadza on podział na trzy elementy: Model, Widok i Prezentera. Zapytać ktoś może po co taki podział? Czemu nie mogę oprogramować okna w jego klasie? Nie mając perspektyw dalszego rozwoju aplikacji moglibyśmy tak zrobić, jednak praktyka sugeruje, że aplikacja będzie rosła a klient będzie wymyślał nowe problemy do rozwiązania. &lt;/p&gt;  &lt;p&gt;Przede wszystkim oddziel zagadnienia (ang. &lt;a href="http://en.wikipedia.org/wiki/Separation_of_concerns"&gt;Separation of Concerns&lt;/a&gt;, SoC). Wprowadzenie tych trzech elementów nie jest przypadkowe. Widok to reprezentacja wizualna danych – która może mieć wiele postaci np. okno czy strona www. Prezenter to logika reprezentacji danych. Definiuje zachowanie widoku np. reaguje na akcje użytkownika. Na końcu model to dane. Czasami danymi jest pojedynczy obiekt, innym razem może to być zbiór obiektów. Promując zasadę SoC rozdzielamy te zagadnienia umieszczając je w osobnych klasach.&lt;/p&gt;  &lt;h3&gt;Podmiany&lt;/h3&gt;  &lt;p&gt;Dobrze, mamy już jakieś wymierne korzyści, które w dowolnej wielkości aplikacji będą procentować. Zastanówmy się teraz jak z tego wzorca wyciągnąć jeszcze więcej. W pracy niejednokrotnie spotkałem się z problemem ponownego wykorzystania komponentów. Weźmy mały przykład, który z powodzeniem odnajdziecie w wielu rozwiązaniach klasy ERP, CRM lub podobnych. Chodzi mi o listę kontrahentów. Z pozoru problem jest prosty – należy umożliwić zarządzanie kontrahentami w aplikacji. Sprowadza się to do zaimplementowania listy kontrahentów, a także szczegółów konkretnego kontrahenta. Skupmy się jednak na samej liście. Zadanie banalne – mamy tabelę w bazie danych zawierającą dane o kontrahentach. Nasza lista będzie pobierała wszystkie dane z tej tabeli i wyświetlała w tabeli w oknie programu.&lt;/p&gt;  &lt;p&gt;Mija czas i przychodzi następne wymaganie. Należy umożliwić wybór kontrahenta z listy. Sytuacja dotyczy wszystkich dokumentów handlowych aplikacji, w których trzeba wybrać kontrahenta. Oczywiście system musi być spójny więc lista musi wyglądać i zachowywać się identycznie (możliwość dodawania, edycji itd.). Zabieramy się za implementację. Tę samą formatkę wzbogacamy o właściwość &lt;i&gt;IsInSelectionMode&lt;/i&gt;, którą ustawiać będziemy na true, gdy będziemy wyświetlać listę w trybie wyboru. Wewnątrz dodajemy odpowiednie if-y, aby zmienić działanie formatki np. jednym z wymagań jest to, aby &lt;b&gt;dwuklik na wierszu powodował wybór w trybie wyboru, a edycję w trybie zwykłym&lt;/b&gt;. Wszystko śmiga. &lt;/p&gt;  &lt;p&gt;Znów mija trochę czasu. Przychodzi klient i mówi: jak wpiszę tylko część nazwy klienta a w bazie jest więcej niż jeden klient o nazwie zaczynającej się na wpisaną frazę to chcę aby pojawiła się lista klientów zawężona tylko do tych pasujących do frazy. Znów przystępujemy do implementacji. Gdy użytkownik wpisze żądaną frazę strzelamy do bazy i mamy już listę pasujących kontrahentów. Zgodnie z warunkiem, jeżeli jest ich więcej niż jeden otwieramy listę. Sama lista też wymaga pewnych modyfikacji. Mając zestaw danych w ręce (listę pasujących kontrahentów) nie chcemy aby lista sama pobierała dane, tylko chcemy jej podać to co już mamy. Dodajemy kolejne pole i przypisujemy mu zawartość listy kontrahentów zaczynających się od frazy. W samej liście znów dodajemy if-y, tak aby spełnić wymagania. Teraz &lt;b&gt;jeżeli nowo dodane pole nie jest puste nie pobieramy wszystkich kontrahentów z bazy, a jedynie wyświetlamy tych otrzymanych&lt;/b&gt;.&lt;/p&gt;  &lt;p&gt;Spoglądając wstecz zauważymy, że stworzyliśmy coś, co na dłuższą metę może być uciążliwe w utrzymaniu. Nie dość, że wszystkie funkcje dotyczące obsługi listy są w jednej klasie to jeszcze sama logika się skomplikowała, ponieważ wprowadzono warunki if. Każda z komend dotycząca zawartości wyświetlanej listy np. ‘odśwież’ musiała zostać odpowiednio zmodyfikowana, aby zapewnić poprawność, bo przecież nie możemy pobierać danych z bazy danych mając zadaną stałą listę. To prowadzi do większego prawdopodobieństwa popełnienie błędu, albo wprowadzenia błędu przy późniejszych modyfikacjach.&lt;/p&gt;  &lt;p&gt;Co więcej z punktu widzenia programisty, który naszej listy będzie używał, jej API nie do końca jest intuicyjne. Musi wiedzieć, że właściwość IsInSelectMode &lt;b&gt;przełącza listę w tryb wyboru&lt;/b&gt;, a ustawienie pola &lt;i&gt;ExternalDataSource&lt;/i&gt; powoduje &lt;b&gt;zmianę zachowania listy i wyświetlenie tylko zadanego zestawu danych&lt;/b&gt;.&lt;/p&gt;  &lt;p&gt;Rozwiązanie takie tworzy wiele problemów. Nie ma jasno powiedziane &lt;b&gt;gdzie znajduje się źródło danych&lt;/b&gt;. Informacja ta jest rozproszona po kodzie całej kontrolki. &lt;b&gt;Nie ma też jasno określonego dostawcy danych&lt;/b&gt;. Lista w standardowym trybie sama zaczytuje sobie dane, ale w przypadku trybu zawężonej listy, dane pobierane są z zewnątrz.&lt;/p&gt;  &lt;p&gt;Z punktu widzenia utrzymania takiego komponentu mamy kolejny problem. Promując zasadę otwarty-zamknięty (ang. &lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2007/09/02/zasada-otwarty-zamkniety.aspx"&gt;Open-Closed Principle&lt;/a&gt;, OCP) chcielibyśmy, aby dodawanie nowych funkcji mogło się odbyć bez modyfikacji już istniejących klas. W tym wypadku dodanie kolejnego trybu (a nie zdziwiłbym się, gdyby poproszono o coś jeszcze) wiąże się z nieustannym modyfikowaniem tej samej klasy, która szybko przyjmie rozmiary kilku tysięcy linii kodu.&lt;/p&gt;  &lt;p&gt;W tym przypadku i wielu innych, które spotkałem podczas pracy z pomocą przyszedł mi wzorzec MVP i podział wprowadzany przez jego komponenty. Tak jak pisałem wcześniej Prezenter określa nam klasę odpowiedzialną za &lt;b&gt;zachowanie&lt;/b&gt;, a model &lt;b&gt;dostarcza danych&lt;/b&gt;. Z powodzeniem możemy wykorzystać te klasy jako elementy wymienne.&lt;/p&gt;  &lt;p&gt;Zobaczmy jak takie rozwiązanie mogłoby wyglądać. W tekście wytłuściłem elementy opisujące kolejne wymagania. Pierwsza zmiana polegała na zmianie zachowania formatki. Skoro zmienialiśmy zachowanie to w modelu MVP wystarczyłoby wymienić prezentera na takiego, który definiuje odrębne zachowanie. Wystarczyłoby mieć bazowego prezentera, który obsługuje wszystkie wspólne komendy takie jak dodawanie i usuwanie, a dwa dziedziczące po nim definiowały by obsługę dwukliku. Prawda, że proste?&lt;/p&gt;  &lt;p&gt;Drugie wymaganie polegała na zmianie danych wyświetlanych na liście. Dlaczego nie wyodrębnić dwóch modeli. Pierwszy obsługiwałby wersję standardową wczytując wszystkich kontrahentów z bazy danych, a drugi przyjmowałby w konstruktorze gotowy zestaw danych. Prawda, że proste?&lt;/p&gt;  &lt;p&gt;Teraz uruchomienie listy w żądanym trybie sprowadza się do wyboru odpowiedniego prezentera oraz odpowiedniego modelu. Na przykład chcąc uruchomić listę w trybie wyboru z zawężoną listą kontrahentów napisalibyśmy:&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;div class="wlWriterSmartContent" id="scid:57F11A72-B0E5-49c7-9094-E3A15BD5B5E7:cda0370b-ca49-4f53-9c23-af9719c83246" style="margin:0px;padding:0px;display:inline;float:none;width:398px;"&gt;&lt;pre style="overflow:auto;background-color:White;font-family:Microsoft Sans Serif;font-size:12px;"&gt;&lt;div&gt;&lt;span&gt;1&lt;/span&gt; &lt;span&gt;var view &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; CustomerListView();
&lt;/span&gt;&lt;span&gt;2&lt;/span&gt; &lt;span&gt;var model &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; ManualCustomerListModel(matchingCustomers);
&lt;/span&gt;&lt;span&gt;3&lt;/span&gt; &lt;span&gt;var presenter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; CustomerSelectionPresenter(view, model);&lt;/span&gt;&lt;/div&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Oczywiście nie moglibyśmy tego osiągnąć gdyby nie to, że każdy z tych komponentów opisany został dobrze określonym interfejsem. Dzięki temu całą trójkę możemy dowolnie komponować.&lt;/p&gt;

&lt;p&gt;Wspomniałem wcześniej, że pierwsze rozwiązanie w ogóle nie spełnia &lt;i&gt;zasady otwarty-zamknięty&lt;/i&gt;. Zobaczmy jaką sytuację mamy teraz. Każda z wariacji na temat listy kontrahentów zamknięta została w swojej własnej klasie. Zaimplementowanie kolejnego przypadku sprowadza się teraz do rozważenia jakie własności są modyfikowane. Jeżeli modyfikujemy zachowania to należy odpowiednio zaimplementować prezentera. Natomiast jeżeli modyfikujemy wyświetlane dane wprowadzamy nową implementację modelu. Co ważne to to, że nawet przez chwilę nie dotykamy klas już istniejących.&lt;/p&gt;

&lt;h3&gt;Podsumowanie&lt;/h3&gt;

&lt;p&gt;Przedstawiony tutaj problem dotyczący listy kontrahentów jest oczywiście dość prostym przypadkiem. W rzeczywistości spotkałem się z sytuacjami, gdzie zachowanie okna znacznie się różniło od jego podstawowej implementacji. Tak samo było z danymi. Nie implementowałem natomiast nigdy dwóch widoków mających prezentować wspólne dane, ale jak łatwo się domyślić nie powinno to nastręczać problemów przy zachowaniu jednolitego interfejsu.&lt;/p&gt;

&lt;p&gt;Ci, którym nie jest obojętna wysoka jakość systemu zauważą, że implementacja testów jednostkowych dla tak zdefiniowanego systemu, nie powinna być skomplikowana. Projektując w ten sposób sprawiamy, że system jest testowalny.&lt;/p&gt;

&lt;p&gt;Z własnych praktycznych doświadczeń mogę szczerze powiedzieć, że zastosowanie wzorca MVP oszczędziło mi wiele pracy, oszczędza i będzie oszczędzało w przyszłości, gdy przyjdzie mi rozszerzać bądź poprawiać błędy. Mając odpowiedni podział możemy dowolnie manipulować jego podzespołami, wymieniając je wedle uznania i komponując co raz to nowe rozwiązania. Tak zaprojektowany system jest niesamowicie elastyczny, zgodny z zasadą OCP, dzięki czemu daje się łatwo utrzymywać.&lt;/p&gt;

&lt;p&gt;Dobrze określony podział obowiązków pozwala nam szybciej i bardziej mechanicznie podejmować decyzje o tym gdzie dana funkcja powinna się znaleźć. Znając naturę dużych i skomplikowanych systemów wiem, że implementacja jednej formatki może nam urosnąć do kilku tysięcy linii kodu. Dzięki podziałowi możemy tą ilość podzielić.&lt;/p&gt;

&lt;p&gt;Moja implementacja tego wzorca cały czas ewoluuje. Oparłem ją na wzorcach, które zdefiniował &lt;a href="http://martinfowler.com/"&gt;Martin Fowler&lt;/a&gt;. &lt;a href="http://martinfowler.com/eaaDev/PassiveScreen.html"&gt;Passive View&lt;/a&gt; definiuje widok jako reprezentację graficzną, która jest „głupia” jeżeli chodzi o obsługę komend i do tego celu wykorzystuje prezentera. &lt;a href="http://martinfowler.com/eaaDev/SupervisingPresenter.html"&gt;Supervising Controller&lt;/a&gt; określa prezentera jako sterownik, który odbiera komendy od widoku i zawiaduje całą logiką. Moją implementację staram się cały czas ulepszać. Jeżeli jeszcze tego nie robicie to zachęcam Was do eksperymentów i zaadaptowania tego wzorca. Zapewniam, że włożony w to wysiłek na pewno się zwróci. Na początku może się to wydawać niepotrzebnie skomplikowane i na wyrost, ale wraz z wzrostem systemu dostrzeżecie zalety.&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=2179" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Windows Forms" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Windows+Forms/default.aspx" /><category term="Zasady projektowe" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Zasady+projektowe/default.aspx" /><category term="Wzorce projektowe" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wzorce+projektowe/default.aspx" /></entry><entry><title>Wtyczki do ReSharper 4.x – Odc. 2 – Przygotowanie środowiska</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/29/wtyczki-do-resharper-4-x-odc-2-przygotowanie-rodowiska.aspx" /><link rel="enclosure" type="application/octet-stream" length="11137" href="http://zine.net.pl/blogs/nuwanda/attachment/2085.ashx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/09/29/wtyczki-do-resharper-4-x-odc-2-przygotowanie-rodowiska.aspx</id><published>2008-09-29T12:06:00Z</published><updated>2008-09-29T12:06:00Z</updated><content type="html">&lt;p&gt;Przygodę z tworzeniem wtyczek zaczniemy od przygotowania środowiska programistycznego. Przede wszystkim potrzebna jest nam instalacja samego R#, bo to jego biblioteki są dla nas podstawą. Z tego co zauważyłem to R# nie ma osobnych bibliotek w stylu SDK. Tworząc wtyczki będziemy odwoływali się do tych bibliotek, które będą nam potrzebne.&lt;/p&gt;  &lt;p&gt;Wtyczka dla ReSharpera to biblioteka DLL. W jednej bibliotece może znajdować się tylko jeden plugin. R# potrafi współpracować z wtyczkami napisanymi dla .Net 3.5 jak również 2.0. Zacząć należy od utworzenia projektu biblioteki (Windows Class Library). Następnie nowo powstały projekt konfigurujemy, aby móc w prosty sposób testować rozwijaną wtyczkę.&lt;/p&gt;  &lt;p&gt;W opcjach projektu ustawiamy, aby przy wywołaniu debugowania uruchamiał się zewnętrzny program z odpowiednimi parametrami w lini poleceń. Oczywiście tym programem będzie Visual Studio jako że to w nim będzie osadzona nasza wtyczka. Do rejestracji wtyczki w R# na czas debugowania służy przełącznik:&lt;/p&gt;  &lt;p&gt;&lt;b&gt;/ReSharper.Plugin "ścieżka do pliku DLL wtyczki"&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Całość może wyglądać tak: c:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe /ReSharper.Plugin "D:\projekty\ResharperPrugins\SampleResharperPlugin\bin\Debug\SampleResharperPlugin.dll”. Dzięki tym ustawieniom będziemy mogli debugować rozwiązanie w Visual Studio.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/WindowsLiveWriter/WtyczkidoReSharp.2Przygotowanierodowiska_C19A/image_2.png"&gt;&lt;img style="border-width:0px;" alt="image" src="http://zine.net.pl/blogs/nuwanda/WindowsLiveWriter/WtyczkidoReSharp.2Przygotowanierodowiska_C19A/image_thumb.png" width="611" border="0" height="331"&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Rys. 1. Konfiguracja opcji debugowania&lt;/p&gt;  &lt;p&gt;Drugim krokiem jest przygotowanie samej wtyczki tak, aby R# mógł ją odpowiednio rozpoznać. Do tego celu służą atrybuty, które możemy umieścić w pliku AssemblyInfo.cs. Aby móc z nich skorzystać musimy dodać referencję do pliku JetBrains.Platform.ReSharper.UI.dll. Do opisu wtyczki służą atrybuty: &lt;b&gt;PluginTitleAttribute&lt;/b&gt;, &lt;b&gt;PluginDescription&lt;/b&gt;, &lt;b&gt;PluginVendor&lt;/b&gt;. Myślę, że ich nazwy mówią same za siebie. Każdy z nich przyjmuje jako parametr napis określający odpowiednią wartość. Przykładowe użycie może wyglądać tak:&lt;/p&gt;  &lt;pre style="font-size:1.3em;"&gt;&lt;span style="color:teal;"&gt;  1&lt;/span&gt; [assembly: PluginTitle(&lt;span style="color:maroon;"&gt;"Szarpnięty!"&lt;/span&gt;)]&lt;br&gt;&lt;span style="color:teal;"&gt;  2&lt;/span&gt; [assembly: PluginDescription(&lt;span style="color:maroon;"&gt;"Wyszarpana wtyczka"&lt;/span&gt;)]&lt;br&gt;&lt;span style="color:teal;"&gt;  3&lt;/span&gt; [assembly: PluginVendor(&lt;span style="color:maroon;"&gt;"Bartłomiej Legiędź"&lt;/span&gt;)]&lt;/pre&gt;

&lt;p&gt;Teraz wciskając F5 po raz pierwszy uruchomimy swoją własną wtyczkę do R#. Chwilowo zignorujemy fakt, że ta wtyczka nic nie robi. Zadowolimy się tym, że R# już o niej wie.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/WindowsLiveWriter/WtyczkidoReSharp.2Przygotowanierodowiska_C19A/zine.net.pl.resharper.plugins.02.loaded_2.png"&gt;&lt;img style="border-width:0px;" alt="zine.net.pl.resharper.plugins.02.loaded" src="http://zine.net.pl/blogs/nuwanda/WindowsLiveWriter/WtyczkidoReSharp.2Przygotowanierodowiska_C19A/zine.net.pl.resharper.plugins.02.loaded_thumb.png" width="501" border="0" height="321"&gt;&lt;/a&gt;&amp;nbsp; &lt;br&gt;Rys. 2. Wtyczka została rozpoznana i załadowana&lt;/p&gt;

&lt;p&gt;Podsumowując, przygotowanie środowiska jest proste i zamyka się w następujących krokach:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Tworzymy projekt Windows Class Library &lt;/li&gt;

  &lt;li&gt;Konfigurujemy opcje debugowania &lt;/li&gt;

  &lt;li&gt;Dodajemy referencję do JetBrains.Platform.ReSharper.UI.dll &lt;/li&gt;

  &lt;li&gt;Dodajemy atrybuty opisujące wtyczkę (nazwa, opis, producent) &lt;/li&gt;

  &lt;li&gt;Wciskamy F5 by sprawdzić, czy wtyczka poprawnie się ładuje &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dla leniwców przygotowałem mały wzorzec projektu dla Visual Studio. Dołączone do postu archiwum wystarczy skopiować do katalogu z wzorcami projektów (domyślnie: c:\Documents and Settings\&amp;lt;user&amp;gt;\Moje dokumenty\Visual Studio 2008\Templates\ProjectTemplates\Visual C#\) i VS pozwoli nam z tego wzorca skorzystać. Zawarłem w nim referencję do biblioteki R# zawierającej atrybuty wtyczki, a w pliku AssemblyInfo.cs umieściłem te atrybuty z pustymi wartościami.&lt;/p&gt;&lt;p&gt;Spis treści:&lt;/p&gt;  &lt;ol&gt;&lt;li&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/23/wtyczki-do-resharper-4-x-odc-1-wprowadzenie.aspx"&gt;Wprowadzenie&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Przygotowanie środowiska (ten tekst)&lt;/b&gt; &lt;/li&gt;&lt;li&gt;... (dalej zobaczymy, dopiero poznaję zagadnienie) &lt;/li&gt;&lt;/ol&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=2085" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /><category term="Wtyczki" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wtyczki/default.aspx" /></entry><entry><title>Wtyczki do ReSharper 4.x – Odc. 1 – Wprowadzenie</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/23/wtyczki-do-resharper-4-x-odc-1-wprowadzenie.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/09/23/wtyczki-do-resharper-4-x-odc-1-wprowadzenie.aspx</id><published>2008-09-23T10:43:00Z</published><updated>2008-09-23T10:43:00Z</updated><content type="html">&lt;p&gt;Będąc jeszcze na studiach byłem na jednym z seminariów, które prowadził mój kolega z roku niżej. Podczas prezentacji pisał kod ‘na żywo’. Nie dość, że i tak już szybko pisał na klawiaturze to okazało się, że używa jeszcze jakichś czarodziejskich sztuczek. W oknie edytora pojawiały się jakieś dziwne kółka i strzałki. Po westchnięciach sali przerwał i przedstawił narzędzie. Był to dodatek do Visual Studio - CodeRush + Refactor! Pro. Był to rok 2004.&lt;/p&gt;  &lt;p&gt;Po zainstalowaniu tego narzędzia szybko okazało się, że mój komputer sobie z nim nie poradzi. Jeszcze tego samego dnia odinstalowałem je i trochę zawiedziony usiadłem do ‘starego’ Visual Studio. W między czasie pojawiały się nowe wersje, a także konkurencja (nie wiem co było pierwsze Refactor! czy ReSharper, ale to nie ważne). Faktem jest, że nie zawracałem sobie nimi głowy, bo po co przyzwyczajać się do jakiegoś narzędzia, na które mnie nie stać i które po 30 dniach trzeba będzie odinstalować...&lt;/p&gt;  &lt;p&gt;Jednak ostatnio ReSharper znów wpadł mi w ręce. Pojawiły się nocne buildy i przez dłuższy okres czasu można było mieć go za darmo. Spróbowałem i okazało się, że po kupnie dodatkowego 1Gb ramu (po tym w sumie mam 2Gb) mogę spokojnie z nim pracować. Okazało się też, że po tygodniu współpracy już nie mogłem bez niego żyć ;)&lt;/p&gt;  &lt;p&gt;I został ReSharper. Jestem z niego bardzo zadowolony. Z jednej strony traktuję to narzędzie jako programistyczny dopalacz, który przyspiesza moją pracę. Z drugiej jednak strony wydaje się on użytecznym zamiennikiem API, którego dostarcza nam Visual Studio. W serii artykułów chciałbym Wam przybliżyć Open API, którego dostarcza ReSharper. Osobiście widzę w tym narzędziu wielki potencjał. Kolejnymi artykułami chciałbym zachęcić Was do tego, aby ten potencjał wyokorzystać. Nadmienić należy również, że wiele funkcji ReSharpera jest pisana tak jak dodatki, czyli przy użyciu Open API.&lt;/p&gt;  &lt;p&gt;Tyle tytułem wstępu. Jeżeli ktoś z Was chciałby tak jak ja zacząć przygodę z dodatkami do ReSharpera na pewno zacząłby od &lt;a href="http://www.google.pl/search?q=ReSharper+Plugin"&gt;googla&lt;/a&gt;, a ten skierowałby go prosto na strony JetBrains dotyczącą &lt;a href="http://www.jetbrains.net/confluence/display/ReSharper/ReSharper+Plugin+Development"&gt;tego właśnie zagadnienia&lt;/a&gt;. Jak wszyscy dobrze wiemy z autopsji, dokumentacji nikt nie lubi pisać. Widać, że chłopaki z JetBrains mieli dobry start, bo do wersji 2.5 jest przynajmniej wstęp do pisania dodatków. Do wersji 3.0 są tylko PowerToy’e obrazujące wykorzystanie niektórych funkcji API. Do wersji 4.0 nie ma jeszcze nic (na forum ReSharpera pojawiły się już informacje, że PowerToy’e zostały już zmigrowane do najnowszej wersji i jak tylko przejdą proces weryfikacji to pojawią się do ściągnięcia).&lt;/p&gt;  &lt;p&gt;Brak dokumentacji do najnowszej wersji wcale mnie nie zniechęcił. Mamy przecież &lt;a href="http://www.red-gate.com/products/reflector/"&gt;Reflectora&lt;/a&gt;, a w razie niejasności zawsze możemy popytać na &lt;a href="http://www.intellij.net/forums/forum.jspa?forumID=67&amp;amp;start=0"&gt;forum&lt;/a&gt;. Niestety okazuje się też, że od wersji 2.5 sporo się zmieniło. Podczas przerabiania PowerToy z wersji 3.0 na wersję 4.0 okazało, że niektóre typy zniknęły, a pewne interfejsy zostały uproszczone. To oczywiście dobrze, bo widać, że w źródłach robione są pewne porządki. Ostatnio na swoim blogu Ilya Ryzhenkov pisał o &lt;a href="http://resharper.blogspot.com/2008/07/whats-next-life-after-release.html"&gt;kolejnych krokach&lt;/a&gt; w rozwoju narzędzia. Możemy się zatem spodziewać kolejnych zmian.&lt;/p&gt;  &lt;p&gt;Dostępnych pluginów nie jest zbyt dużo. Mamy &lt;a href="http://www.jetbrains.com/resharper/plugins/"&gt;dziewięć oficjalnych&lt;/a&gt; na stronie JetBrains, a ponadto możemy znaleźć jeszcze kilka na stronach CodePlex, CodeProject itd. Jednak nie jest to duża liczba. W Sieci nie znajdziemy również wielu tekstów dotyczących tworzenia dodatków. Najwyższy więc czas zmienić ten stan rzeczy!&lt;/p&gt;  &lt;p&gt;Spis treści:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;strong&gt;Wprowadzenie (ten tekst)&lt;/strong&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/29/wtyczki-do-resharper-4-x-odc-2-przygotowanie-rodowiska.aspx"&gt;Przygotowanie środowiska&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;... (dalej zobaczymy, dopiero poznaję zagadnienie) &lt;/li&gt; &lt;/ol&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=2046" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /><category term="Wtyczki" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wtyczki/default.aspx" /></entry><entry><title>TfsSpotlight odsłona piąta (v0.5)</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/09/21/tfsspotlight-ods-ona-pi-ta-v0-5.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/09/21/tfsspotlight-ods-ona-pi-ta-v0-5.aspx</id><published>2008-09-21T20:20:32Z</published><updated>2008-09-21T20:20:32Z</updated><content type="html">&lt;p&gt;Kilka dni temu wydałem kolejną wersję mojego małego projektu. W piątej iteracji dodałem możliwość tworzenia nowych jednostek roboczych (ang. work item) oraz zmieniłem spos&amp;#243;b zarzadzania poszczeg&amp;#243;lnymi serwerami.&lt;/p&gt;  &lt;p&gt;Dla tych, kt&amp;#243;rzy nie wiedzą czym jest &lt;a href="http://www.codeplex.com/tfsspotlight"&gt;TfsSpotlight&lt;/a&gt;:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Jest to aplikacja wykorzystująca API Team Foundation Server. &lt;/li&gt;    &lt;li&gt;Pozwala na przeglądanie oraz modyfikację jednostek pracy. &lt;/li&gt;    &lt;li&gt;Poszczg&amp;#243;lne zapytania oraz konkretne jednostki pracy można przeglądać jednocześnie dzięki zakładkom. &lt;/li&gt;    &lt;li&gt;Jest niezależne od Visual Studio i dzięki temu można niezależnie np. pobierać wersję z serwera i przeglądać jednostki pracy. &lt;/li&gt;    &lt;li&gt;Ułatwia szybkie znalezienie jednostki pracy dzięki funkcji GoTo. &lt;/li&gt;    &lt;li&gt;Jest ergonimiczny, co pozwala skupić się na wykonywanej pracy a nie na niedociągnięciach aplikacji. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Przede mną kolejna iteracja a w niej:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Historia jednostek pracy, kt&amp;#243;rych dotyczyły nasze operacje (tworzenie, modyfikacja).&lt;/li&gt;    &lt;li&gt;Udogodnienia w dostępie do projekt&amp;#243;w. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Aplikacja jest dostępna jak zwykle w postaci archiwum zip, instalatora windows oraz pakietu autoaktualizacji (dla tych co już mają ją zainstalowaną).&lt;/p&gt;  &lt;p&gt;Udanej pracy!&lt;/p&gt;  &lt;p&gt;&lt;a href="http://zine.net.pl/blogs/nuwanda/WindowsLiveWriter/TfsSpotlightodsonapitav0.5_13804/Screen.v.0.5_2.png"&gt;&lt;img style="border-right:0px;border-top:0px;border-left:0px;border-bottom:0px;" height="585" alt="TfsSpotlight v0.5" src="http://zine.net.pl/blogs/nuwanda/WindowsLiveWriter/TfsSpotlightodsonapitav0.5_13804/Screen.v.0.5_thumb.png" width="899" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=2037" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Tfs Spotlight" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Tfs+Spotlight/default.aspx" /></entry><entry><title>Nocny ReSharper 4.0</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/04/17/Nocny-Resharper-4-0.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/04/17/Nocny-Resharper-4-0.aspx</id><published>2008-04-16T22:17:00Z</published><updated>2008-04-16T22:17:00Z</updated><content type="html">&lt;p&gt;Odkąd zacząłem używać &lt;a href="http://www.jetbrains.com/resharper/"&gt;Resharpera &lt;/a&gt;moje dotychczasowe życie programisty
zmieniło się nie do poznania. Nie jest to bynajmniej długa historia, ale faktem
jest, że odbiła się piętnem na mojej codziennej pracy. &lt;/p&gt;

&lt;p&gt;W lutym wystartował proces nocnych buildów nowej wersji
Resharpera. Buildy te są &lt;a href="http://www.jetbrains.net/confluence/display/ReSharper/ReSharper+4.0+Nightly+Builds"&gt;publicznie dostępne&lt;/a&gt;. Każdy kolejny build zawiera
nową 30-dniową licencję. Dzięki temu mogę korzystać z tego wspaniałego
narzędzia i cieszyć się zaoszczędzonym czasem (przynajmniej do czasu premiery
tej wersji). Później nie pozostanie nic innego jak zakupić licencję dla siebie.
Co ciekawe warunki licencji personal pozwalają na używanie Resharpera zarówno w
domu jak i w pracy. Warunkiem jest to, żeby używała go osoba, która zakupiła
licencję. Cieszy mnie to bardzo, bo dzięki temu R# będzie mógł być zawsze
blisko mnie ;). A skoro w planach mam taki zakup to nikogo nie zdziwi fakt, że zależy mi na tym, aby powstał produkt wysokiej jakości.
Zarejestrowałem się więc w &lt;a href="http://www.jetbrains.net/jira/browse/RSRP"&gt;ich systemie śledzenia błędów&lt;/a&gt; i tam właśnie lądują
wszystkie moje uwagi i znalezione błędy. Was również zachęcam do czynnego
udziału.&lt;/p&gt;

&lt;p&gt;Dwa miesiące pacy z wersją 4.0 i ponad miesiąc z wersją 3.0
i jestem zachwycony, żeby nie powiedzieć uzależniony. Tym co jeszcze nigdy nie
spróbowali, a pracują w środowisku VSC#, polecam gorąco. Tym co używają wersji
wcześniejszej polecam wypróbowanie któregoś z nocnych buildów wersji 4.0. A ja zadaję sobie pytanie: czemu tak późno
zacząłem tego używać?!&lt;/p&gt;

&lt;p&gt;Thank you JetBrains!&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=922" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="ReSharper" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/ReSharper/default.aspx" /></entry><entry><title>Biblioteka dostępu do TFS i testy jednostkowe</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/04/03/biblioteka-dostepu-do-tfs-i-testy-jednostkowe.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/04/03/biblioteka-dostepu-do-tfs-i-testy-jednostkowe.aspx</id><published>2008-04-03T10:42:00Z</published><updated>2008-04-03T10:42:00Z</updated><content type="html">&lt;p&gt;W poście &lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/03/11/Tfs-Spotlight-buduje-wlasny-CAB.aspx"&gt;Tfs Spotlight – buduję własny CAB&lt;/a&gt; wprowadzającym do mojego projektu &lt;a href="http://www.codeplex.com/tfsspotlight"&gt;TfsSpotlight&lt;/a&gt; wspomniałem, że
jednym z moich celów jest pisanie testów jednostkowych. Chcę w ten sposób
zobaczyć jakie problemy pojawią się podczas pisania testów jednostkowych dla
większego i bardziej skomplikowanego projektu niż te, które do tej pory
robiłem. &lt;/p&gt;
&lt;p&gt;Proces pisania testów jednostkowych nie jest łatwy i co
jakiś czas napotykam pewne problemy. Ostatni problem pojawił mi się w momencie,
gdy chciałem napisać test dla klasy prezentera, który obsługuje widok
konkretnego workitema. Źródłem danych dla tego widoku jest obiekt klasy
pochodzącej z biblioteki dostępu do &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt; – klasa WorkItem. Jak się szybko okazało
napisanie testu jest niemożliwe, jeżeli bierzemy przypadek całkowitego
odizolowania. Wszystko to dlatego, że nie mogę ręcznie utworzyć instancji tej
klasy. Do tego potrzebne jest połączenie z serwerem (sic!). Moje zamiary spełzły
na niczym. W dodatku klasa ta jest sealed, więc &lt;a href="http://ayende.com/projects/rhino-mocks.aspx"&gt;Rhino Mocks&lt;/a&gt; robie nie radzą. Nie
mam zielonego pojęcia jak sobie z tym poradzić.&lt;/p&gt;
&lt;p&gt;Wracając do biblioteki &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt; pozwalającej na dostęp do
serwera to należy zauważyć, że programiści i projektanci tego API zabezpieczyli
się przed nieumiejętnym jego wykorzystaniem, aby zapobiec nadmiernemu
obciążeniu serwera. Elementy takie jak Project, Query, WorkItem czy ich
kolekcje są zaimplementowane w postaci klas, które swoje dane ładują w sposób
leniwy. Tym sposobem wyświetlając w tabeli workitemy, pobierane są tylko
właściwości wyświetlane w kolumnach. Pozostałe nie zostaną ściągnięte. Takie
zachowanie na pewno ogranicza ilość danych przesyłanych z serwera, ale niestety
sposób wykonania tych elementów może pozostawiać wiele do życzenia. Wszystkie
te klasy o których mowa są sealed, a serwisy nie zostały opisane żadnymi
interfejsami definiującymi kontrakty. Wygląda na to, że w tym przypadku nie
pomyślano o Zasadzie oddzielenia zagadnień (ang. Separation of Concerns).
Podobnie jest z kontrolkami dostarczanymi w bibliotece &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt;. Okazuje się, że
podpinając im źródło danych one i tak pytają o coś serwer (sic!)&lt;/p&gt;
&lt;p&gt;Budując system luźno powiązany praca z takimi komponentami
jest bardzo uciążliwa, a w tym przypadku skutecznie uniemożliwia mi pisanie
testów. Chociaż trzeba zaznaczyć, że jest światełko w tunelu. Od jakiegoś czasu co raz częściej słyszy się o co raz to nowszych projektach ze stajni MS, które wspominają o testach jednostkowych. Ba, wczoraj Scott Guthrie &lt;a href="http://weblogs.asp.net/scottgu/archive/2008/04/02/unit-testing-with-silverlight.aspx"&gt;ogłosił&lt;/a&gt;, że Silverlight 2 jest testowany przy pomocy testów jednostkowych (jest ich ponad 2000) i co więcej dostarczają również narzędzi do testowania własnych projektów Silverlight-owych.&amp;nbsp; Bardzo mnie ten trend cieszy, gdyż może następna wersja biblioteki dostępu do &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt; będzie ten trend uwzględniała.&lt;br&gt;&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=903" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Testy jednostek" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Testy+jednostek/default.aspx" /><category term="Zasady projektowe" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Zasady+projektowe/default.aspx" /><category term="Team System" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Team+System/default.aspx" /></entry><entry><title>Globalizacja aplikacji i wątki</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/04/02/Globalizacja-aplikacji-i-watki.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/04/02/Globalizacja-aplikacji-i-watki.aspx</id><published>2008-04-02T12:23:00Z</published><updated>2008-04-02T12:23:00Z</updated><content type="html">&lt;p&gt;Natknąłem się na pewne zachowanie
.Net Frameworka, które było zupełnie odmienne od tego, którego się spodziewałem.
Problem dotyczy globalizacji i wątków. Okazuje się, że mając aplikację, która
jest zlokalizowana na wiele języków musimy zwrócić szczególną uwagę za każdym
razem gdy korzystamy z wątków.&lt;/p&gt;

&lt;p&gt;Ustawienia dotyczące kultury są
właściwościami wątku. W systemie Windows przy starcie wątku ustawienia kultury pobierane
są z ustawień systemowych. Zatem jeżeli uruchamiamy aplikację, jej watek otrzymuje
ustawienia użytkownika. Działanie to jest jak najbardziej oczekiwane. Okazuje
się jednak, że tworzone w aplikacji kolejne watki otrzymują ustawienia
kulturowe w taki sam sposób jak wątek pierwszy. Może nie było by w tym nic
złego, ale osobiście oczekiwałem zachowania odwrotnego – czyli że nowy wątek
otrzyma takie same ustawienia jak wątek główny. Natomiast gdy w międzyczasie zmienimy
ustawienia kulturowe pierwszego wątku to pojawiają się dodatkowe problemy.&lt;/p&gt;

&lt;p&gt;Wyobraźmy sobie aplikację, która
przed uruchomieniem wyświetla okno, w którym możemy wybrać język w jakim
chcemy, żeby się uruchomiła. Wybierając język inny niż ten, który znajduje się
w ustawieniach systemu, będziemy działać swobodnie dopóki nie aplikacja nie
zacznie korzystać z wątków. &lt;/p&gt;

&lt;p&gt;Ja, będąc niedoświadczonym
programistą, założyłem, że skoro aplikacja ma określone ustawienia kulturowe w
pierwszym wątku, to tworząc nowe wątki ustawienia te zostaną zachowane
(skopiowane z wątku, który tworzy nowe watki). Okazuje się jednak, że CLR nie
zawiera żadnego mechanizmu kontrolowania ustawień kulturowych wątków tworzonych
w danym procesie. Dlatego każdy tworzony wątek będzie miał ustawienia te
pobrane z systemu Windows.&lt;/p&gt;

&lt;p&gt;Dobrze, skoro już wiemy o tych
niuansach, to trzeba się do tego przystosować. Jeżeli ręcznie tworzymy wątki to
nie ma z tym żadnego problemu. Klasa Thread ma odpowiednie właściwości, dzięki
którym możemy ustawienia kulturowe ustawić według własnego uznania. Ja jednak
korzystam zwykle z puli wątków (klasa ThreadPool). Jak się szybko okazało klasa
ta nie ma żadnego bezpośredniego wsparcia dla uruchamiania wątków z innymi
ustawieniami kulturowymi. A szkoda. Skoro reguły są jasne, to powinniśmy chociaż
dostać jakieś przeciążenie metody QueueUserWorkItem pozwalające określić
ustawienia kulturowe uruchamianego wątku. A tak trzeba się tym ręcznie bawić i określać
ustawienia kulturowe już w metodzie, uruchomionej w osobnym wątku, co moim
zdaniem tylko wprowadza niepotrzebne zamieszanie w kodzie. Uważam, że można
było tego uniknąć udostępniając odpowiednie API.&lt;/p&gt;

&lt;p&gt;Podsumowując okazuje się, że
pracując z aplikacją, która ma być lokalizowana na inne języki musimy
szczególną uwagę zwrócić na obsługę wątków i odpowiednio ustawiać im
właściwości kulturowe. Niemniej jednak zachowanie to jest dla mnie zgoła nie intuicyjne.
Bo przecież zwykle chcemy, aby cała aplikacja działała z tymi samymi
ustawieniami kulturowymi, a przypadki odwrotne są raczej sporadyczne. A może
jest jakieś dobre uzasadnienie tego stanu rzeczy?&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=885" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="CLR" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/CLR/default.aspx" /></entry><entry><title>Tfs Spotlight v0.3</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/03/30/tfs-spotlight-v0-3.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/03/30/tfs-spotlight-v0-3.aspx</id><published>2008-03-30T15:04:00Z</published><updated>2008-03-30T15:04:00Z</updated><content type="html">Wydałem kolejną wersję mojego małego projektu – &lt;a href="http://www.codeplex.com/tfsspotlight"&gt;Tfs Spotlight&lt;/a&gt;.&lt;br&gt;&lt;br&gt;Co nowego w &lt;a href="https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=tfsspotlight&amp;amp;ReleaseId=10449"&gt;wersji 0.3&lt;/a&gt;?



&lt;ul&gt;&lt;li&gt;Dodano możliwość otwierania poszczególnych workitemów w
osobnych zakładkach. Wystarczy kliknąć dwa razy na dany workiem i otworzy się
on w nowej zakładce.&lt;/li&gt;&lt;li&gt;Dodano możliwość otwierania workitemów po bezpośrednim
podaniu id oraz wyborze serwera. Operację tę można wykonać też bez użycia
myszy. Zdefiniowany został skrót klawiszowy Ctrl + G, który przenosi fokus do
pola, gdzie należy wpisać id workitema. Następnie wciskając klawisz Enter
możemy rozwinąć listę dostępnych serwerów, albo jeżeli mamy tylko jeden serwer,
od razu otworzyć zakładkę z workitemem o podanym id.&lt;/li&gt;&lt;li&gt;Dodano możliwość przechodzenia między zakładkami za pomocą
kółka myszy.&lt;/li&gt;&lt;/ul&gt;





&lt;p class="MsoNormal"&gt;Dla tych co już używają tego programu umieściłem też pakiet
do auto aktualizacji. Przy uruchomieniu aplikacji powinien pojawić się monit
proponujący aktualizację. To będzie próba generalna tego mechanizmu :)&lt;br&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;&lt;br&gt;&lt;/p&gt;&lt;p class="MsoNormal"&gt;Miłej pracy!&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=884" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Tfs Spotlight" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Tfs+Spotlight/default.aspx" /></entry><entry><title>Tfs Spotlight – buduję własny CAB</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/03/11/Tfs-Spotlight-buduje-wlasny-CAB.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/03/11/Tfs-Spotlight-buduje-wlasny-CAB.aspx</id><published>2008-03-11T11:01:00Z</published><updated>2008-03-11T11:01:00Z</updated><content type="html">&lt;p&gt;Od kilku miesięcy nic tu nie pisałem (oczywiście poza poprzednim nieplanowanym &lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2008/03/03/automatyczne-podlaczanie-debuggera-do-procesu-debugger-launch.aspx"&gt;wpisem&lt;/a&gt;). Jak łatwo
się domyślić czas mi na to nie pozwalał. Z jednej strony natłok zadań w pracy
(stabilizacja finalnej wersji – &lt;a href="http://www.comarch-altum.com/"&gt;Comarch
ALTUM&lt;/a&gt; ujrzał niedawno światło dzienne na tegorocznym &lt;a href="http://www.comarch.com/en/Press+Center/Press+Information/The+Comarch+ALTUM+premiere+at+CeBit.htm"&gt;CeBicie&lt;/a&gt;)
a z drugiej strony projekt, który chciałbym opisać w tym tekście.&lt;/p&gt;

&lt;p&gt;Pierwsze odcinki z serii &lt;a href="http://codebetter.com/blogs/jeremy.miller/archive/2007/07/25/the-build-your-own-cab-series-table-of-contents.aspx"&gt;Build
your own CAB&lt;/a&gt; pojawiły się już dość dawno, a ponieważ Jeremy jest moim
ulubionym bloggerem, to śledzę je od początku z zainteresowaniem i nie mogę się
doczekać następnych (ciekaw jestem ilu z Was także czyta regularnie jego teksty).
Osobiście jako zawodowy programista mam jeszcze niewielką praktykę w budowaniu
złożonych aplikacji, ale czytając ciągle różne teksty, pojawiające się na
blogach, moja głowa wypełnia się wiadomościami teoretycznymi, a po przeczytaniu
wszystkich dostępnych dotąd artykułów z Serii zapragnąłem tę wiedzę wykorzystać
w praktyce. &lt;/p&gt;

&lt;p&gt;Pracę jako programista zacząłem właśnie od
aplikacji budowanej w oparciu o &lt;a href="http://msdn2.microsoft.com/en-us/library/aa480450.aspx"&gt;Composite
Application Block&lt;/a&gt;, pracuję z nią już ponad rok, a Seria Jeremy’ego
pozwoliła mi z dystansu spojrzeć na rozwiązania zastosowane w CAB, lepiej je
zrozumieć i wykorzystać. Ponadto chęć poszerzania swoich doświadczeń i
umiejętności pchnęła mnie dalej. W Sieci można znaleźć pomysł, by Serię
opatrzyć &lt;a href="http://rmkitson.net/archive/2008/01/25/keeping-ui-construction-dry.aspx"&gt;przykładami&lt;/a&gt;.
Ten pomysł zachęcił mnie by wykorzystać zdobytą wiedzę w praktyce i wykonać
jakiś projekt open source. Tak na marginesie trzeba zauważyć, że i tak najlepszym
przykładem jest projekt &lt;a href="http://storyteller.tigris.org/"&gt;Story Teler&lt;/a&gt;
Jeremy’ego, z którego to pochodzi większość przykładów, które możemy zobaczyć w
Serii.&lt;/p&gt;

&lt;p&gt;Pomysł ten pojawił się w momencie, gdy szukałem
trochę wolnego czasu by zająć się projektem dotyczącym Team Foundation Server
Workitem Tracking, czyli po prostu zarządzaniem workitemami. Na co dzień
pracuję w środowisku &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt; i do tego celu używam programu &lt;a href="http://www.codeplex.com/fissum/"&gt;Fissum&lt;/a&gt;. Program ten
jest naprawdę sprytny i pozwala mi lepiej wykorzystać mój czas. Gdy pobieram
wersję z repozytorium, bądź też kompiluję projekt, Team Explorer nie nadaje się
do użytku, tak jak i całe Visual Studio.
Są po prostu zablokowane. Inną sprawą jest to, że TE w ogóle jest toporny i
wolny - czytałem, że w VS2008 ma być o niebo lepiej. Pożyjemy, zobaczymy.
Program zewnętrzny, taki jak &lt;a href="http://www.codeplex.com/fissum/"&gt;Fissum&lt;/a&gt;, powala mi w tym czasie zająć się moimi workitemami.
W szczególności najczęściej korzystam z niego w fazie stabilizacji, w której
zwykle mam do czynienia z błędami, które muszę poprawić. Dzięki niemu w czasie,
gdy Visual Studio jest zajęte, mogę na boku analizować przychodzące błędy jak i
weryfikować te już istniejące. To jest mój sposób na optymalizację czasu, a
czas przecież trzeba szanować (szczególnie, gdy harmonogram ciśnie).&lt;/p&gt;

&lt;p&gt;Można zapytać: skoro tak dobrze Ci się pracuje z &lt;a href="http://www.codeplex.com/fissum/"&gt;Fissum&lt;/a&gt;,
to czemu chcesz pisać swoje oprogramowanie? Przede wszystkim chodzi o naukę.
Generalnie aplikacja ma być prosta, jednak z drugiej strony będzie na tyle
skomplikowana, że większość tematów poruszanych w Serii znajdzie w niej
zastosowanie - chociażby implementacja UI, komend, stanu menu, zakładek itd. Drugim
celem było zapoznanie się z narzędziami, które od dawna czekają na półce - mam
tu na myśli jakiś kontener IoC (w moim wypadku &lt;a href="http://structuremap.sourceforge.net/Default.htm"&gt;StructureMap&lt;/a&gt;) oraz bibliotekę
do logowania (&lt;a href="http://msdn2.microsoft.com/en-us/library/aa480464.aspx"&gt;Logging
Application Block&lt;/a&gt; już widziałem w &lt;a href="http://zine.net.pl/blogs/arkadiusz_wasniewski/archive/2007/09/24/konkurs-enterprise-library-logging-lab.aspx"&gt;akcji
u Arka&lt;/a&gt;, więc wypadałoby z niego skorzystać). Trzecim powodem jest to, że
projekt &lt;a href="http://www.codeplex.com/fissum/"&gt;Fissum&lt;/a&gt; mimo że jest otwarty (open source), to jednak jest zamknięty. Program
ten zawiera kilka rzeczy, które mi przeszkadzają. O jednej już &lt;a href="http://zine.net.pl/blogs/nuwanda/archive/2007/12/18/zachowywanie-stanu-okna.aspx"&gt;pisałem&lt;/a&gt;.
Ponadto mam kilka pomysłów jak można by było go usprawnić (generalnie chodzi o
użyteczność a nie o funkcjonalność). Proponowałem &lt;a href="http://blogs.developpeur.org/miiitch/"&gt;Miiitchowi&lt;/a&gt; swoją pomoc jednak
powiedział mi, że jest to jego prywatny projekt, na którym realizuje swoje
pomysły dotyczące &lt;a href="http://msdn2.microsoft.com/en-us/teamsystem/aa718934.aspx"&gt;TFS&lt;/a&gt;. Trzeba to uszanować. Na początku modyfikowałem &lt;a href="http://www.codeplex.com/fissum/"&gt;Fissum&lt;/a&gt;
do swoich potrzeb, ale jest to uciążliwe do utrzymania, gdy Miiitch wydaje nowe
wersje.&lt;/p&gt;

&lt;p&gt;W niniejszym tekście chciałbym opisać kilka problemów,
które napotkałem budując &lt;a href="http://www.codeplex.com/tfsspotlight"&gt;TfsSpotlight&lt;/a&gt; oraz znalezionych dzięki Serii
rozwiązań. Jakoś tak wyszło, że zacząłem trochę od końca. W swojej Serii Jeremy
nie poruszył jeszcze tematu Application Shell, a w momencie gdy pisałem
pierwszą wersję aplikacji nie było też tekstu o &lt;a href="http://codebetter.com/blogs/jeremy.miller/archive/2008/02/15/build-your-own-cab-18-the-command-executor.aspx"&gt;Command
Executor&lt;/a&gt;, a aby zbudować podstawę, trzeba było zacząć właśnie od tych
elementów. Dlatego właśnie rozwiązania zawarte w &lt;a href="http://www.codeplex.com/tfsspotlight"&gt;TfsSpotlight&lt;/a&gt; dotyczące tych
zagadnień są tylko i wyłącznie mojego pomysłu.&lt;/p&gt;

&lt;h3&gt;Oddzielenie logiki aplikacji od formatek&lt;/h3&gt;



&lt;p&gt;Po &lt;a href="http://vimeo.com/718265"&gt;prezentacji&lt;/a&gt;
Wojtka o MVC chyba każdy się ze mną zgodzi, że budując chociażby kalkulator
należy oddzielić logikę dziedziny od sposobu prezentacji. Pierwszą i dla mnie
najważniejszą zaletą takiego podejścia jest możliwość łatwego przetestowania
logiki bez konieczności angażowania w ten proces komponentów graficznych. Nie
napisałem tego wcześniej, ale w tym projekcie jednym z większych dla mnie
wyzwań jest zadanie pisania testów jednostkowych dla jak największych części
kodu. Co z tego wyjdzie – zobaczymy.&lt;/p&gt;

&lt;p&gt;Od dłuższego czasu przy budowaniu formatek
korzystam z pewnego wariantu wzorca Model-View-Presenter. Mówię tu wariantu,
gdyż, tak jak już wspominał Wojtek na swojej prezentacji, nie ma jednej
najlepszej implementacji wzorca MVP. W moim wykonaniu widok odpowiada wzorcowi
PasiveView (&lt;a href="http://www.martinfowler.com/eaaDev/PassiveScreen.html"&gt;Fowler&lt;/a&gt;,
&lt;a href="http://codebetter.com/blogs/jeremy.miller/archive/2007/05/30/build-your-own-cab-part-4-the-passive-view.aspx"&gt;Jeremy&lt;/a&gt;),
gdyż jest to postać najbardziej przyjazna testowaniu, bo zawiera minimalną
ilość logiki. Prezenter jest główną jednostką dowodzącą, która zawiera wszelką
logikę prezentacji i obsługi danego widoku - SupervisingController (&lt;a href="http://www.martinfowler.com/eaaDev/SupervisingPresenter.html"&gt;Fowler&lt;/a&gt;,
&lt;a href="http://codebetter.com/blogs/jeremy.miller/archive/2007/05/25/build-you-own-cab-part-3-the-supervising-controller-pattern.aspx"&gt;Jeremy&lt;/a&gt;).
Z modelem natomiast bywa różnie. W prostych przypadkach są to bezpośrednio
encje reprezentujące dane, na których dany widok pracuje. W przypadkach
bardziej skomplikowanych preferuję oddzielną klasę modelu, która w
szczególności zawiera logikę obsługi danych tj. wczytywanie, zapisywanie. Ma to
szczególne znaczenie, gdy widok ma wiele źródeł danych (np. listy wyboru, które
również trzeba zasilić danymi z bazy). W takich przypadkach model zajmuje się przygotowaniem
wszystkich potrzebnych zestawów danych i odciąża tym prezentera. W niniejszym
projekcie taka skomplikowana sytuacja jeszcze nie zaszła, gdyż biblioteki TFS
dostarczają nam gotowych kontrolek do reprezentacji całych elementów, toteż
zostałem zwolniony z konieczności implementowania ich własnoręcznie.&lt;/p&gt;

&lt;p&gt;Wykorzystując MVP preferuję podejście bazujące na
bezpośrednim odwoływaniu się widoku do prezentera. W porównaniu z podejściem
opartym na zdarzeniach jest to podejście znacznie prostsze. Przede wszystkim
dlatego, że prostsza jest nawigacja po kodzie. Mając bezpośrednie odwołania do
metod możemy wykorzystać narzędzia nawigacyjne, jakie daje nam Visual Studio, i
przemieszczać się z widoku do prezentera dwoma kliknięciami myszy. Sprawa się
trochę komplikuje, jeżeli prezenter jest opisany interfejsem. Wtedy niestety VS
sobie nie radzi i pokazuje nam implementację interfejsu, a nie kod konkretnego
prezentera, czyli nie do końca tego, czego byśmy chcieli. Problemu tego nie
mają użytkownicy &lt;a href="http://www.jetbrains.com/resharper/"&gt;ReSharepera&lt;/a&gt;,
którego genialna funkcja &lt;i&gt;Go to inheritor&lt;/i&gt;
pozwala natychmiast przemieścić się do klasy implementującej dany interfejs.
Poza tym implementacja zdarzeń wymaga od nas o wiele większego nakładu pracy.
Należy przecież w widoku zaimplementować zdarzenia dla każdej możliwej do
wykonania operacji. Następnie w prezenterze trzeba do tych wszystkich zdarzeń
podpiąć odpowiednie metody. Jak dla mnie za dużo roboty.&lt;/p&gt;

&lt;h3&gt;Zarządzanie komendami&lt;/h3&gt;

&lt;p&gt;Pytanie jest proste: jak zarządzać komendami,
które użytkownik może wykonywać? Wymagania zwykle są następujące:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;
Komenda powinna być dostępna w wielu miejscach aplikacji: menu główne,
menu kontekstowe, ikona na pasku narzędzi, skrót klawiszowy, wywołanie
z kodu programu nawet w innym module.&lt;/li&gt;&lt;li&gt;
Zarządzanie stanem komendy – czy jest aktywna czy nie.&lt;/li&gt;&lt;li&gt;
Sposób uruchomienia komendy – synchroniczne czy asynchroniczne – w samej implementacji
komendy chcielibyśmy abstrahować od sposobu jej uruchomienia.

&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;Jak nie trudno się domyśleć bez spójnego
mechanizmu definiowania i obsługi komend szybko zabrniemy w ślepy zaułek i
rozwijanie aplikacji stanie się nieprzyjemne. Z resztą bez odpowiedniego
mechanizmu trudno będzie w prosty sposób zarządzać choćby stanem poszczególnych
komend. Z pomocą przychodzi nam bardzo prosty wzorzec – Komenda (&lt;a href="http://codebetter.com/blogs/jeremy.miller/pages/build-your-own-cab-14-managing-menu-state-with-microcontroller-s-command-s-a-layer-supertype-some-structuremap-pixie-dust-and-a-dollop-of-fluent-interface.aspx"&gt;Jeremy&lt;/a&gt;).
Wzorzec ten wprowadza interfejs komendy – ICommand – dzięki któremu możemy wykonanie
każdej komendy zunifikować do postaci wykonania jednej metody
ICommand.Execute(). Osobiście nigdy nie wykorzystywałem jeszcze tego podejścia,
znałem je jedynie z definicji. Pierwsze pytanie jakie mi się nasunęło to jak
dana komenda ma poznać swój kontekst (czyli dane na których ma pracować)?
Przecież metoda Execute nie przyjmuje żadnego parametru! Tutaj zrozumiałem, że
aby dobrze zaimplementować ten wzorzec trzeba do tego odpowiednio nasz system
przygotować. &lt;/p&gt;

&lt;p&gt;Na przykład rozważmy interfejs programu, który
opiera się na zakładkach. Z reguły będziemy mieli jeden pasek z przyciskami i
jedno menu główne, gdzie umieścimy komendy dotyczące aktywnej zakładki. Aby móc
dostarczyć tym komendom kontekstu należy wprowadzić jakiś sposób pobierania
aktywnej zakładki, aby komenda mogła oddelegować do niej akcję. Podobnie należy
pomyśleć o innych elementach powłoki, do których będziemy chcieli mieć dostęp.
Wydzielenie odpowiednich serwisów pozwala szybko odpowiedzieć na pytanie „jak
dana komenda ma uzyskać interesujące ją dane”.&lt;/p&gt;

&lt;p&gt;Kontynuując zadanie, potrzebny jest nam teraz
spójny sposób obsługi komend. Po pierwsze potrzebujemy jednolitego sposobu przypisywania
komend do kontrolek, a po drugie jakiegoś mechanizmu pozwalającego te komendy
uruchamiać. Zajmijmy się teraz pierwszym zadaniem, a drugie omówimy sobie
trochę później. &lt;/p&gt;

&lt;p&gt;Od jakiegoś już czasu obserwuję w eterze coraz
częściej pojawiające się odwołania do tekstu Martina Fowlera o &lt;a href="http://martinfowler.com/bliki/FluentInterface.html"&gt;fluent interfaces&lt;/a&gt;.
Co więcej, zauważyć można pojawiające się implementacje wykorzystujące ten
sposób budowania interfejsów klas. Prawdę mówiąc, gdy po raz pierwszy
przeczytałem ten artykuł, idea bardzo mi się spodobała, ale nie widziałem
jeszcze konkretnego jej zastosowania. Dopiero później, używając &lt;a href="http://ayende.com/projects/rhino-mocks.aspx"&gt;Rhino Mocks&lt;/a&gt;
zauważyłem, że przecież używam właśnie fluent interface! I rzeczywiście, do
zadań konfiguracyjnych podejście to jest niezastąpione, a wynikowy kod jest
niesamowicie czytelny. Idąc za &lt;a href="http://codebetter.com/blogs/jeremy.miller/pages/build-your-own-cab-14-managing-menu-state-with-microcontroller-s-command-s-a-layer-supertype-some-structuremap-pixie-dust-and-a-dollop-of-fluent-interface.aspx"&gt;przykładem&lt;/a&gt;
Jeremy’ego konfigurację komend zaimplementowałem wykorzystując tę technikę. Z
wyniku jestem bardzo zadowolony, gdyż powstał naprawdę bardzo elastyczny, a
zarazem spójny mechanizm przypinania komend do interfejsu użytkownika. Sposób
implementacji możecie zobaczyć w kodzie (plik ConfigureMenuExpression.cs).
Poniżej przedstawię tylko wynik użycia tej klasy konfiguracyjnej.&lt;/p&gt;

&lt;p&gt;Przykład [C#] 1. Przykład wykorzystania klasy
konfiguracyjnej wykorzystującej fluent interface.&lt;/p&gt;&lt;p style="text-align:justify;"&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;ConfigureMenuExpression&lt;br /&gt;    .Execute(CommandsNames.ExitApplication)&lt;br /&gt;    .Synchronous()&lt;br /&gt;    .For(&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;this&lt;/span&gt;.miExit)&lt;br /&gt;    .For(&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;this&lt;/span&gt;.tsbExit)&lt;br /&gt;    .WithShortcut(Keys.F10)&lt;br /&gt;    .Enable();&lt;br /&gt;&lt;br /&gt;ConfigureMenuExpression&lt;br /&gt;    .Execute(CommandsNames.SaveAllWorkItems)&lt;br /&gt;    .For(&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;this&lt;/span&gt;.tsbSaveAll)&lt;br /&gt;    .WithShortcut(Keys.Control &lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;|&lt;/span&gt; Keys.Shift &lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;|&lt;/span&gt; Keys.S)&lt;br /&gt;    .Disable();&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;br&gt;Dzięki zastosowaniu takiego mechanizmu udało mi się
zebrać całą logikę dotyczącą konfiguracji komend w jednym miejscu. Jak łatwo
zauważyć spełniłem większość wymagań postawionych wcześniej. Po pierwsze daną
komendę możemy przypisać do wielu elementów, możemy nadać skrót klawiszowy,
możemy oznaczyć jako operację synchroniczną (domyślnie komendy uruchamiane są w
trybie asynchronicznym) oraz możemy nadać komendzie początkowy stan. Wszystko w
jednym miejscu, wszystko czytelne do granic możliwości. Kontrastując to z
koniecznością odpalenia formatki w trybie projektowania, nawigowaniu po
elementach menu i sprawdzaniu w panelu właściwości czy podpięta jest
odpowiednia metoda i czy został dobrze zdefiniowany skrót klawiszowy widać jak
wiele zalet ma przedstawione tu podejście.&lt;br&gt;

&lt;h3&gt;Powłoka – application shell&lt;/h3&gt;

Chciałem, aby moja aplikacja składała się z okna
głównego (powłoki, ang. shell), które zawierać będzie podstawowe elementy takie
jak menu, pasek narzędzi z ikonami, pasek statusu oraz kontener na zakładki. W
odróżnieniu od &lt;a href="http://www.codeplex.com/fissum/"&gt;Fissum&lt;/a&gt; chciałem, aby &lt;a href="http://www.codeplex.com/tfsspotlight"&gt;TfsSpotlight&lt;/a&gt; pracował cały czas w
jednym oknie, a za pomocą zakładek pozwalał na otwieranie wielu elementów
jednocześnie. Ponadto chciałem dać użytkownikowi bezpośredni dostęp do zapytań
zdefiniowanych dla danego projektu, co zaowocowało powstaniem panelu bocznego.&lt;br&gt;&lt;br&gt;&lt;div align="center"&gt;&lt;img src="http://zine.net.pl/photos/posts_pictures/images/855/original.aspx"&gt;&lt;br&gt;&lt;/div&gt;&lt;br&gt;W moim odczuciu zadaniem powłoki jest
dostarczenie API pozwalającego na manipulację jej elementami. Dlatego właśnie
wydzieliłem z niej kilka serwisów (zarządzanie menu – komendami, zarządzanie
zakładkami, zarządzanie paskiem statusu oraz zarządzenie zakładkami).
Zdefiniowałem spójne interfejsy opisujące te usługi i dzięki temu dowolny
element systemu może mieć dostęp do elementów wspólnych.



&lt;h3&gt;Jak to wszystko ze sobą powiązać?&lt;/h3&gt;

&lt;p&gt;Każdy, kto choć trochę liznął wzorców
projektowych GoF, do tego problemu podszedłby z Singletonem pod pachą. Implementując
każdy z serwisów powłoki w postaci singletonu umożliwiamy innym elementom
systemu łatwy dostęp do instancji tych serwisów. Niestety singleton ma jedną
poważną wadę – bardzo, ale to bardzo mocno wiąże ze sobą klasy. Klasa, która
odwołuje się do elementów statycznych innej klasy jest z nią bardzo mocno
związana. Dlaczego nie chcemy takiego mocnego powiązania? Przecież miliony
programistów na całym świecie używają singletonów z powodzeniem. Otóż tak mocne
powiązania nie pozwalają efektywnie testować klas w izolacji, a przecież o to
właśnie chodzi w pisaniu testów jednostkowych – żeby poszczególne klasy
testować w izolacji. Zatem z mojego punktu widzenia i z punktu widzenia testów
jednostkowych silngleton wypada blado. Na marginesie należy dodać, że istnieją
narzędzia pozwalające testować takie sytuacje. Narzędziem takim jest &lt;a href="http://www.typemock.com/"&gt;TypeMock&lt;/a&gt;, które w środowisku praktyków TDD
jest dość &lt;a href="http://codebetter.com/blogs/jeremy.miller/archive/2008/01/18/typemock-isn-t-too-powerful-and-quot-designing-for-testability-quot-is-much-more-than-merely-mocking-anyway.aspx"&gt;kontrowersyjne&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Z pomocą przychodzi nam zasada odwracania
zależności (&lt;a href="http://www.objectmentor.com/resources/articles/dip.pdf"&gt;Dependency
Inversion Principle&lt;/a&gt;) i narzędzia pozwalające tę zasadę wprowadzać w życie,
czyli kontenery IoC (Inversion of Control). Osobiście w żadnym z moich
prywatnych projektów nie korzystałem jeszcze z tego typu narzędzi. Naturalnym
moim wyborem oczywiście jest &lt;a href="http://structuremap.sourceforge.net/Default.htm"&gt;StructureMap&lt;/a&gt; Jeremy’ego. W momencie, gdy piszę
ten tekst, na horyzoncie jest już &lt;a href="http://blogs.msdn.com/agile/archive/2008/01/14/diab-is-now-unity.aspx"&gt;Unity&lt;/a&gt;
ze stajni Microsoftu. David Hayden zrobił &lt;a href="http://www.pnpguidance.net/Screencast/UnityDependencyInjectionIoCScreencast.aspx"&gt;screencast&lt;/a&gt;
wprowadzający w jego użycie.&lt;/p&gt;

Zasada odwracania zależności mówi, że:&lt;br&gt;&lt;ol&gt;&lt;li&gt;

Moduły
     wysokiego poziomu nie powinny być zależne od modułów niższego poziomu. I
     jedne i drugie powinny być zależne od abstrakcji.&lt;/li&gt;&lt;li&gt;
Abstrakcje
     nie powinny być zależne od szczegółów. To szczegóły powinny być zależne od
     abstrakcji.&lt;/li&gt;&lt;/ol&gt;

Klasa A zależy od klasy B, gdy klasa A wymaga
obecności klasy B podczas kompilacji. Klasa A jest nazywana klientem natomiast
klasa B – usługą. O zależnościach między klasami pisał już &lt;a href="http://www.testability.de/"&gt;Stefan Jungmayr&lt;/a&gt;. Jungmayr
wyróżnił dwa rodzaje zależności:&lt;br&gt;&lt;ol&gt;&lt;li&gt;Zależność
     od typu (ang.
     dependency on a type) oznacza, że egzemplarz usługi musi implementować
     pewien dobrze określony typ, który może być zdefiniowany za pomocą
     interfejsu, klasy abstrakcyjnej bądź konkretnej klasy.&lt;/li&gt;&lt;li&gt;
Zakodowana
     zależność (ang.
     hard-wired dependency) oznacza, że egzemplarz usługi musi być konkretnego
     typu. Zwykle odwołanie do konstruktora usługi lub elementu statycznego
     prowadzi do powstania zakodowanej zależności.&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;Aby lepiej zobaczyć o czym mówi ta zasada,
przeanalizujmy przykład dwóch klas, które zaprojektowano bez jej uwzględnienia.
&lt;/p&gt;

&lt;p&gt;Przykład [C#] 2. Przykład dwóch klas
zaprojektowanych bez uwzględnienia zasady odwracania zależności.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;class&lt;/span&gt; RefreshQueryCommand : ICommand&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;void&lt;/span&gt; Execute()&lt;br /&gt;    {&lt;br /&gt;        ITabPresenter presenter &lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;=&lt;/span&gt; TabsService.Instance.GetActiveTab();&lt;br /&gt;        &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;if&lt;/span&gt; (presenter !&lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;=&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;null&lt;/span&gt;)&lt;br /&gt;        {&lt;br /&gt;            presenter.HandleRefresh();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;W przykładzie 2 mamy dwie klasy. Klasa RefreshQueryCommand
reprezentuje komendę odświeżającą zakładkę. Druga klasa – TabsService –
reprezentuje serwis obsługujący zakładki i została zaimplementowana w postaci
singletonu. Klasa RefreshQueryCommand ma zakodowaną zależność do klasy
TabsService. Jest to spowodowane odwołaniem się do statycznej właściwości tej
klasy. Taka sytuacja skutecznie utrudnia nam przetestowanie komendy w izolacji.
Testując ją chcielibyśmy sprawdzić dwa przypadki: jeżeli aktywna zakładka
istnieje operacja powinna zostać do niej oddelegowana natomiast jeżeli nie to
nic nie powinno zostać wykonane. Niestety bezpośrednie odwołanie do klasy
TabsService utrudnia nam odpowiednie przygotowanie kontekstu testu.&lt;/p&gt;

&lt;p&gt;Problemy te pomaga nam ominąć zasada odwracania zależności. Dzięki
niej pozbędziemy się niechcianego odwołania do statycznej właściwości klasy
TabsService. Zasada mówi, że tak moduły wysokiego poziomu jak i moduły niskiego
poziomu powinny zależeć od abstrakcji. Spróbujmy zatem wprowadzić abstrakcję do
przykładu 2. W module wyższego poziomu – klasie RefreshQueryCommand – chcemy
abstrahować od klasy TabsService. Opiszmy zatem klasę TabsService interfejsem.
Jego definicje przedstawiono w przykładzie 3. &lt;/p&gt;

Przykład [C#] 3. Wyodrębniony interfejs klasy TabsService.&lt;br&gt;&lt;br&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;interface&lt;/span&gt; ITabsService&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color: Green;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;/// &amp;lt;summary&amp;gt;Gets active tab&amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;br /&gt;    &lt;span style="color: Green;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;/// &amp;lt;returns&amp;gt;A presenter of a currently active tab&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;br /&gt;    ITabPresenter GetActiveTab();&lt;br /&gt;}&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;br&gt;Mając abstrakcje spróbujemy teraz uniezależnić komendę
od konkretnej realizacji serwisu. Chcemy uniknąć odwołania do statycznej
właściwości klasy TabsService&lt;i&gt; &lt;/i&gt;i pozwolić innej klasie podjąć decyzję o
konkretnej realizacji. Do tego celu stworzymy konstruktor, który jako parametr
będzie brał implementacje interfejsu ITabsService. Ulepszoną realizację klasy RefreshQueryCommand
wykorzystującą klasę TabsService poprzez interfejs przedstawiono w przykładzie
4. 

&lt;p&gt;Przykład [C#] 4. Ulepszona realizacja klas RefreshQueryCommand.&lt;/p&gt;&lt;p&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;class&lt;/span&gt; RefreshQueryCommand : ICommand&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;private&lt;/span&gt; ITabsService TabsService { get; set; }&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; RefreshQueryCommand(ITabsService tabsService)&lt;br /&gt;    {&lt;br /&gt;        &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;this&lt;/span&gt;.TabsService &lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;=&lt;/span&gt; tabsService;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;void&lt;/span&gt; Execute()&lt;br /&gt;    {&lt;br /&gt;        ITabPresenter presenter &lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;=&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;this&lt;/span&gt;.TabsService.GetActiveTab();&lt;br /&gt;        &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;if&lt;/span&gt; (presenter !&lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;=&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;null&lt;/span&gt;)&lt;br /&gt;        {&lt;br /&gt;            presenter.HandleRefresh();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Na czym polega odwrócenie zależności
przedstawione w przykładzie 4? Zauważmy, że w obecnej sytuacji klasa RefreshQueryCommand
nie zależy już od klasy TabsService, ale od interfejsu ITabsService. Odwrócenie
zależności polega na tym, że teraz klasa TabsService również zależy od interfejsu
ITabsService. Zatem zarówno klasy wyższego jak i niższego poziomu zależą od
abstrakcji a nie od konkretnych realizacji.&lt;/p&gt;

&lt;p&gt;Wśród narzędzi do zarządzania zależnościami
możemy przebierać. Począwszy od wspomnianego &lt;a href="http://structuremap.sourceforge.net/Default.htm"&gt;StructureMap&lt;/a&gt;, czy Unity, po &lt;a href="http://www.springframework.net/"&gt;Spring.Net&lt;/a&gt; oraz &lt;a href="http://www.castleproject.org/container/index.html"&gt;Castle Windsor&lt;/a&gt; (w
szczegóły wprowadzi Was Michał &lt;span class="nfakpe"&gt;Harasimowicz na XXII
spotkaniu wg.net – mam nadzieję, że pojawi się jakieś nagranie z tej sesji :).&lt;/span&gt;
Ja swój projekt oparłem o pierwszy z nich. Zasada działania jest banalnie
prosta. Najpierw musimy wrzucić do kontenera zabawki, aby potem pojawiły się
automatycznie, gdy będziemy ich potrzebować. Większość z kontenerów obsługuje
dwa typy wstrzykiwania implementacji: poprzez konstruktor oraz poprzez settery.
Ja osobiście wolę pierwszy ze sposobów, ale to kwestia wyboru.&lt;/p&gt;

&lt;p&gt;Zwykle, budując system luźno powiązany, opieramy
się na interfejsach definiujących kontrakty. Tak też zrobiłem w moim projekcie.
Każdy ze wspomnianych wcześniej serwisów powłoki opisany został interfejsem. Po
zarejestrowaniu konkretnych implementacji tych interfejsów w kontenerze mogę
bez ograniczeń z nich korzystać. Teraz, jeżeli dana klasa potrzebuje instancji
danego serwisu, to do jej konstruktora dodaję parametr o typie będącym interfejsem
tego serwisu. Zmienia się tylko sposób tworzenia instancji klas. Teraz zamiast
używać słowa kluczowego &lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;new&lt;/span&gt; &lt;/span&gt;&lt;/code&gt; skorzystać należy z kontenera, aby ten przygotował
na instancję żądanej klasy. Zadaniem kontenera jest znalezienie konstruktora i
wypełnienie go wszystkimi wymaganymi parametrami. Parametry te są rozpoznawane
na podstawie typów. W taki oto magiczny sposób zabawki automatycznie pojawiają
się w piaskownicy.&lt;/p&gt;

&lt;p&gt;Dzięki wykorzystaniu kontenera możemy budować
klasy, które wszystkie swoje zależności otrzymują z zewnątrz. Oznacza to, że
klasy te, same nie tworzą innych obiektów potrzebnych im do współpracy. Patrząc
z punktu widzenia testów jednostek potencjalny zysk jest wielki. Teraz, aby
daną klasę przetestować w izolacji, wystarczy, że utworzymy jej instancję z
zależnościami będącymi obiektami zastępczymi (ang. mock objects). Dzięki temu
testując możemy się skupić na kodzie tylko i wyłącznie testowanej klasy, a nie
usług, od których testowana klasa zależy.&lt;/p&gt;

&lt;p&gt;Pozbywając się singeltonów tracimy także kontrolę nad
tym ile instancji danej klasy powstanie. A co jeżeli rzeczywiście chcieliśmy,
aby dana klasa miała tylko jedną instancję? Otóż nic prostszego. Kontener daje
nam możliwość określenia w jaki sposób instancja danej klasy ma być
kontrolowana. Sposób wykonania tego z użyciem &lt;a href="http://structuremap.sourceforge.net/Default.htm"&gt;StructureMap&lt;/a&gt; opisał Jeremy na &lt;a href="http://structuremap.sourceforge.net/SingletonInjection.htm"&gt;stronie&lt;/a&gt;
projektu.&lt;/p&gt;&lt;p&gt;

&lt;/p&gt;&lt;h3&gt;Uruchamianie komend - command runner&lt;/h3&gt;

&lt;p&gt;Nie wiem jak pozostałe kontenery, ale &lt;a href="http://structuremap.sourceforge.net/Default.htm"&gt;StructureMap&lt;/a&gt;
ma bardzo fajną opcję rejestrowania instancji konkretnych klas pod pewną
ustaloną nazwą. Później, chcąc otrzymać tę nazwaną instancję możemy zapytać
kontener podając mu właśnie tę nazwę. Opcja ta jest o tyle sprytna, że pozwala
w banalny sposób spełnić pozostałe wymagania dotyczące obsługi komend, o której
pisałem wcześniej.&lt;/p&gt;

&lt;p&gt;Jak można było zauważyć wcześniej przy
konfiguracji komend, nigdzie nie podaliśmy ani klas, ani metod obsługujących
konkretne komendy. Jedyne co widzieliśmy to stała w postaci CommandsNames.ExitApplicationCommand.
Aby zbudować system elastyczny, który będzie spełniał wszystkie wymagania, nie
mogliśmy podczas konfiguracji komend podać ich konkretnych implementacji
chociażby dlatego, że implementacja danej komendy wcale nie musi znajdować się w
klasie dostępnej podczas kompilacji. Dlatego właśnie do identyfikacji komend
używamy stałych napisowych. Stałych tych używamy przy rejestracji implementacji
komend w kontenerze, a dzięki temu znając nazwę komendy możemy później zapytać
kontener o jej instancję.&lt;/p&gt;

&lt;p&gt;Przykład [C#] 5. Realizacja klasy uruchamiającej komendy.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;class&lt;/span&gt; CommandRunner : ICommandRunner&lt;br /&gt;{&lt;br /&gt;    &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;void&lt;/span&gt; Run(&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;string&lt;/span&gt; commandName)&lt;br /&gt;    {&lt;br /&gt;        Run(commandName, &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;true&lt;/span&gt;);&lt;br /&gt;    }&lt;br /&gt;    &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;public&lt;/span&gt; &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;void&lt;/span&gt; Run(&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;string&lt;/span&gt; commandName, &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;bool&lt;/span&gt; runInAsync)&lt;br /&gt;    {&lt;br /&gt;        var command &lt;span style="color: Red;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;=&lt;/span&gt; ObjectFactory.GetNamedInstance&amp;lt;ICommand&amp;gt;(commandName);&lt;br /&gt;        &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;if&lt;/span&gt; (runInAsync)&lt;br /&gt;        {&lt;br /&gt;            ThreadPool.QueueUserWorkItem(&lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;delegate&lt;/span&gt; { command.Execute(); }, &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;null&lt;/span&gt;);&lt;br /&gt;        }&lt;br /&gt;        &lt;span style="color: Blue;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;else&lt;/span&gt;&lt;br /&gt;        {&lt;br /&gt;            command.Execute();&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;Dzięki opisaniu komend wspólnym interesem
implementacja klasy CommandRunner staje
się banalnie prosta. Ponadto klasa ta też została opisana intefjesem i
zarejestrowana w kontenerze dzięki czemu nawet klasy z innych assembly mogą się
do niej odwoływać i wykonywać komendy.

&lt;/p&gt;&lt;p&gt;Ktoś z Was może zauważyć, że rzeczywiście staramy
się tworzyć system luźno powiązany a jednocześnie korzystamy z kontenera, który
dostarcza implementacji wymaganych typów za pomocą statycznej klasy
ObjectFactory (sic!). Prawda jest taka, że niestety kontener nie może
dostarczyć instancji samego siebie więc, akurat w tym przypadku nic nie możemy
zrobić. Dlatego trzeba ograniczać miejsca, gdzie będziemy się bezpośrednio
odwoływać do niego. Dobrą praktyką, pozwalającą łatwo testować takie odwołania,
jest umieszczenie tego odwołania w metodzie wirtualnej, którą na czas testów
będziemy mogli nadpisać i zwrócić z niej to, czego będziemy potrzebowali w
kontekście testu.&lt;/p&gt;

&lt;h3&gt;Podsumowanie&lt;/h3&gt;

&lt;p&gt;Zachęcam wszystkich, którzy jeszcze nie czytali
Serii, by w wolnej chwili to zrobili. Zachęcam w szczególności tych, którzy
podobnie jak ja, pracują z CAB na co dzień. Nie chodzi oczywiście o to by każdy
z nas budował własny CAB, ale by poznać techniki i koncepcje pozwalające pisać
lepszy kod i lepiej projektować systemy.&lt;/p&gt;

&lt;p&gt;Projekt, którego część tutaj opisałem, na pewno
nie jest idealny, ale jego budowanie przynosi mi wiele satysfakcji. Na pewno w
miarę wolnego czasu będę go rozwijał. Jeżeli komuś z Was się przyda to tym
lepiej. Wszystkie uwagi są oczywiście mile widziane, tak do samej architektury,
jak i funkcji programu.&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=856" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Visual Studio" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Visual+Studio/default.aspx" /><category term="TDD" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/TDD/default.aspx" /><category term="Testowanie oprogramowania" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Testowanie+oprogramowania/default.aspx" /><category term="Testy jednostek" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Testy+jednostek/default.aspx" /><category term="Mock Objects" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Mock+Objects/default.aspx" /><category term="Inżynieria oprogramowania" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/In_7C01_ynieria+oprogramowania/default.aspx" /><category term="Zasady projektowe" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Zasady+projektowe/default.aspx" /><category term="Tfs Spotlight" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Tfs+Spotlight/default.aspx" /><category term="Wzorce projektowe" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Wzorce+projektowe/default.aspx" /></entry><entry><title>Automatyczne podłączanie debuggera do procesu - Debugger.Launch();</title><link rel="alternate" type="text/html" href="http://zine.net.pl/blogs/nuwanda/archive/2008/03/03/automatyczne-podlaczanie-debuggera-do-procesu-debugger-launch.aspx" /><id>http://zine.net.pl/blogs/nuwanda/archive/2008/03/03/automatyczne-podlaczanie-debuggera-do-procesu-debugger-launch.aspx</id><published>2008-03-03T20:07:00Z</published><updated>2008-03-03T20:07:00Z</updated><content type="html">&lt;p&gt;Od jakiegoś czasu mieliśmy w projekcie jeden dość uciążliwy problem: MasterSolution z blisko trzydziestoma projektami. Jak nie trudno się domyślić było to powodem kilku problemów: długi czas kompilacji, problemy z wydajnością ReSharpera itd. Na szczęście od dziś mogę pracować już tylko z podzbiorem projektów, które mnie interesują, a to wszystko dzięki odpowiedniemu podziałowi MasterSolution. &lt;/p&gt;  &lt;p&gt;Rozbicie MasterSolution zmienia niestety sposób w jaki pracowałem do tej pory. Z modelu "Edytuj i F5 (debuguj)" muszę przejść na trochę bardziej skomplikowany "Edytuj, zbuduj, uruchom i podłącz debuggera". Nowy model, choć bardziej skomplikowany, nie będzie bardziej czasochłonny z wyjątkiem jednej monotonnej czynności. Za każdym razem, gdy aplikacja się załaduje, trzeba będzie podłączyć debuggera (Debug-&amp;gt;Attach debugger). Jest to o tyle monotonne, że trzeba się trochę naklikać. Pierwsza myśl jaka mi przyszła do głowy to: czy nie da się tego zrobić automatycznie?&lt;/p&gt;  &lt;p&gt;&lt;img style="margin:10px 10px 10px 0px;" src="http://zine.net.pl/photos/posts_pictures/images/833/original.aspx" align="left"&gt;&lt;/p&gt;  &lt;p&gt;Odpowiedź jest częściowo twierdząca, a dokładnie jest nim wywołanie jednej metody klasy &lt;a href="http://msdn2.microsoft.com/en-us/library/system.diagnostics.debugger.aspx" target="_blank"&gt;System.Diagnostics.Debugger&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;Debugger.Launch();&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;/p&gt;  

&lt;p&gt;To jedno wywołanie uruchamia debuggera i podłącza go do procesu, z którego pochodzi powyższe wywołanie. Rozwiącanie jest częściowe, ponieważ i tak zmusza nas do kilku kliknięć. Po wywołaniu powyższej linii zobaczymy takie oto znajome okienko wyboru debuggera (po lewej).&lt;/p&gt;

&lt;p&gt;Gdy po raz pierwszy mi się to okienko pokazało pomyślałem sobie, że mi się aplikacja wywaliła, ale pokrótce okazało się, że okno jest efektem pożądanym i pozwala wybrać debuggera, którego chcemy użyć. Po tym jak dokonamy wyboru, wybrana przez nas instancja Visual Studio przejdzie w tryb debug, tak jakbyśmy mieli ustawiony breakpoint. I voila! Podpięliśmy się do interesującego nas procesu.&lt;/p&gt;

&lt;p&gt;Bardzo mi się to rozwiązanie podoba, gdyż zaoszczędzi mi monotonnego klikania. Wywołanie debuggera można umieścić zaraz przy starcie aplikacji, lub też wstawiać w pożądanym miejscu, tylko na czas debugowania.&lt;/p&gt;

&lt;p&gt;Jest jeszcze jedna metoda klasy Debugger, o której chciałbym wspomnieć. &lt;/p&gt;&lt;code&gt;&lt;span style="color: Black;background-color: Transparent;font-family: Courier New;font-size: 11px;font-weight: normal;"&gt;Debugger.Break();&lt;/span&gt;&lt;/code&gt;&lt;br&gt;&lt;p&gt;Wywołanie to jest tożsame w ustawieniem breakpointa bezpośrednio w Visual Studio. Zatem czy programowe zatrzymywanie programu może nam się przydać?&lt;/p&gt;

&lt;p&gt;Otóż ciekawym zastosowaniem może być sytuacja, gdy w pewnym miejscu programu wiemy, że pewien jego stan jest nieprawidłowy. Jest wiele sposobów na takie miejsca. Możemy na przykład wypisywać komunikat na konsolę. Jeżeli jednak zachowanie to jest niepoprawne, to zwykle będziemy chcieli sprawdzić dlaczego aplikacja znalazła się w danym stanie. Prawdopodobnie wrócimy i ustawimy breakpoint by zbadać stan interesujących nas obiektów. Zatem dlaczego od razu w momencie wystąpienia tego zdarzenia nie zatrzymać programu?&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Zatem miłego debugowania!&lt;/p&gt;&lt;img src="http://zine.net.pl/aggbug.aspx?PostID=834" width="1" height="1"&gt;</content><author><name>nuwanda</name><uri>http://zine.net.pl/members/nuwanda.aspx</uri></author><category term="Visual Studio" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Visual+Studio/default.aspx" /><category term="Tips&amp;amp;Tricks" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Tips_2600_amp_3B00_Tricks/default.aspx" /><category term="Debugging" scheme="http://zine.net.pl/blogs/nuwanda/archive/tags/Debugging/default.aspx" /></entry></feed>
