31/08/2010

Jeden duży projekt, czy może wiele małych 2

Home

Publikując artykuł na temat "małych" i "dużych" byłem ciekawy opinii innych ludzi z branży. Pierwszy odzew otrzymałem od kolegi ze studiów Marka Kozłowskiego i za jego zgodą przedstawiam go poniżej.

Marek po przeczytaniu postu wyraził zdziwienie, że taki temat w ogóle pojawił się w rozmowie, a rozbicie kodu na podprojekty jest dla niego czymś naturalnym i oczywistym. Stwierdził również, że nie wyobraża sobie pracy z jednym dużym projektem (Aspekt psychologiczny), na który składa się na przykład 100 plików z kodem zawierających wszystko począwszy od logiki biznesowej, przez konwersję obiektów biznesowych na obiekty bazy danych, po warstwę dostępu do danych oraz interfejsy i implementację obiektów zdalnych. Przytoczył również taki przykład.

Wyobraźmy sobie, że piszemy projekt, który ma realizować funkcjonalność zarządzania magazynem i składa się (w skrócie) z warstwy prezentacji, warstwy logiki biznesowej oraz warstwy dostępu do bazy danych. Jest to prosta aplikacja desktopowa, a implementacje poszczególnych warstw znajdują sie w osobnych projektach (Wymusza poprawną architekturę). Po roku klient prosi o przygotowanie tej samej aplikacji ale w wersji WWW. Co robimy? Budujemy interfejs użytkownika WWW ale dalej korzystamy z tego samej logiki biznesowej i warstwy dostępu do bazy danych (Łatwiejsze użycie tego samego kodu w innym projekcie biznesowym). Innymi słowy bierzemy sobie dwie dll'ki i dodajemy do referencji aplikacji WWW.

Jak by to wyglądało w przypadku jednego dużego projektu? Albo musielibyśmy kopiować kod między projektami co doprowadziło by do redundancji i konieczności poprawiania tych samych błędów w dwóch projektach albo dodalibyśmy do projektu interfejsu WWW referencję do jednej dużej biblioteki albo nawet pliku exe!!!, która zawierać będzie wiele niepotrzebnych rzeczy.

Na koniec Marek słusznie zauważył, że wraz z wprowadzeniem obiektowych języków programowania nastąpił czas tworzenia pakietów, bibliotek, które tworzą sieć powiązań oraz, że jest to wręcz intuicyjne i naturalne.

Opinia Marka jest więc zgodna z moim zdaniem, co mnie bardzo cieszy, ale kij ma zawsze dwa końce. W najbliższej przyszłości postaram się, więc znaleźć i zachęcić do wypowiedzi kogoś z przeciwnego obozu.

15/08/2010

Jeden duży projekt, czy może wiele małych

Home

Kilka dni temu dyskutowałem z kolegą na temat tego czy całość/większość kodu powinna być umieszczona w jednym dużym projekcie (jak On uważa) czy rozłożona pomiędzy mniejsze projekty (jak uważam Ja). Przez projekt rozumiem tutaj jednostkę organizacji kodu np.: csproj w Visual Studio. Duży projekt to dla mnie taki, który zawiera wszystko czyli: implementację GUI, logikę biznesową, interfejsy, struktury danych, klasy dostępu do danych itd. Może się to przekładać na liczbę linii kodu ale nie musi. Dalej, aby odróżnić projekt jako jednostkę organizacji kodu od projektu jako przedsięwzięcia biznesowego będę używał sformułowania projekt biznesowy dla tego drugiego.

Wracając do wspomnianej dyskusji to zakończyła się impasem ponieważ żaden z nas nie zmienił swojego zdania. Spowodowała jednak, że postanowiłem jeszcze raz gruntownie przemyśleć sprawę. W ten sposób powstała poniższa lista zalet i potencjalnych wad małych projektów. Listy te są skonstruowałem w ten sposób, że na początki znajdują się najważniejsze/najpoważniejsze zalety i wady.

