O ile mądrzejszy będę, gdy się posłużę NDependem.
Do niedawna nie miałem pojęcia o czymś takim jak NDepend. Co prawda szperając po sieci raz na jakiś czas trafiałem na informacje o metrykach, ale jakoś niespecjalnie mnie to pociągało, ani nie odczuwałem potrzeby, aby „szukać dziury w całym” czyli we własnym kodzie. Zakładałem, że jest wystarczająco prawidłowy, a ja nie mam aż tyle czasu, aby cyzelować go do perfekcyjnej postaci.
Sytuacja zmieniła się, kiedy niedawno nadarzyła się okazja zostania obdarowanym właśnie ta aplikacją. Oczywiście był pewien warunek (kto korzysta z .NET-o-maniaka na pewno wie o co chodzi) – należało owo narzędzie opisać. Co prawda wśród przysłów (które – bezsprzecznie – są mądrością narodu) znajduje się takie, które mówi, żeby nie zaglądać darowanemu koniowi w zęby, ale skoro taki był warunek darowizny, założyłem, że to przysłowie traci w takiej sytuacji swoją moc. Przez myśl przemknęło mi jeszcze inne przysłowie, tj. żeby nie kopać się koniem, ale nie przewidywałem, że NDepdend mógłby mnie – nomen omen – wykończyć ;).
Postąpiłem zatem zgodnie z otrzymaną instrukcją: pobrałem plik instalacyjny (rozmiar 10.70MB), który okazał się zwykłym ZIP-em, a następnie rozpakowałem w dowolne miejsce. I tu lekkie zdziwienie – tym miejscem nie mógł być folder Program Files, ze względu na jego szczególną ochronę przez system operacyjny (Vista, Win7), a wszak jest to naturalne miejsce, w którym umieszcza się oprogramowanie, więc eliminowanie go nasuwało podejrzenia, że NDepend nie do końca przestrzega reguł narzucanych przez system operacyjny (najprawdopodobniej coś w nim zapisuje). Trudno, miałem dedykowany folder na takie wynalazki, więc go użyłem.
Oprócz rozpakowania aplikacji do folderu, należało w nim także umieścić plik z licencją (jest to zwyczajny plik XML).
Po tych zabiegach mogłem już cieszyć się aplikacją. Uruchomiłem ją więc (VisualNDepend.exe) i moim oczom ukazał się następujący widok:
Na marginesie – zabawne, ale ikona programu kojarzy mi się z logo Lidla ;).
Ekran startowy (zaprojektowanym w stylistyce Visual Studio) podzielony jest (patrząc od lewej, do prawej i w dół) na pięć obszarów: projektu, niezbędnej pomocy, mechanizmów instalacji w środowisku Visual Studio oraz Reflector, monitoringu aktualności aplikacji i na koniec zakładek z błędami oraz systemem pomocy, który po rozwinięciu prezentuje się następująco:
Jeśli zaś chodzi o instalację narzędzia w Visual Studio, to realizujące je okno wygląda tak:
Jest to więc okno opcji pozycjonowane na odpowiedniej z nich. Widać, że NDepend umożliwia swoją integrację z wersjami: 2008, 2010 i 2012. Tymczasowo postanowiłem nie instalować narzędzia w VS, skoro mam dedykowany program, wykorzystam właśnie go.
Jak wspominałem moja wiedza na temat metryk nie jest imponująca, ale to nie przeszkodziło mi skorzystać z NDepend. Postanowiłem przetestować go na czymś prostym. Była to banalna aplikacja WinForms, która wykonywała procedurę składowaną na serwerze SQL 2008 i prezentowała jej wyniki. Oto jej kod:
public partial class Form1 : Form { private SqlConnection Connection = new SqlConnection(); private DataSet Results; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Calendar(); } private void Calendar() { if (Connection.State != ConnectionState.Open) { Connection.ConnectionString = "Persist Security Info=False;Data Source=.\\SQLEXPRESS2K8R2;Initial Catalog=TryDB;Integrated Security=SSPI;"; Connection.Open(); } SqlCommand cmd = new SqlCommand("Kalendarz", Connection); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@odDaty", SqlDbType.Date).Value = new DateTime(2012, 01, 01); cmd.Parameters.Add("@doDaty", SqlDbType.Date).Value = new DateTime(2012, 01, 31); SqlDataAdapter da = new SqlDataAdapter(cmd); Results = new DataSet(); da.Fill(Results); dataGridView1.DataSource = Results.Tables[0]; } }
W obszarze projektów NDepend prezentowane są te, których używano ostatnio w Visual Studio. Można więc w prosty sposób (zazwyczaj bowiem sprawdzamy bieżące projekty) wskazać, który projekt ma podlegać analizie.
Po wskazaniu przeze mnie projektu, NDepend natychmiast przystąpił do jego analizy i zaprezentował wyniki w następujący sposób:
oraz za pomocą strony WWW.
Widzimy tutaj w lewym górny rogu (Queries and Rules Edit) – kod reguły sprawdzającej (dotyczący reguł z zakresu architektury i warstw – Architecture and Layering) zgodny z zapytaniem wskazanym w drugiej tabelce Eksploratora zapytań i reguł (Query and Rules Explorer), pod spodem klasa, którą reguła sprawdzała (bywa, że ten obszar jest pusty). Po prawej znajduje się obszar, w którym można obejrzeć schemat zależności, macierz zależności, metryki i właściwości projektu. Pod nim znajduje się – wspomniany już Eksplorator zapytań i reguł (Query and Rules Explorer), zawierający dwie tabele: grup i reguł wchodzących w skład wybranej w pierwszej tabeli grupy.
Trzeba przyznać, że analiza wykonała się dość szybko – niemniej nie jest to żaden znaczący wskaźnik szybkości narzędzia, bo projekt był mały i banalny.
Na zaprezentowanym zrzucie okna widać wyraźnie, że NDepend dopatrzył się nieprawidłowości nawet w tak prostym projekcie. Zatem po kolei.
Pierwsze zastrzeżenie miał on w kwestii projektowania zorientowanego obiektowo, a brzmiało ono: „Class with no descendant should be sealed if possible”, czyli klasie bez potomków powinno się – o ile to możliwe – ustawić blokadę dziedziczenia (dotyczyło to klasy Form1). Hm, trochę to dyskusyjne, wszak niekoniecznie brak potomków musi takie zachowanie implikować, może po prostu potomkowie jeszcze nie powstali – wszak może być to klasa biblioteczna stworzona właśnie w celu dziedziczenia. Oczywiście w tym przypadku nie było mowy o dalszym dziedziczeniu, ale i tak nie miało sensu wprowadzać sealed do definicji klasy. Co ciekawe, to stwierdzenie „o ile to możliwe”, jest na tyle nieprecyzyjne, że dyskwalifikuje samą regułę. Na szczęście regułę tę (jak i dowolną inną) można wyłączyć z procesu analizy i nie będzie więcej generować fałszywych alarmów.
Kolejne zastrzeżenia dotyczyły projektu (Design) i wskazywały, że klasa, która zawiera pola implementujące interfejs IDisposable, także powinna taki interfejs implementować. Oczywiście znowu chodziło o klasę Form1, więc z jej punktu widzenia nie miało to znaczenia, albowiem jej instancja istnieje przez cały czas pracy programu. Niemniej – było to spostrzeżenie korzystne z punktu widzenia edukacyjnego – zalecenie było logiczne, a ja właściwie nie zdawałem sobie sprawy z akurat tej konsekwencji stosowania w klasie typów implementujących interfejs IDisposable ;).
Drugie z zastrzeżeń – z tej samej kategorii – dotyczyło unikania wydzielania przestrzeni nazw dla jedyne kilku typów. Owszem, ma ono sens, niemniej bywają przypadki, kiedy to zastrzeżenie będzie należało zignorować (przestrzeń nazw może mieć tylko jedną klasę, bo to wynika z logicznego – modularnego podziału oprogramowania).
Ostatnie zastrzeżenie dotyczyło rozmiaru instancji klasy, który nie powinien być zbyt duży. „Zbyt” czyli przekraczać 64 bajty. To wartość powyżej, której następuje (wg opisu w regule) spadek wydajności (oczywiście zależny od ilości takowych instancji). Jak można się domyślić źródłem ostrzeżenia znowu była klasa Form1, ale co się dziwić – ona po prostu musi taka być. Na szczęście z jej powodu spadek wydajności nie groził, albowiem jej instancja miała być tylko jedna. W tym momencie zastanowiło mnie, czy NDepend byłby w stanie rozpoznać, że klasa jest singletonem i wówczas nie zgłaszać zastrzeżeń? E, chyba za bardzo się rozmarzyłem, ale jak będę miał nadmiar czasu ;), to sobie to sprawdzę.
Kolejna para zastrzeżeń dotyczyła architektury i warstw. Tu nie było wątpliwości, dotyczyły one bezpośredniego użycia typów DB i warstwy DAL.
Jeśli chodzi o kategorię widzialności, to było tu tylko zastrzeżenie do (a jakże 😉 ) klasy Form1, z jej widzialnością publiczną – sugerowana była internal. Oczywiście w innym przypadku byłoby to zasadne, ale w tym, kiedy to VS nadaje taki zakres widoczności, nie było sensu tego zmieniać. Reguła ma chronić przed uzewnętrznianiem poza dany zestaw elementów, które są wykorzystywane jedynie na jego poziomie. Chociaż i tutaj należałoby mieć świadomość, że dana klasa może być wykorzystywana tak wewnątrz jak i na zewnątrz zestawu, więc fakt, że wykorzystano ją wewnątrz nie kwalifikuje jej widzialności do wewnętrznej.
Zastrzeżenia zgłoszono jeszcze w trzech kategoriach:
- czystości, niezmienności i efektów ubocznych – 3 zastrzeżenia;
- konwencji nazewniczych – 8 zastrzeżeń;
- użycia bibliotek .NET – 1 zastrzeżenie.
W pierwszej kategorii tylko jedno zastrzeżenie dotyczyło zaprezentowanego tutaj kodu. Wg NDepend pole Connection powinno być R/O – co ma swoje uzasadnienie, jeśli spojrzeć na kod jako coś ukończonego – co jak wiadomo, jest mało prawdopodobne ;). Możliwe, że pole to mogłoby w pewnych przypadkach ulegać zmianom, więc tak ograniczone pole miałoby krótką karierę. Zresztą – to była kolejna reguła z dopiskiem „o ile to możliwe”. Pozostałe dwa zastrzeżenia dotyczyły kodu generowanego przez VS, więc nie bardzo był sens, aby je analizować. I tak nie miałbym możliwości ich zmiany.
W przypadku drugiej kategorii (konwencje nazewnicze – Naming Conventions) doznałem mieszanych uczuć, albowiem sugerowana było poprzedzanie przedrostkiem pól klasy – czyli coś co jest już passe. W dodatku sugerowane były przedrostki z podkreśleniami, zatem nie dość, że notacja węgierska (chodziło m.in. o uwidocznienie zakresu zmiennej lub jej typu), to jeszcze niepotrzebnie rozbudowana. Czyżby były to indywidualne preferencje autorów NDepend?
Ostatnia z kategorii dotyczyła zestawu System, który – wg analizatora – powinien być oznaczony jako CLS Compliant. Niestety – nie bardzo miałem na to wpływ.
We wszystkich przypadkach zastrzeżeń brakowało mi jednak jednego – każdorazowego przyjrzenia się winowajcy, żebym nie musiał się zastanawiać, kto danego zastrzeżenia jest podmiotem i wyszukiwać go w kodzie. Bywało, że kiedy próbowałem dwukliknąć w obszarze Query and Rules Edit na elemencie, który był kwestionowany lub zawierał takie fragmenty kodu, NDepend pokazywał kod (w VS), ale nie ustawiał się dokładnie na owym fragmencie kodu. Bywało też, że otrzymywałem każdorazowo komunikat „Can’t open declaration in source file. Reason: Source declaration of field is not yet parsed by NDepend”. Było to dziwne, bo jeszcze przed chwilą NDepend bez trudu znajdował ten sam plik, ale dla innej reguły.
Podsumowując – potraktowanie NDepend jako czarnej skrzynki czyli użycie w sposób domyślny nie jest najlepszym rozwiązaniem. Lepiej jest go stosować w celu odnalezienia konkretnych nieprawidłowości, czyli ustalić zakres sprawdzanych reguł i tylko w ten sposób badać kod. Dodając do tego możliwość pisania własnych reguł może to dać konkretne korzyści. Być może uda się wówczas (poprzez oznaczenia w kodzie lub inaczej) wyeliminować pola, które wg niektórych reguł kwalifikują się do zgłoszenia jako nieprawidłowe, ale zgłaszane być nie powinny. Jeśli tylko dowiem się jak to uzyskać (i czy w ogóle się uda), na pewno nie zawaham się wrzucić na bloga ;).
Niniejszy tekst należy traktować jako opinię kogoś kto miał pierwszą styczność z NDepend. Całkiem możliwe, że pewne problemy, które wystąpiły wynikają z nieznajomości narzędzia (choć przyznam, że nie poddawałem się łatwo). Możliwe też, że część z nich zostanie wyjaśniona dzięki Wam czytelnicy, poniżej, w komentarzach, do których pisania oczywiście gorąco zachęcam ;).