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ę.