Zalety
  • Łatwiejsze użycie tego samego kodu w innym projekcie biznesowym. Przy dużych projekcie również jest to możliwe ale oznacza dodanie referencji do wielu innych niepotrzebnych w danym kontekście rzeczy czyl bałagan.
  • Wymusza poprawną architekturę. Na przykład jeśli GUI, logika biznesowa i dostęp do bazy danych znajdują się w innych projektach to projekt z GUI będzie miał referencję na projekt z logiką biznesową ale nie na odwrót ponieważ referencje cykliczne nie są dozwolone.
  • Ułatwia dalszy rozwój. Wyobraźmy sobie sytuację, w której projekt biznesowy jest na etapie stabilizacji i zbliża się termin oddania. Z drugiej strony istnieje konieczność dalszego rozwijania jakiejś jego części. Po wdrożeniu klient zapewne będzie zgłaszał błędy. Po jakimś czasie może pojawić się potrzeba złączenia (merge) dwóch (lub więcej) ścieżek rozwoju czyli przeniesienia poprawek błędów z wersji produkcyjnej na rozwojową i dodanie nowych funkcji z wersji rozwojowej do wersji produkcyjnej. W przypadku małych projektów łatwiej zorientować się co się zmieniło, co trzeba przenieść, a co nie, czy merge spowoduje jakieś błędy itd.
  • Aspekty psychologiczne. Nie przytłacza liczbą plików i folderów. Łatwiej zorientować się "o co biega" - łatwiej jest pracować z małym projektem odpowiedzialnym za jedną konkretna rzecz niż z dużym odpowiedzialnym za dziesiątki różnych rzeczy.
  • Łatwiejsze utrzymanie testów jednostkowych. W przypadku podejścia, w którym testy jednostkowe są umieszczane w innym projekcie niż testowany kod będzie mieli kilka małych projektów z testami jednostkowymi. W podejściu przeciwstawnym w danym projekcie będziemy mieli ograniczoną liczbę testów dotyczących tego jednego projektu. Należy to przeciwstawić dużym projektom gdzie powstanie nam albo kolejny duży projekt na testy jednostkowe albo bardzo duży projekt zawierający wszystko plus jeszcze testy jednostkowe tego wszystkiego.
  • Prostsze i łatwiejsze w utrzymaniu pliki konfiguracyjne. Ma to znaczenie jeśli używamy technologii wymagających wielu plików konfiguracyjnych, najczęsciej dokumentów XML np.: Spring.
  • Krótsza kompilacja. Jeśli nie zmieniły się interfejsy to można skompilować pojedynczy, mały projekt.
  • Wykonywanie brancha małego projektu trwa szybciej
Wady
  • Defragmentacja pamięci. Pisałem o tym już wcześniej. Problem polega na tym, że przy ładowaniu do pamięci moduły nie są ustawiane jeden po drugim ale są umieszczane w "losowo" wybranych miejscach co powoduje poszatkowanie pamięci. W większości wypadków nie jest to problemem ale na przykład przy alokacji dużej bitmapy potrzebny jest ciągły obszar pamięci. W wyniku defragmentacji system będzie dysponował dostatecznie dużą ilością pamięci ale nie w jednym kawałku. Problem nie występuje na systemach 64-bitowych, które są coraz powszechniejsze.
  • Dłuższe uruchamianie VS. Jeśli utworzymy Solution i dodamy do niego kilkadziesiąt projektów to jego otwieranie będzie trwać długo. Z drugiej strony czy aby na pewno praca z kilkudziesięcioma projektami ma sens?
  • Konieczność zarządzania dużą liczbą referencji. Każdy lub prawie każdy projekt będzie miał kilka lub więcej referencji do innych projektów. Zgadzam się, że może to być problem. Z drugiej strony pracowałem przy rozwijaniu naprawdę dużego systemu składającego się z kilkunastu podsystemów, każdy z kilkunastoma małymi projektami z czego część była współdzielona i radziliśmy sobie.
  • Trudniejsza instalacja. Wynika to z dużej ilości bibliotek dll, które powstają w wyniku kompilacji wielu małych projektów. Mogą również wystąpić konflikty wersji. Podobnie jak wyżej zgadzam się, że jest to możliwe ale podobnie jak wyżej przeciwstawiam tej wadzie swoje doświadczenie, które mówi co innego.
  • Dłuższa kompilacja całego systemu. Zgadzam się, przy wielu małych projektach kompilacja wydłuży się i to znacznie. Jednak i tutaj dołożę swoje trzy grosze. Jak często istnieje potrzeba przekompilowania całego systemu? Jeśli w danym momencie pracujemy z kilkoma konkretnymi projektami to po co wykonywać build wszystkich pozostałych? Ma to sens, jeśli zostały zmienione klasa, struktury lub interfejsy używane w wielu innych projektach.
  • Problemy z konfiguracją tych samych rzeczy w różnych projektach. Przy odpowiedniej architekturze systemu i zastosowaniu odpowiednich wzorców projektowych (singleton, fabryka) nie jest to dla mnie żaden problem.
  • Zwiększony czas uruchamiania aplikacji. Tak ale o ułamki sekund.
Ponieważ w powyższych wyliczeniach powoływałem się na to, że coś trwa tyle, a tyle przytoczę bardzo fajne zestawienie porównujące czasy kompilacji, ładowawania solution'a przez Visual Studio itd. dla różnej liczby projektów, które pojawiło sie w dyskusji na portalu stackoverflow - odpowiedź autorstwa jerryjvl'a.

Reasumując jestem przekonany, że zalety dzielenia kodu na małe projekty przeważają potencjalne wady. Co więcej uważam, że problemy z małymi projektami wynikają głównie ze złego podejścia i przyjęcia nieodpowiednich rozwiązań takich jak: budowanie wszystkich projektów zamiast kilku wybranych lub z dogmatycznego trzymania się małych projektów czyli bezrefleksyjnego tworzenia małego projektu na wszystko co się da. Co za dużo to jednak nie zdrowo :)

Na koniec jedna uwaga. Na początku projektu biznesowego może się wydawać, że lepiej trzymać wszystko w dużym projekcie bo tak prościej, bo kodu mało. Ale o ile nie jest to rzeczywiście malutki projekt biznesowy to szybko okaże się, że podzielenie kodu na mniejsze projekty będzie wymagać tyle pracy, że nikomu nie będzie się tego chciało zrobić.

12/08/2010

SlickRun

Home

Dwa dni temu kolega polecił mi program SlickRun, który spodobał mi się tak bardzo, że nie wiem jak radziłem sobie bez niego. SlickRun umożliwia tworzenie poleceń/komend, przez twórców zwanych "magicznymi słowami", do uruchamiania aplikacji, wyświetlania katalogów i stron WWW. Niby nic wielkiego ale równocześnie po zainstalowaniu programu w prawym dolnym rogu ekranu pojawia sie malutkie okienko (niezauważalne w codziennej pracy), do którego możemy wpisywać te komendy. Co ważne SlickRun jest na tyle mądry, że potrafi podpowiadać komendy, a więc nawet nie trzeba znać ich pełnych nazw. Teraz zamiast tworzyć skróty na pulpicie lub każdorazowo przeszukiwać dysk w poszukiwaniu potrzebnego katalogu mam kilkanaście intuicyjnych komend. Dla mnie super program, którego potrzebowałem nie zdając sobie z tego sprawy :)

09/08/2010

Logi IntelliTrace bez tajemnic

Home

IntelliTrace to jedno z najciekawszych narzędzi jakie pojawiło się w Visual Studio 2010. Dla tych, którzy jeszcze go nie znają w skrócie służy do nagrywania działania programu w celu późniejszej jego analizy. Nagrywane są wywołania metod oraz tzw. zdarzenia diagostyczne czyli ważne punkty w historii działania programu np.: nawiązanie połączenia z bazą danych, wykonanie zapytania do bazy danych, załadowanie modułu czy uruchomienie wątku. Visual Studio 2010 udostępnia kilka sposobów ich analizowania.

Listę wszystkich zarejestrowanych zdarzeń znajdziemy w oknie IntelliTrace Events View. Możemy je przefiltrować na podstawie wątku w jakim wystąpiły lub kategorii do jakiej należą. W oknie Calls View zobaczymy natomiast zdarzenia wplecione pomiędzy wywołania metod. Na tej podstawie łatwo zorientować się co doprowadziło do ich wygenerowania. Możliwości są więc całkiem spore ale jednak ograniczone. Z pomocą przychodzi IntelliTrace API, które pozwala na programową analizę logów IntelliTrace (*.iTrace).

Do czego może się to przydać? Do głowy przychodzi mi kilka pomysłów. Pierwszy z brzegu to program to graficznej wizualizacji zdarzeń. Dalej analiza logu pod kątem poprawności działania programu na podstawie analizy częstotliwości występowania poszczególnych typów zdarzeń. Sądzę, że w podobny sposób można analizować wydajność aplikacji. Może się również zdarzyć, że dane prezentowane przez Visual Studio będą dla nas zbyt ubogie i jedynym wyjściem będzie napisanie własnego analizatora.

Aby skorzystać z interfejsu programistycznego IntelliTrace powinniśmy zacząć od dodania do projektu referencji do biblioteki Microsoft.VisualStudio.IntelliTrace.dll. Znajdziemy ją w standardowym oknie Add Reference w zakładce .NET. Właściwą analizę logu rozpoczynamy od jego wczytania przy pomocy klasy IntelliTraceFile.
using (IntelliTraceFile file = new IntelliTraceFile(pathToIntelliTraceLog))
{
...
}
Klasa ta ma tylko jedną interesującą właściwość o nazwie Processes. Przy jej pomocy uzyskujemy dostęp do listy procesów, dla których log zawiera jakieś zdarzenia. W praktyce lista ta będzie zawierać tylko jedną pozycję. Proces reprezentowany jest przez klasę IntelliTraceProcess, która jest bardziej interesująca. Dzięki niej możemy dowiedzieć się o modułach załadowanych do procesu (właściwość Modules), uruchomionych wątkach (właściwość Threads) czy środowisku w jakim został uruchomiony proces (właściwość SystemInformationEvent). Wątki reprezentowane są przez klasę IntelliTraceThread.

Najważniejsze jest to, że klasy IntelliTraceProcess oraz IntelliTraceThread pozwalają na dobranie się do zdarzeń IntelliTrace. Służą do tego odpowiednio metody IntelliTraceProcess.CreateProcessChain oraz IntelliTraceThread.CreateThreadChain. Pierwszej z nich użyjemy jeśli interesują nas "łańcuchy zdarzeń" globalne dla całego procesu, a drugiej jeśli "łańcuchy zdarzeń" specyficzne dla danego wątku. Druga kategoria jest mniejsza i zawiera tylko trzy pozycje: ThreadChain, ThreadStreamChain oraz ThreadCheckpointChain. Warto zaznaczyć, że na tym poziomie wszystko jest zdarzeniem czyli zarówno wspomniane zdarzenia diagnostyczne jak i wywołania metod będą obsługiwane w taki sam sposób. Różnica polega tylko na innej klasie zdarzenia. Poniżej przykład wywołania metody IntelliTraceThread.CreateThreadChain, które zwróci łańcuch ze zdarzeniami odpowiadającymi min.: wywołaniu metod:
Chain chain = thread.CreateThreadChain<ThreadChain>();
Teraz nie pozostaje nic innego jak odczytać i przetworzyć kolejne zdarzenia:
EventToken eventToken = chain.FirstValidToken;
while (eventToken != chain.AfterLastToken)
{
  IntelliTraceEvent ev = chain.GetEvent(eventToken);

  //...

  eventToken = chain.GetNextToken(eventToken);
}
Klasa IntelliTraceEvent jest klasą bazową dla wszystkich typów zdarzeń, a więc aby uzyskać dokładniejsze dane należy wykonać rzutowanie na jeden z wydziedziczonych z niej typów.

Warto zwrócić uwagę na jedną szczególnie interesująca klasę z perspektywy analizy logu. Mam tutaj na myśli klasę DiagnosticStreamChain przy pomocy, której uzyskamy dostęp do tzw. zdarzeń diagnostycznych. Przykładem zdarzenia diagnostycznego jest wspomniane wcześniej wykonanie zapytania do bazy danych, naciśnięcie przycisku, wypisanie czegoś na konsolę czy przeładowanie strony w aplikacji ASP.Net. Zdarzeń tych jest dużo, a pełną ich listę znajdziemy w ustawieniach Visual Studio: Tools->Options->IntelliTrace->IntelliTrace Events. Możliwość analizy tego typu zdarzeń jest tym ciekawsza jeśli uwzględnimy możliwość rozszerzania tej listy o swoje własne zdarzenia! Zainteresowanych odsyłam do moich dwóch wcześniejszych tekstów Własne zdarzenia IntelliTrace oraz Własne zdarzenia IntelliTrace 2 .

Użycie IntelliTrace API nie jest skomplikowane ale nasuwa się pytanie:

Gdzie znaleźć dokumentację, szczegółowy opis poszczególnych klas reprezentujących zdarzenia, "łańcuchy zdarzeń" itd.?

Niestety ale dostępna dokumentacja na ten temat jest bardzo uboga. Znajdziemy w niej co prawda listę typów zdarzeń czy "łańcuchów" ale bez jakiegokolwiek opisu. Chyba, że opisem można nazwać coś takiego: "Describes a DiagnosticEvent." (dla klasy DiagnosticEvent) albo coś takiego: "Describes the ThreadCreateEvent." (dla klasy ThreadCreateEvent). Dla mnie to masło maślane. Konie z rzędem temu kto domyśli się na podstawie dostępnych informacji czym na przykład różnią się klasy ThreadChain oraz ThreadStreamChain? Można do tego dojść analizując kod biblioteki przy pomocy reflektora lub eksperymentalnie ale wymaga to dodatkowego wysiłku.

W zgłębianiu IntelliTrace API pomocny okaże się natomiast bardzo fajny projekt o nazwie iTraceReader, który znajdziemy tutaj. iTraceReader to nakładka na surowe IntelliTrace API, która ułatwia wykonanie wielu czynności. Zapoznając się z tym projektem można nauczyć się bardzo wielu rzeczy. Wszystkich, których ten temat zaciekawił zachęcam do bliższego przyjrzenia się.

28/07/2010

Używanie IntelliTrace poza Visual Studio 2010!

Home

W oficjalnej dokumentacji IntelliTrace można przeczytać, że narzędzie to jest dostępne tylko i wyłącznie z poziomu środowiska Visual Studio 2010. To bardzo źle ponieważ z góry przekreśla użycie IntelliTrace do diagnozowania błędów u klienta. Wyobraźmy sobie, że dostajemy zgłoszenie trudnego do powtórzenia błędu. Czy nie byłoby wspaniale uruchomić aplikację w środowisku produkcyjnym, pod kontrolą IntelliTrace i w razie wystąpienia błędu poprosić klienta o przekazanie nam plików z logami (*.iTrace). Nie wszystko jest jednak stracone. Pomimo, że nie jest to oficjalnie wspierane można uruchomić IntelliTrace z poza Visual Studio 2010!

Na początek należy odpowiedzieć na pytanie czym jest IntelliTrace: wątkiem działającym w ramach procesu Visual Studio 2010, niezależnym procesem, a może jeszcze czymś innym? Odpowiedź na to pytanie można uzyskać przy pomocy programu Process Explorer. Na poniższym obrazku widać fragment drzewa procesów dla węzła devenv.exe (czyli dla Visual Studio).



Pod nim znajdziemy węzeł odpowiadający debug'owanemu programowi oraz węzeł IntelliTrace, a w jego właściwościach ścieżkę z jakiej jest uruchamiany (VS_2010_INSTALL_DIR to katalog instalacyjny środowiska) np.:

VS_2010_INSTALL_DIR\Team Tools\TraceDebugger Tools\IntelliTrace.EXE

Reasumując IntelliTrace to niezależny proces, który komunikuje się z Visual Studio przy użyciu jakiegoś mechanizmu IPC (ang. Inter-process communication ). Jeśli spróbujemy go uruchomić z linii poleceń na ekran zostanie wypisana lista dostępnych komend. Z kilku dostępnych interesująca jest komenda launch, która zgodnie z podanym opisem uruchamia podaną aplikację i rozpoczyna jej monitorowanie. Po wpisaniu w linię poleceń: IntelliTrace help launch uzyskamy bardziej szczegółowe informacje na jej temat.

Z dostępnych parametrów najważniejsze to /logfile (w skrócie /f) przy pomocy, którego wskazujemy docelowy plik z logiem oraz /collectionplan (w skrócie /cp) przy pomocy, którego wskazujemy plik z planem działania, konfiguracją IntelliTrace. Skąd go wziąć? Domyślny plik używany przez Visual Studio 2010, a który jest dokumentem XML, znajdziemy w lokalizacji:

VS_2010_INSTALL_DIR\Team Tools\TraceDebugger Tools\en\CollectionPlan.xml

Mając już wszystkie elementy układanki, spróbujmy uruchomić jakiś program pod kontrolą IntelliTrace ale bez pomocy Visual Studio. Poniżej pokazano takie przykładowe wywołanie. W miejsce LOG_PATH należy wstawić ścieżkę do wyjściowego pliku z logiem (najlepiej nadać mu rozszerzenie iTrace), w miejsce COLLECTION_PLAN_PATH ścieżkę do pliku CollectionPlan.xml (może nazywać się inaczej i mieć inne rozszerzenie). PROGRAM_PATH to oczywiście ścieżka do programu jaki chcemy monitorować.

VS_2010_INSTALL_DIR\Team Tools\TraceDebugger Tools\IntelliTrace.EXE launch /f:LOG_PATH /cp:COLLECTION_PLAN_PATH PROGRAM_PATH

Po wykonaniu tego polecenia we wskazanym przez nas katalogu pojawi się plik z logiem. Jeśli będzie miał rozszerzenie iTrace to po dwukliku uruchomi się Visual Studio 2010. W tym momencie czeka nas jednak niemiła niespodzianka ponieważ okaże się, że plik z logiem nie zawiera żadnych informacji tak jakby program nie był monitorowany.



Na brakujący element układanki naprowadzi nas ponownie Process Explorer, który umożliwia podejrzenie parametrów wywołania programu. Interesuje nas oczywiście proces IntelliTrace. Jeśli odczytamy jego parametry wywołania to dowiemy się, że został uruchomiony z komendą run, a nie launch. Różnica polega na tym, że run w przeciwieństwie do launch uruchamia tylko proces monitorujący i nie wskazuje konkretnej aplikacji do monitorowania. To jaka aplikacja będzie monitorowana zależy od Visual Studio 2010. Kolejna, ważna dla nas różnica to wartość parametru /cp, który nie wskazuje na domyślny plan wykonania ale na jakiś "dziwny" plik np.:

C:\Users\user\AppData\Local\Microsoft\VisualStudio\10.0\TraceDebugger\Settings\frueouq2.vfs

Jest to plik tymczasowy, a jego nazwa generowana jest w sposób losowy. Jeśli spróbujemy go otworzyć to okaże się, że bardzo przypomina domyślny plik CollectionPlan.xml. Dokładniej mówiąc schemat XML jest taki sam, inne są natomiast wartości atrybutów, liczba węzłów itd. czyżby brakujący element układanki? Tworzymy, więc kopię tego pliku, wskazujemy go w parametrze \cp i ponownie wydajemy wcześniej pokazane polecenie. Tym razem po otworzeniu wynikowego pliku z logiem zobaczymy listę monitorowanych wątków, drzewo wywołań itd.



Pełny sukces? Niestety nie do końca. Po bliższym przyjrzeniu się zauważymy, że mamy dostęp tylko do drzewa wywołań, a brakuje informacji o zdarzeniach diagnostycznych (ważne punkty w historii wykonania programu np.: wykonanie zapytania do bazy danych). Tego ograniczenia nie udało mi się jeszcze ominąć. Przypuszczam, że przy uruchamianiu IntelliTrace przez Visual Studio 2010 do programu przekazywane są jakieś dodatkowe opcje. Być może do rejestrowania zdarzeń potrzebne jest Visual Studio, a logger IntelliTrace nie potrafi tego robić?

Najważniejsze jest jednak to, że już teraz IntelliTrace można uruchomić niezależnie od środowiska programistycznego, chociaż z pewnymi ograniczeniami. Biorę to za dobrą monetę i mam nadzieję, że pełne wsparcie dla uruchamiania IntelliTrace z poza Visual Studio jest w planach Microsoft'u i pojawi się, jeśli nie z którymś service pack'iem to w kolejnej edycji Visual Studio.

Artukuł opublikowałem również w serwisie software.com.pl.