Exceptions – performance

Ileż to rozmów już prowadziłem na temat

Czy w tym miejscu powinien być rzucany wyjątek czy zwracany status?

Najlepszą na to odpowiedzią jest

To zależy 😉

Ale dzisiaj nie o tym. Kilka razy podczas takich rozmów padł argument

Wyjątki są wolne…

W tym wpisie chciałbym przeprowadzić kilka testów w celu potwierdzenia lub obalenia tej tezy.

Zbadajmy więc czy rzucanie wyjątków faktycznie spowalnia naszą aplikację. W tym celu skorzystam ze znanej już z poprzednich wpisów biblioteki BenchmarkDotNet. Porównam dwa podejścia, w pierwszym metoda będzie zwracała jakiś StatusCode, a w drugim metoda będzie rzucała wyjątkiem.

Aby testy miały podobną strukturę to po zwróceniu/przechwyceniu wyniku metody, odwołam się do obiektu oraz pobiorę jego wiadomość z pola Message. Poniżej zaimplementowane testy:

Jak widać testów jest pięć: Chciałbym zbadać następujące kwestie:

  • Czy rzucanie wyjątków faktycznie jest wolniejsze niż zwrócenie statusu
  • Jaki jest narzut czasowy na tworzenie obiektu typu Exception
  • Czy blok try…catch ma jakikolwiek wpływ na szybkość wykonania metody

Jeszcze dla dopełnienia, implementacja metod z obiektu Helper:

Chciałem aby obiekt statusu miał podobną strukturę do obiektu Exception i aby były wykorzystane tego samego typu konstruktory.

No to wyniki!!!

Faktycznie, wyniki mówią same za siebie. Potwierdzają tezę, wyjątki są znacznie wolniejsze niż zwykłe zwrócenie statusu. Różnica faktycznie jest kolosalna, ponad 2700 razy na jednostkowym wywołaniu. Ale należy zwrócić uwagę na jednostki. Nanosekundy. Po szybkim przeliczeniu wyjdzie, że  rzucenie wyjątku trwa dużo, dużo mniej niż 1 milisekunda. Biorąc pod uwagę specyfikę w której używa się wyjątków (przeważnie w „górnych” partiach wykonywanego kodu) mogę z całą pewnością stwierdzić, że to podejście nie ma najmniejszego znaczenia jeżeli chodzi o wydajność aplikacji.

Możemy jeszcze porównać podejście z tworzeniem obietów StatusCode oraz Exception. Tworzenie wyjątku poprzez new faktycznie trwa dłużej. Jest to spowodowane dodatkową inicjalizacją pól wewnątrz klasy.

Poniżej wkleiłem częściowa implementację klasy Exception:

Jeżeli chodzi o samą konstrukcję try…catch. Widać tu niewielki narzut na wydajność. Wynika on najprawdopodobniej z implementacji tego mechanizmu. Chciałem wrzucić kod maszynowy generowany z bloku  try…catch oraz bez niego, ale nie do końca go jeszcze rozumiem ;D

Podsumowanie

Czy to wszystko oznacza, że rzucanie wyjątków może być używane zawsze i wszędzie ? Nie wydaje mi się.

Wpis ten pokazuje jedynie, że obsługa wyjątków trwa, ale trwa na tyle krótko, że na pewno nie jest to miejsce gdzie bym zaczął optymalizację swojego kodu.

Poniżej jeszcze dopełnienie tego co napisałem powyżej:

Linq – optymalizacja Single oraz Last

Ostatnimi czasy zamiast siedzieć nad moim gitWebem, grzebie w kodzie źródłowym .NET. W dzisiejszym wpisie chciałem opisać dwie bardzo przydatne oraz często wykorzystywane metody LinqSingle oraz Last.

Ale dlaczego opisywać tak proste i powszechnie wykorzystywane metody?

Ano dlatego:

Single

Widzicie?  Warunek jest weryfikowany na każdym elemencie kolekcji. Pytanie brzmi dlaczego na każdym?! Czy sprawdzanie nie powinno być przerwane na drugim elemencie spełniającym warunek? Moja implementacja pętli:

To teraz benchmark:

Jak widać wyniki mówią same za siebie.

Ciekawa implementacja znajduje się w CoreFx. Sprytnie zlikwidowano zamieniono zliczanie powtórzeń i warunek sprawdzający na dodatkową wewnętrzną pętlę, iterującą od momentu spełnienia warunku.

 

Pierwsze wzmianki nt. tego błędu/niedoskonałości można znaleźć już w 2013 roku, póki co w obecnej wersji .NET nie zostało to jeszcze poprawione. Jak dobrze zauważył kolega Szymon brak poprawy w implementacji mógł być spowodowany koniecznością zachowania wstecznej kompatybilności. Ale dalej uważam, że stosowne by było dodanie chociaż w komentarzu wzmianki.

Last

Obecna implementacja:

Jaki tu jest problem? Metoda Last, która ma pobrać ostatni pasujący element iteruje od początku! W przypadku list gdzie mamy dostęp do elementu poprzez indeks, aż się prosi aby iterować od tyłu. Lepsza wersja mogła by wyglądać tak:

 

Tak jak w metodzie Single, trudno mi zrozumieć, dlaczego to jeszcze nie zostało naprawione. Wydaje się, że było to najwidoczniej przeoczenie bo w implementacji .NET Core obie dwie metody zostały poprawione (last, single)

Podsumowanie

Mam nadzieje, że po przeczytaniu tego posta nie zaczniecie przepisywać Waszych aplikacji na nowo. Nie o to chodzi. Prawdopodobnie w 99% przypadków ten błąd nie będzie miał większego znaczenia na działanie Waszej aplikacji. Ważne jest to aby widzieć, że coś takiego występuje.

Po raz kolejny chciałem pokazać, że wiedza o narzędziach/bibliotekach jest szalenie istotna. Może w tej aplikacji nie będzie Ci potrzebna optymalizacja na tym poziome, no ale w następnej?

Ps. Pisząc tego posta szukałem materiałów i natrafiłem na post Jona na ten sam temat.

 

Reference Equality – co i jak

Tym wpisem chciałbym rozpocząć serię o trzewiach c# i ILAssembly. Ostatnimi czasy chyba dwa razy słyszałem pytanie, czym się różni operator == od metody Equals. Czy zawsze porównują referencję, czy to jest to samo? W tym wpisie chciałbym pokazać metody porównywania typów referencyjnych, dostępnych w c#. Również spojrzeć troszkę niżej i zobaczyć jak takie metody są zaimplementowane i jaki kod IL generują.

 

Więc na jakie sposoby możemy porównać dwa obiekty. Poniżej przedstawiłem wszystkie znane mi metody:

Jako że klasa Foo nie dostarcza nam, żadnej implementacji operatora == oraz metody Equals, wywoływane są bazowe z obiektu bazowego typu Object.

Operator ==

 

Operator == zawsze będzie porównywać referencję przekazanych obiektów. Na dowód tego, poniżej zamieściłem kod z IlSpy jaki został wygenerowany właśnie dla tego operatora.

Najważniejszym elementem powyższego kodu jest instrukcja ceq. Jak z dokumentacji wynika, ceq porównuje dwie wartości ze stosu, jeżeli są te same zwraca 1, w przeciwnym wypadku zwraca 0. W naszym przypadku wartościami są referencję do instancji klas Foo i dla nich następuje porównanie.

Metoda Equals

Jeżeli metoda Equals nie jest przeciążona, wywoływana jest metoda bazowa z klasy Object, metoda zwróci true w momencie gdy referencje obiektów będą te same. Zobaczmy jak ona jest zaimplementowana:

No jak widać nie za wiele widać. Atrybut MethodImplOptions.InternalCall  informuje nas, że implementacja metody jest w samym CLR. Jeszcze się nie doszukałem tego w kodzie cpp. Jak ktoś znajdzie dajcie znać.

Jeszcze kod w IL:

A więc potwierdzenie, wywołana metoda wirtualna Equals.

W tym linku znajdziemy potwierdzenie tego co napisałem, plus parę wskazówek kiedy i co najlepiej stosować.

Metoda Object.ReferenceEquals

Metoda statyczna klasy Object. Porównuje zawsze referencje. Na dowód tego kodzik źródłowy:

Wywołanie operatora ==. Jako że jest to metoda statyczna więc nie ma możliwości nadpisania jej w żaden sposób. Wywołując tą metodę mamy pewność, że zawsze wykonamy porównanie referencji.

Metoda Object.Equals

W pierwszej kolejności porównywana jest referencja. Następnie sprawdzane jest czy obiekty nie są null. Na końcu wywoływane jest Equals na pierwszej instancji.

Na podstawie powyższych przykładów można wyciągnąć jeden bardzo istotny wniosek.

Dla typów referencyjnych, jeżeli nie dostarczymy jawnych implementacji operatora == lub metody Equals, powyższe wywołania zawsze porównują referencję obiektów.

Dla potwierdzenia powyższego zaimplementujmy np. Equals:

I jeszcze porównanie za pomocą statycznej metody Equals:

Wynikiem tego porównania będzie oczywiście True. Jako, że operator == zwrócił False, oraz żadne z obiektów nie jest null, wykonana została metoda Equals. Jest ona przeciążona w klasie Bar, więc ta implementacja została wywołana.

Podsumowanie

W powyższym wpisie opisałem różne metody porównywania obiektów referencyjnych. Chciałbym nadmienić, że rozpatrywałem wyłącznie nasze implementacje. I zasada którą przytoczyłem dotyczy wyłącznie obiektów nie implementujących operatorów i metod. Należy o tym pamiętać. Zwrócić należny uwagę na np.  klasę string która posiada takie implementacje i niekoniecznie musi się zachowywać w dokładnie taki sam sposób. Omówienie klasy string zachowam sobie na inny wpis.

git log – optymalizacja

Iteruj po liście commitów, dla każdego commita na liście wyszukaj jego rodziców z tej samej listy. Połącz każdego rodzica z obecnym commitem obiektem Link.

Tak brzmi zadanie które musiałem zaimplementować w celu uzyskania pięknych kolorowych linii pomiędzy commitami na grafie.

Proste? Jasne. Szybkie 3 linijki w LINQ i mam! Mój projekt testowy ładuje się 3 ms! Genialnie!

A może podepnę jakieś firmowe repozytorium i zobaczę jaka będzie wydajność dla struktury o większej ilości commitów? Może zamiast 13 commitów, uruchomię to na 2200 commitach? Sure… czekamy…. czekamy. Umarło.

W dzisiejszym wpisie chciałbym przedstawić kilka podejść do implementacji tego zadania. Od najgorszego, po takie w którym rezultaty są zadowalające. Zastrzegam że nie musi być to koniecznie najlepsza implementacja. Po prostu lepszej mi się nie udało zrobić. Jeżeli widzisz jakieś potencjalne miejsca optymalizacji, pisz w komentarzu!

Zadanie zrealizowałem w pięciu różnych konfiguracjach:

  1. Kolekcja typu List, metoda wyszukująca LINQ
  2. Kolekcja typu Array, metoda wyszukująca LINQ
  3. Kolekcja typu Array, metoda wyszukująca Array.Find
  4. Kolekcja typu Array, klasyczne wyszukiwanie poprzez pętle for
  5. Kolekcja typu Dictionary, wyszukiwanie po kluczu słownika

Poniżej jeszcze sam obiekt Commit

Wynika z tego, że zawsze wyszukujemy po kluczu typu string.

Jeszcze w gwoli wstępu. Do pomiaru wydajności z każdych z metod użyłem frameworka benchmarkDotNet, naprawdę polecam. Dodatkowo posiłkowałem się kodem źródłowym c# dostępnym tu.

Podejście pierwsze

Pierwsze podejście najbardziej klasyczne, czyli lista i Linq. Zaletą Linq jest to, że jest wygodne, łatwo dostępne, ładnie wygląda. Wadą, są jego pierwsze dwie zalety. Dlaczego? Czasami wydaje mi się, że zbyt łatwa dostępność tego narzędzia sprawia, że ludzie przestają myśleć o implikacjach jego użycia. Nie zastanawiają się co siedzi pod spodem, dla jakich operacji, zadań jest ono dobre, a dla jakich nie.

Jaki problem może wynikać z tego kawałka kodu.

Rozszerzenie Where oraz metoda Contains. Obie te metody posiadają złożoność O(n), a co to oznacza? Przeszukiwanie liniowe, szukamy elementu, który spełnia nasze kryteria i zwracamy wynik. W przypadku Contains, wynik jest zwracany natychmiastowo po spełnieniu wymagań. W przypadku Where, iterowane są wszystkie elementy i zwrócona kolekcja poprawnych elementów. Można to podejrzeć w kodzie źródłowym c#:

Contains:

Contains to rozszerzenie metody IndexOf – który iteruje po każdym elemencie listy i porównuje go poprzez operator ==. Przypomnę, że operator ten w domyślnej implementacji zachowuje się tak samo jak Equals.

Where to tak naprawdę Extension na IEnumerable, które implementuje List. Poniżej kod źródłowy:

Tak jak w przypadku Contains,iterator przechodzi po każdym elemencie kolekcji sprawdzając predicate czyli warunek przekazany w wyrażeniu lambda. W tym przypadku jest to wynik metody Contains.

Drugie podejście

Jak widać różnicą jest jedynie przekazany typ. Tym razem użyta została tablica commtów. Jako, że tablice typów dziedziczą po typie Array, który jest z kolei klasą abstrakcyjną implementującą IEnumerable, mamy dostęp do Linq. Implementacja Where wygląda następująco:

Wywoływana jest dokładnie ta sama metoda Where rozszerzająca typ IEnumerable. Istotna w tym przypadku jest ta linijka:

A więc sprawdzenie czy typem na którym operujemy jest tablica. Jeżeli tak zwracamy WhereArrayIterator.

Tak jak mogło by się spodziewać w tym przypadku została wykorzystała „natywna” operacja dostępu do elementu poprzez index. I jest to jedyna różnica w implementacji metody MoveNext.

Trzecie podejście

Klasyczne podejście przy operacjach na tablicach. Tak nas uczyli na studiach. Brak Linq i innych „magii”. Nic szczególnego się tu nie dzieje więc przejdę dalej.

Czwarte podejście

Tutaj wykorzystałem dedykowaną metodę z obiektu Array. Można by pomyśleć, że to dobry pomysł. Zobaczmy co siedzi w środku:

Cóż my tu mamy. Na pierwszy rzut oka widać, że wykorzystanie tej metody nie jest najlepszym pomysłem. Zachowuje się jak powyższe przykłady, dodatkowo tymczasowa lista jest ponownie konwertowana na tablicę za pomocą ToArray. Można się spodziewać wyższego zużycia pamięci przy tym podejściu.

Piąte podejście

W ostatnim podejściu zastosowałem Dictionary. Jako, że w zadaniu głównie chodzi o wyszukiwanie commitów po kluczu, można się spodziewać, że ten typ będzie idealny do tego zadania. Jeżeli chodzi o wydajność wyszukiwania. MSDN informuje, że złożoność przy wyszukiwaniu po kluczu zbliżona jest do O(1). Zobaczmy jak to jest zaimplementowane:

Widać wyraźnie dlaczego operacja pobrania wartości na podstawie klucza nie jest ani O(1) ani O(n). Dzięki zastosowaniu hasha naszego klucza, ilość iteracji na kolekcji jest maksymalnie ograniczona.

Wydajność

Tak jak już wcześniej wspomniałem, testy przeprowadziłem za pomocą frameworka benchmarkDotNet. Przeprowadziłem trzy testy na różnej wielkości repozytoriach.

Benchmark – 13 commitów
Benchmark – 514 commitów
Benchmark – 2144 commitów

Chyba nikogo nie zaskoczyły powyższe wyniki. Najwydajniejszym podejściem w tym zadaniu jest zastosowanie Dictionary. Szybki dostęp do zasobu dzięki zastosowaniu hash powoduje, że różnica z podejściami opartymi na liniowym przeszukiwaniu jest kolosalna.

Z pozoru „najbrzydsze”, klasyczne podejście z użyciem pętli for również wychodzi o niebo lepiej niż pozostałe metody. W tym przypadku wyszukiwanie również jest liniowe. Wydaje mi się że na wydajność w tym przypadku ma wpływ przede wszystkim prostota rozwiązania, szybki dostęp do zasobu poprzez Index tablicy i niski współczynnik alokacji pamięci.

No właśnie, a co z pamięcią. Tak jak wspomniałem w punkcie przedstawiającym podejście z Array.FindAll, zużycie pamięci w tym przypadku jest największe. Tak jak w przypadku wydajności, ilość za alokowanej w przypadku Dictionary oraz „klasycznego” podejścia jest najniższa.

Uwaga na koniec – zaalokowana pamięć nie jest tylko wynikiem wyszukiwania i iteracji po kolekcjach. Każde podejście wywoływało metodę CreateLinks która dodawała do listy nowe linki. Metoda była wywoływana tyle samo razy. Więc ilość pamięci za alokowanej w tym przypadku nie jest miarodajną wartością. Należało by w tym przypadku wyskalować te wartości do najwyższej.

Method Alocated Scaled %
Run_LinqApproach 0,08 100
Run_DictionaryApproach 0,03 37,5
Run_ArrayApproach_WithLinq 0,07 87,5
Run_ArrayApproach_WithForLoopSearching 0,03 37,5
Run_ArrayApproach_WithArrayFindAllMethod 0,09 112,5

Przygotowanie danych

Podczas recenzji tego wpisu zwrócono mi uwagę, zadano pytanie. A co z przygotowaniem danych? Przecież takie Dictionary należy wcześniej przygotować. Na szczęście pomiar zajął mi kilka chwil. Poniżej wyniki:

Czas konwersji listy 2144 commitów na odpowiedni typ

Te kolekcje są na tyle małe, że czasy konwersji na odpowiedni typ jest znikomy. Zsumowanie obydwu wartości i tak nie zmienia wyników na tyle aby rozważać jeszcze ten aspekt.

Podsumowanie

Wiele się nauczyłem przy tym prostym zadaniu. Wydaje mi się, że problem jaki dzisiaj poruszyłem jest dość często spotykanym problemem w naszej pracy. Jak dobrać strukturę danych i algorytm aby zadanie było wydajne? Ważne jest aby sprawdzać zaimplementowane rzeczy na większej ilości danych. Żeby nie trzeba było mówić „u mnie działa”. Działa bo lista zawiera 20 elementów albo ilość produktów w DB to 4.

Przede wszystkim musimy też wiedzieć jak coś jest zbudowane. To, że Linq jest łatwo dostępne i łatwe w u życiu nie oznacza, że jest to remedium na wszystkie nasze problemy. Zanim użyjesz, zastanów się, czy przypadkiem nie będzie lepszego rozwiązania. Tak jak w tym przypadku, znalazłem ich 5, a wydaje mi się, że jest ich jeszcze wiele, wiele więcej.

Od momentu gdy .NET wszedł do świata open source, poznawanie wewnętrznej implementacji stało się dużo łatwiejsze. Nie trzeba już nic dekompilować aby poznać szczegóły, wystarczy wejść na https://referencesource.microsoft.com.

gitWeb – branching cz.2

W dzisiejszym wpisie chciałbym omówić kolejne funkcjonalności modułu odpowiedzialnego za branching. W poprzednim wpisie opisałem następujące kwestie:

  • Lista branchy
  • Tworzenie nowego brancha

Dzisiaj skupię się na:

  • Checkout – przełączanie się pomiędzy branchami
  • Pobranie drzewa comitów dla aktualnego brancha
  • Opisanie na drzewie commitów, dostępnych branchy

Checkout

Git

Podstawowa funkcjonalność gita, bez której nie da się działać pracując w środowisku z wieloma branchami. Pozwala na przełączenie się pomiędzy dwoma branchami. W konsoli operacja dostępna pod komendą:

Najważniejsze kwestie jakie należy wziąć pod uwagę korzystając z tej komendy to:

  • Wszystkie zmiany, które posiadamy na aktualnym branchu przeniosą się na nowy branch. Oczywiście, jeżeli wystąpi konflikt to zostaniemy o tym poinformowani.
  • Przełączenie się na nowy branch powoduje, że HEAD repozytorium ustawi się na Tip danego brancha. Innymi słowy, zostanie pobrany stan repozytorium z najnowszego commita na nowym branchu.

Tip w branchu, jest to najnowszy commit zrobiony na danym branchu.

VueJs

Po stronie widoku za wiele się nie dzieje. Wywołane kolejne akcje, które na końcu wywołują API. Jedyną rzeczą, na którą chciałem zwrócić uwagę jest główny plik actions.js w sekcji store.

Generalnie chciałem zmodularyzować Vuex w obrębie głównych komponentów Vue. A więc, miałem moduł Vuex branchArea.js, który odpowiadał komponentowi branch.vue, itd.

Aby była możliwa komunikacja między tymi modułami, wydzieliłem nadrzędne miejsce, w którym akcje będą je spajać.

W tym konkretnym przypadku akcja checkout znajduje się w pliku nadrzędnym actions.js, a sama akcja prezentuje się następująco:

Wywołana sekwencja kolejnych akcji. W pierwszej kolejności wywoływany jest CHECKOUT_BRANCH na wskazanym przez użytkownika branchu. W momencie gdy operacja zakończy się sukcesem wywoływana jest akcja GET_COMMIT_TREE_FROM_HEAD, która pobiera listę commitów, zaczynając od HEAD „nowego” brancha. Na samym końcu wywoływane jest builder odpowiedzialny za kompozycję grafu.

Api

Aby przełączyć branch należy odwołać się do:

Sam kontroler wygląda tak samo jak w poprzednich wpisach, a więc przejdę od razu do właściwej implementacji przy pomocy libgit2sharp:

I pojawia się… Magiczne Commands. Z jakiegoś powodu autorzy biblioteki postanowili, że wydzielą część funkcjonalności do statycznych metod. Oczywiście, to jest OS projekt i rozumiem dlaczego wszystko co miało by trafić do commands, jeszcze tam nie trafiło. Ja mam z tym jeden problem, mianowicie dość ciężko do statycznych metod pisze się testy, trzeba je oklejać jakimiś wrapperami, żeby móc mockować takie wywołania.

Poza tym kawałek kodu dość standardowy, parę sprawdzeń warunków brzegowych, zwrócenie NullObject w momencie gdy branch nie istnieje i translacja do obiektu DTO.

Pobranie drzewa commitów dla aktualnego brancha

Tu sprawa wygląda bardzo prosto. Komenda:

pobiera zawszę listę commitów zaczynając od HEAD aktualnego brancha, na którym się znajdujemy.

Pobieranie listy commitów oraz budowanie z nich branchy opisałem we wpisie dotyczącym komendy git log –graph, serdecznie do niego zapraszam.

Opisanie na drzewie commitów branchy

Na powyższym gifie w prawej stronie widać skromnie wyglądające labele BRANCH. Reprezentują one „status” aktualnego brancha. A więc pokazują się tam wszystkie branche przypisane do commita „widocznego” z poziomu obecnego brancha.

Zadanie do zrealizowania może brzmieć następująco:

Pokaż wszystkie branche których najnowszy commit znajduje się na grafie aktualnego brancha.

Powyższa metoda robi dwie rzeczy. W pierwszej kolejności pobrana jest lista commitów dla aktualnego brancha. Następnie pobrany jest Tip każdego dostępnego brancha. Mając te dwie informacje możemy w prosty sposób zmapować te dwie kolekcje.

Podsumowanie

Komponent zbudowany… ale nie kompletny. Brakuje w nim jeszcze wielu, wielu rzeczy. Między innymi zmiana nazwy, usuwanie, „śledzenie” branchy zdalnych. Operacja Checkout wiąże się również z konfliktami i mergowaniem. Jak widać sporo pracy jeszcze przede mną.

W tych dwóch wpisach opisałem budowę jednego z wielu komponentów, które będą budować moją aplikację. Oprócz samego opisywania kodu, chciałem również wrzucić troszkę teorii dotyczącej samego gita. Chciałbym w dalszym ciągu pisać w takiej konwencji. Dzięki temu mam zamiar wprowadzić trochę urozmaicenia i ciekawej wiedzy we wpisy.

W tym momencie kończę kolejny spory moduł. Jeszcze przed świętami chciałbym opisać część dotyczącą tworzenia commitów i podglądu zmian w plikach.

gitWeb – branching cz.1

W dzisiejszym wpisie opiszę jak zrealizowałem branchowanie w mojej aplikacji.

Na powyższym  gifie widzimy wszystkie funkcjonalności, które udało mi się do tej pory zrealizować. A są to:

  • Lista branchy
  • Stworzenie nowego brancha
  • Przełączenie się na inny branch
  • Pobranie drzewa comitów dla aktualnego brancha
  • Opisanie na drzewie commitów branchy

W tym wpisie opiszę kompletną ścieżkę implementacji od frontendu do wywołania biblioteki libgit2sharp. Podzielę to na 3 sekcje:

  • komenda jaką należy wykonać w konsoli gita
  • moduł VueJs
  • API wraz z implementacją libgit2sharp.

Jako, że każdy z elementów jest obszerny dzisiaj skupie się jedynie na opisie listy branchy i sposobie w jaki udało mi się zaimplementować dodanie nowego brancha.

Lista branchy

Za pomocą tej komendy w konsoli pokażą się wszystkie branche w repozytorium. Zarówno te lokalne jak i „śledzące” zdalne. Dodatkowo dostajemy informację na którym branchu obecnie się znajdujemy.

Komponent VueJs

Lista zaimplementowana została w komponencie o nazwie branch.vue. Nie chcę wklejać tu całego komponentu bo nie będzie to miało sensu. Wkleję jedynie najważniejsze kawałki odpowiedzialne za pobranie listy branych i ich wyświetlenie.

Pobranie danych odbywa się w następujący sposób:

Funkcja beforeMount wywoływana jest przed umieszczeniem komponentu na widoku. Jest to mój punkt wejściowy do aplikacji. Zostanie on nim dopóki nie wymyślę lepszego punktu wejściowego. Funkja jest bardzo prosta wywołuje ona akcję Vuex o nazwie GET_ALL_BRANCHES.

Jak widać akcja jest również prosta. Wywołany zostaje serwis, który odpytuje  WebApi. W przypadku poprawnego pobrania danych przekazywane są one do mutacji SET_ALL_BRANCHES. Ustawia ona stan aplikacji zgodnie z tym, jak powinno się to robić korzystając z Vuex. Tylko mutacje powinny zmieniać stan aplikacji.

Sam serwis do komunikacji wygląda następująco:

 

Jak widać wszystko jest dość przejrzyste i proste. Chciałbym aby cała aplikacja tak wyglądała 😉

API

Po stronie backendu funkcjonalności dotyczące branchy wystawione są za pomocą API w kontrolerze BranchController.cs.

Jeżeli chodzi o samo API wybrałem REST ze względu na to, że jest teraz super fancy i modny. A tak na serio to dzięki tej aplikacji chciałbym poznać to podejście. Jego wady, zalety, ograniczenia.

No więc lista branchy dostępna jest pod:

a sam kontroler jest zaimplementowany maksymalnie prosto:

Jak widać nic szczególnego, wstrzyknięty jedynie odpowiedni provider odpowiedzialny za obsługę branchów. Sam provider wygląda tak:

Ot i pierwsze spotkanie z libgit2Sharp. Jeszcze nie opisywałem tej biblioteki. Do providera wstrzykiwany jest obiekt typu IRepository. Jest to interfejs pochodzący właśnie z tej biblioteki. Stanowi on swoiste serce całego frameworka. Reprezentuje on repozytorium gita. Dzięki temu obiektowi mamy dostęp do prawie wszystkich (opiszę w kolejnych wpisach) operacji i zasobów, które możemy wywołać na repozytorium.

W tym konkretnym  przypadku pobranie listy branchy sprowadza się do zwykłej projekcji w Linq na kolekcji Branches. Klasa Branch jest moją klasą. Chciałem ograniczyć ilość zależności w aplikacji do minimum. Dlatego postanowiłem, że nie będę korzystał z dostępnych w bibliotece typów tylko stworze własne obiekty DTO. Dzięki temu podejściu zależność do libgit2Sharp mam wyłącznie w projekcie gitWeb.Core. Kontrolery nic nie wiedzą jaki provider do gita czy innego typu repozytorium jest wykorzystywany. Otwiera to furtkę do implementacji innych systemów kontroli wersji jak np Mercurial czy SVN bez konieczności większych zmian w aplikacji.

Nowy branch

Podstawowa komendą do stworzenia nowego brancha jest:

Oczywiście jest całe mnóstwo opcji, które można wykorzystać do jego stworzenia, ale w obecnej fazie wykorzystuję tą podstawową.

Dodatkowo, tworzone branche nie odwołują się do żadnego zdalnego brancha. Tą funkcjonalność dorobię w ramach Push i Pull.

Komponent VueJs

Do komponentu branch.vue musiałem dodać mini formularz który umożliwi mi wpisanie nazwy nowego brancha.

 

Po stronie komponentu do obsługi tworzenia nowego brancha mamy 2 funkcję:

clearCreationForm – prosta funkcja do czyszczenia stanu.

createBranch – tak jak w przypadku listy branchy, komponenty delegują całą logikę dotyczącą komunikacją z API do Vuex. A więc w tym przypadku wywołujemy akację CREATE_NEW_BRANCH. Przyjmuje ona jako parametr nazwę nowego brancha. Jeżeli operacja tworzenia przebiegnie pomyślnie, czyszczona jest forma z danymi.

Sama akcja w Vuex jest już bardziej skomplikowana:

Więc co tu się dzieje? Tak jak wspomniałem wcześniej, na początku wywoływana jest akcja CREATE_NEW_BRANCH. Akcja ta wywołuje serwis który z kolei wywołuje metodę API do stworzenia brancha. Jeżeli wszystko pójdzie zgodnie z planem to wywoływana jest akcja CHECKOUT_BRANCH.

W tej fazie projektu nowy branch jest automatycznie checkoutowany. I tak samo, jeżeli wszystko będzie w porządku to wywoływana jest (opisana wcześniej) akcja GET_ALL_BRANCHES.

W tym wpisie nie będę opisywał procesu checkoutowania., poświecę na niego osobny wpis.

Serwis odpowiedzialny za odwołanie się do API został zaimplementowany następująco:

API

Stworzyć nowy branch możemy odwołując się do poniższego zasobu:

Szybkie spojrzenie na kontroler oraz branchProvider:

W podstawowej formie żeby stworzyć branch należy jedynie wywołać metodę CreateBranch na obiekcie repozytorium oraz przekazać nazwę jako parametr.

Podsumowanie

Dzisiejszym wpisem rozpocząłem serię dotyczącą implementacji poszczególnych elementów projektu. Już powoli zaczyna coś działać! Strasznie mnie to nakręca do dalszego działania.

Widać wyraźnie, że biblioteka libgit2sharp jest naprawdę prosta w użyciu. Samo opisujące się metody i prosty interfejs sprawia, że bardzo dobrze się z niego korzysta. Dodatkowo community (github, stackOverflow) jest dość potężne, więc nie ma też problemu z rozwiązywaniem ewentualnych problemów czy błędów w samej bibliotece.

W kolejnym wpisie opisze kolejne części branchingu – a więc checkout brancha, zmianę nazwy oraz usunięcie.

4Developers 2017

To był bardzo bardzo ale to bardzo długi dzień.

Trudne początki

Co może być trudnego w dojeździe rano z Zabrza do Katowic? Hmm większość powie, że nic. Dzisiaj na własnej skórze przekonałem się, że z jednego przystanku autobusowego, ta sama linia może jechać w dwa różne kierunki. Tak, wsiadłem w ten zły… No ale przecież chill, 5 rano, godzina do odjazdu pociągu, a ja jestem na jakimś odludziu, chyba w rudzie śląskiej. Na szczęście jedyne 80 zł wydane na taksówkę i byłem w pociągu. Istny koszmar!

No ale to miała być relacja z konferencji więc zacznijmy.

Sama konferencja od samego początku robiła na mnie spore wrażenie. Ilość uczestników ~1500, 14 ścieżek tematycznych. Bardzo byłem ciekaw czy organizatorzy podołają temu szalenie trudnemu zadaniu.

Rejestracja

Na wstępie, genialna sprawa z tym, że była możliwość wcześniejszego odebrania wejściówki, nie w dniu samego wydarzenia. Wydaje mi się że dzięki temu zabiegowi czekaliśmy w kolejce jedynie parę minut! Jak na tyle osób zarejestrowanych, rewelacja!

Konferencja znajdowała się w hotelu niedaleko lotniska Chopina. Bardzo dobry dojazd ale… jednak to jest ponad 1500 osób i nie wydaje mi się, że to był odpowiedni obiekt na taką rzeszę ludu. Później jednak sobie pomyślałem, że mało jest takich miejsc odpowiednich do tego typu imprez, więc postanowiłem nie pastwić się nad tym aspektem.

Prelekcje

To po co tu przyjechałem a więc wiedza, dużo wiedzy.

Consumer-Driven Contractsna ten wykład niestety nie udało mi się zdążyć, byłem jedynie na ostatnich 10 minutach, a szkoda bo po pytaniach jakie padały w tym czasie, odniosłem wrażenie, że bardzo ciekawy temat poruszył Jędrzej.

Public enemy No. 1, mid-sized building blocks and hexagonal architecture in real life – Bardzo ciekawa prezentacja, pokazująca jak komponować, organizować nasz projekt. Niestety wydaje mi się, że problemem z tą prezentacją było zbyt mocne przywiązanie do technologii. Jakub prezentował wszystko na przykładach z Javy. Wydaje mi się, że było tego za dużo, jednak ścieżka architektury powinna wystawać ponad szczegóły implementacje danego języka czy frameworka.  Oczywiście nie miałem większego problemu zrozumieć jaki był ogólny przekaz tej prezentacji ale lekki niesmak pozostał.

Azure Service Fabric czyli wszędobylskie mikroserwisy – Tak jak już kiedyś wspominałem, nie za bardzo lubię tego typu prezentacje. Lubię prezentacje nastawione na problem, a nie na produkt. Ale poszedłem na nią aby „liznąć” troszkę tej technologii. Dużo o niej słyszałem od kolegi Szymona i postanowiłem zobaczyć co to takiego. Tym razem się nie zawiodłem. Rzetelnie poprowadzona, ciekawa prezentacja. Zdecydowanie na plus.

.NET Core w 2017 – Łukasz przekazał nam usystematyzowaną wiedzę na temat tego wszystkiego co się dzieje w MS w obrębie .NET. Jak wszyscy wiemy dzieje się sporo, i tego dobrego i złego. Super, że chciało mu się to wszystko ogarnąć i przekazać nam w tak dobry sposób!

Obiad – jak do tej pory byłem całkiem dobrze nastawiony, to jak przyszedł moment obiadu to jakoś wszystko przestało mieć znaczenia. To wszystko co wyżej pisałem słabawa lokalizacjia i milion ludzi sprawiło, że obiad przerodził się w jeden wielki dramat. Kolejki były wszędzie i nie wiadomo było gdzie stać. Niestety ale ten punkt uważam za najsłabszy w całym dniu. Choć sam obiad bardzo smaczny 😉 ale tu były bardzo skrajne opinię wśród kolegów również.

Ale poniżej kolejna duży minus.

JIT me baby one more time – Kurde w sposób w jakiś rozeszła się informacja o przesunięciu tej prelekcji godzinę wcześniej, dawał wiele do życzenia. Pytanie do organizatorów. Czy było jakieś info o przesunięciu na twiterze? Ja nie widziałem. Przecież to jest główne medium przekazu obecnie. Nawet mieliście taki telewizor co pokazywał „ćwierknięcia” Kurczę wiele osób czekało na występ Jarka. I jak przyszli to dowiedzieli się, że był godzinę wcześniej. C’mon!

Co do samej prezentacji – po raz kolejny najwyższy poziom! chapeau bas! Mimo, że nie siedzę w Javie, a w .NET poszedłem na tą prezkę. Ze względu właśnie na Jarka, a także na tematykę. Chciałem wiedzieć jak to „u Was” się robi. Było bardzo dobrze. Szkoda tylko, że tak krótko.

The only thing that matters – Szymon zapewniał, że będą żarty i będzie lepiej niż na BoilingFrogs (chociaż uważam, że było bardzo dobrze nic nie brakowało). Żart z krową mega! Sama prezentacja też wydaje mi się lepsza. Wiele kwestii zostało rozwiniętych dzięki czemu to co mówił Szymon było łatwiejsze do przyswojenia. Takich prezentacji powinno być zdecydowanie więcej. Mniej nowości z nowych Angularów więcej technicznych rzeczy proszę! Jedyną rzecz, i tu nie wiem do kogo to adresować. Zarówno część widowni jak i pewnie sam Szymon odczuł to, że ta prezentacja nie była o .NET!!!!!!!!!!!! i nie wiem czemu była na tej ścieżce. Naprawdę. Zdecydowanie lepiej by się wpasowała w jedną ze ścieżek Arch.

Po tej prezentacji naszło mnie takie zmęczenie, że już nie uczestniczyłem w pełnych prelekcjach. Chodziłem po salach i słuchałem po parę minut każdej. Z tego co zaobserwowałem to, że poziom był naprawdę wysoki. Dużo technicznych prezentacji, poruszających ciekawe problemy wydajnościowe, architektoniczne. Bardzo fajnie.

Na koniec został panel dyskusyjny Bottegi. Panowie, nieźle nieźle, trzeba przyznać, że bardzo dobrze się Was słucha. Dodatkowo publika była bardzo chętna do zadawania dobrych pytań! Szkoda tylko, że nie było Sławka Sobótki ;(

Podsumowanie

Wyjeżdżałem z Warszawy z mieszanymi uczuciami. Z jednej strony bardzo dobre prelekcje, dużo się dowiedziałem. Spotkałem kupę znajomych, przeprowadziłem sporo ciekawych rozmów. Z drugiej strony ten tłok i lekkie kłopoty organizacyjne – głównie obiad. Mimo wszystko uważam, że konferencja była dobra. Tylko nie wiem czy iście w tak dużą ilość osób jest dobrym pomysłem, no ale to już nie ode mnie zależy. Na pewno zjawie się za rok !

Vuex – getters – enkapsulacja logiki

W dzisiejszym krótkim wpisie opiszę jak wykorzystałem getters w Vuex do implementacji pobrania statusu repozytorium.

Stara wersja

Powyższy kod wykonuje trzy czynności, w pierwszej kolejności wysyła POST na serwer, informując, że dany plik musi przejść na stage. Kolejne dwie to wywołanie akcji, które pobierają status repozytorium w stage unstage.

Widać bardzo duże podobieństwo i niepotrzebne powielenie kodu. Po stronie serwera, pobranie odbywa się poprzez odpytanie tej samej metody RetriveStatus, różnicą jest jedynie typ statusu.

Warto by zmienić lekko podejście, zlikwidować duplikację.

Nowsza wersja

No ok, dwa odpytania serwera po „te same” dane. Zmieńmy to:

Jak widać spore zmiany, pozbyliśmy się dwóch wywołań. Zastąpione one zostały jednym wywołaniem funkcji fetch_repository_status, pobiera ona nie filtrowane dane. Przypisuje je następnie do jednego stanu, repositoryStatus.

Ok, to teraz wyświetlmy to na widoku:

Działa? Działa pięknie. Jest tylko jeden problem z tym rozwiązaniem. Jeżeli będę chciał wykorzystać taka listę w innym miejscu , będę musiał powielić kod. Zobaczmy jak będzie wyglądała implementacja z wykorzystaniem getters.

Najnowsza wersja

Getters w Vuex, są koncepcyjnie tożsame z computted we Vue. Generalnie chodzi o to, że jest to miejsce w którym możemy poddać filtracji nasze dane. Co warto zauważyć, filtracji, a więc procesowi który nie zmienia nam stanu aplikacji.

a teraz wykorzystanie w komponencie Vue:

Korzyści są oczywiste, wydzielenie logiki do osobnego miejsca. Dzięki temu użycie plików ze stage lub ustage sprowadza się do wywołania powyższej linijki. Dodatkowo jakiekolwiek modyfikacje w przyszłości będą znacznie łatwiejsze. Jedno miejsce gdzie jest logika = jedno miejsce do zmiany.

Podsumowanie

Tym rozbudowanym wpisem opisałem dość prosty mechanizm gettersVuex. Jednakże chciałem również nakreślić problem wydzielania logiki i powielania kodu. Powyższy przykład można zastosować do każdego innego frameworka czy języka. Odpowiednie oddzielenie zależności znacznie ułatwi nam pracę oraz utrzymanie kodu, pamiętajmy o tym!

git log, wstęp do D3JS

Dzisiaj więcej kodu! W końcu to blog programistyczny. W dzisiejszym wpisie pokażę jak udało mi się stworzyć wizualizację grafu commitów.

Obecnie graf wygląda tak:

Graf składa się z trzech struktur

node – pojedynczy commit, opisany przez sha, koordynaty x oraz y

link – połączenie pomiędzy dwoma commitami, składa się z identyfikatorów poszczególnych commitów oraz z identyfikatora kolumny. Kolumna to zmienna pomocnicza, przydaje się do wyznaczenia kolorów połączeń

message – dodatkowe informacje o commicie, treść commita, autor, data, identyfikator

Wstęp do d3js

D3js, framework do wizualizacji danych. Niby tyle… lub aż tyle. Możecie zobaczyć ile jest przykładów i jak niesamowicie się one prezentują tu! Naprawdę robi to wrażenie. Widać też jak potężnym narzędziem jest ta biblioteka.

Standardowo możemy sobie ją pobrać za pomocą package managera npm lub załączyć cdn.

Obecnie można się spotkać z dwoma wersjami 3 i 4. Należy na to zwrócić uwagę, ponieważ zmiany są w pewnych przypadkach dość spore i część przykładów z 3 nie będzie nam działać na wersji 4. Zdecydowałem się na wersję 3, ponieważ więcej przykładów znalazłem właśnie w tej wersji.

Bazą grafu do gita będzie element svg. Dlaczego? daje bardzo dużo możliwości, jeżeli chodzi o bardziej zaawansowane animacje czy kształty.

Ok, na początku określmy punkt startowy dla grafu:

W pliku HTML wstawiamy  tag svg, do tego elementu będziemy się odwoływać w naszych skryptach i w ramach tego tagu powstaną wszystkie inne elementy.

W pierwszej kolejności musimy „chwycić” svg przez d3js

naprawdę proste, na obiekcie d3 wywołujemy metodę select, jako jej parametr określamy na jakim elemencie chcemy pracować. W tym przypadku jest to svg, ale nic nie stoi na przeszkodzie aby zaznaczyć jakikolwiek inny tag HTML. Dodatkowo możemy zaznaczyć klasę jak i identyfikator. Dla klasy odwołujemy się poprzez ‚.nazwa_klasy’, a do identyfikatora ‚#identyfikator’.

W pierwszej kolejności wygenerujmy sobie grupy, które będą nam spajać węzły, dodatkowe informacje oraz ten szary prostokąt w tle.

  • selectAll – zaznacza nam wszystkie tagi g w obrębie elementu svg. Dodam , że tag svg jest o tyle specyficzny, że nie możemy (możemy ale nie będą widoczne) wewnątrz niego dodawać niektórych „klasycznych” tagów min. div. Odpowiednikiem div jest właśnie element g.
    Różnicą pomiędzy metodą select, a metodą selectAll , jest to, że ta druga zwraca nam wszystkie elementy w postaci tablicy.
  • data – przekazujemy tablicę commitów, tak przekazane dane będą służyć do następnych operacji.
  • enter – tworzy powiązanie pomiędzy zaznaczonymi elementami przez funkcję selectAll, a przekazanymi przez funkcję data.
  • append – funkcja tworząca elementy, w tym przypadku tagi g. Ile ich ma stworzyć, to zwraca funkcja enter. Ilość stworzonych elementów określa rozmiar tablicy przekazanej w funkcji data. Istotne jest to, że zostaną stworzone jedynie brakujące elementy. Dzięki temu ilość elementów oraz danych w tablicy zawsze jest taka sama.
  • attr – przekazujemy atrybut dla każdego elementu, tego stworzonego jak i tych istniejących.

Po tej operacji powinna powstać poniższa struktura, oczywiście na widoku na razie nie będzie nic widać, ale to jeszcze chwilka 😉

Dodajmy troszkę elementów wizualnych. Na początku szare prostokąty.

Więc, tak jak w poprzednim przykładzie, rozpoczynamy od funkcji append, która doda nam dokładnie taką samą ilość elementów rect jak g. Ustawiamy, aby prostokąt rozpoczynał się w punkcji x = 0, punkt wyznaczamy dynamicznie na podstawie danych przekazanych poprzez funkcję data.

classed – funkcja spełnia dokładnie takie samo zadanie jak .attr(„class”,’nazwa_klasy’), czyli ustawia klasę dla elementu. W tym jednak przypadku posiada jeden bardzo przydatny parametr setFlag, ustawienie go na true powoduje, że klasa ta będzie dodana do elementu. Wartość false usunie klasę z elementu.

style – oczywiście jeżeli chcemy zmienić jeden parametr wizualny naszego elementu, nie musimy specjalnie tworzyć  do tego klasy w CSS. Możemy przekazać go wprost za pomocą funkcji style. Poniżej to samo wywołanie ale z wykorzystaniem funkcji style zamiast classed:

A efektem tego jest tak ostylowany element:

Dokładnie takim samym sposobem tworzymy commity oraz informacje o commicie.

Do końca pozostały nam jedynie połączenia. Zaimplementować można to tak:

Można zauważyć że wyjściem do generacji jest svg. Jako, że połączenia są pomiędzy elementami różnych grup, to wydzieliłem je na zewnątrz. Jedyną wartą uwagi częścią jest:

W ten sposób odwołujemy się do węzła, w którym aktualnie się znajdujemy, w tym przypadku aktualizujemy wartości punktów granicznych dla linii.

Podsumowanie

Przede wszystkim należy zwrócić uwagę na fantastyczne API jakie udostępnia biblioteka. Bardzo przejrzyste, intuicyjne, naprawdę rewelacja. Tak jak pisałem w poprzednim wpisie, dokumentacja jest… ciężka i toporna, ale jest w niej wszystko. Za to istnieje mnóstwo tutoriali blogów i przykładów. Jest się z czego uczyć. W następnym wpisie postaram się opisać interakcję użytkownika z elementami, jak i również integrację d3js z VueJs.

git log –graph

Od samego początku projektu wiedziałem, że ten etap będzie najtrudniejszy. Nie wiedziałem, że aż tak. Od ostatniego wpisu minęło sporo… czasu. Bynajmniej powodem nie był brak tematów lecz ilość czasu jaki spędzałem nad rozwiązaniem tego problemu. Cały czas pracowałem nad wizualizacją grafu comimtów w gicie. W Togglu mam prawie 30h pracy nad tym komponentem, co w stosunku do ilości pracy włożonej w cały projekt od początku konkursu, wynosi coś koło 25%.

Dobra ale gdzie ten problem? Co mi zajęło tyle czasu. Miałem dwa problemy do rozwiązania:

  • struktura grafu
  • wizualizacja

Co jest ważniejsze?!

Za pomocą biblioteki libgit2Sharp pobranie listy commitów wraz z identyfikatorami rodziców jest trywialne:

Powyższe zapytanie wyciąga commity rozpoczynając od HEAD (najwyższy dostępny lokalnie commit obecnego brancha) oraz są one w kolejności chronologicznej. Jak się okaże później, są to bardzo istotne informacje, szczególnie ta druga.

Podgląd grafu w SourceTree

Na samym początku zadałem sobie pytanie, które już od dawna mnie nurtowało: Które commity wchodzą w skład „głównej” ścieżki ( w powyższym przypadku kolor seledynowy). Postaram się to opisać jeszcze dokładniej. Analizę rozpoczynamy od góry od commita b82… Jego rodzice to 50df.. i a24b… i teraz zakładając, że mogą one przyjmować dowolne daty wcześniejsze niż b82…, który z rodziców jest „głównym” rodzicem? Nie dawało mi to spokoju, uważałem to za najważniejszą informację jaką muszę pozyskać. Odpowiedź przyszła dopiero po długim czasie czytania dokumentacji gita.

Okazuje się, żeby wyznaczyć główną linię grafu należy wykonać komendę git log –first-parent. Dzięki temu uzyskamy wyłącznie commity zaznaczone na seledynowo. GENIALNIE! miałem rozwiązanie mojego problemu. Teraz jak to zaimplementować przy pomocy libgit2Sharp….

Nie trzeba!, domyślnie struktura listy parentów dla każdego commita jest posortowana tak, że „główny” commit jest pierwszy! Jest to kluczowa informacja. Trzeba pamiętać że sama lista commitów posortowana jest chronologicznie.

Wizualizacja

Mając przygotowane dane jedyne co mi zostało to je wyświetlić na widoku. Jedynie… Od samego początku odrzuciłem pomysł

Może napiszę framework do generowania grafów!

Od znajomego dostałem cynk, że do tego nada się doskonale d3js!

Czym jest d3js? Oh, dużo by mówić, można zobaczyć tutaj, w skrócie bardzo przyjemne API do wizualizacji różnego rodzaju danych.

Spędzając parę kolejnych godzin na wertowanie dokumentacji (jest straszna i obszerna!) wybrałem sposób na wizualizację mojego grafu. Zastosowałem klasyczne podejście z węzłami i liniami łączącymi.

Po kilku godzinach pracy udało się!

Jeszcze nie jest doskonały, niektóre ścieżki się nie kończą i czasami kolor się miesza (czarny i żółty).  Dodatkowo dochodzi problem wydajności, puki co napisałem to „żeby działało” jest tam jeszcze co optymalizować.

Ale uff udało się, najbardziej bałem się tego etapu. Jak już mnie nic nie blokuje, będę mógł częściej publikować nowe wpisy 😉

Dwa tygodnie już za znami

Dwa tygodnie konkursu mijają za troszkę ponad godzinę. Czas na lekkie podsumowanko postępów.

To były naprawdę dwa intensywne dla mnie tygodnie, w większości nastawione na naukę Vuejs. Jakie mam o nim zdanie można przeczytać w moich poprzednich wpisach. W skrócie to jest całkiem nieźle. Powolutku do przodu posuwały się pracę nad interfejsem graficznym mojej aplikacji. Jako, że nie jestem mistrzem JS ani stylów, powolutku to mało powiedziane. Ale mam! poniżej moja krwawica, ależ jestem z tego dumny!

W poprzednim wpisie pokrótce opisałem poszczególne elementy, teraz możemy je zobaczyć na „żywo”

W górnej części widzimy trzy ściśle powiązane ze sobą komponenty, staged files, unstaged files, commit. Służą one do definiowania zbioru plików które mają wejść w skład commita.

Poniżej nich komponent z którego puki co jestem najbardziej zadowolony! Podgląd zmian w pliku. A czemu akurat z tego? Bo wygląda całkiem nieźle! Ten element i drzewo commitów stanowią niejako serce gitGUI. Cieszę się że mam choć jedną z tych rzeczy.

Co dalej

Krótka mapa drogowa kolejnych dwóch tygodni (właśnie co dwa tygodnie będę publikować tego typu wpisy)

  • Poprawki do powyższego modułu, zwiększenie UX
  • W komponencie z podglądem zmian chciałbym dodać możliwość wyłączenia poszczególnej zmiany z commita
  • GRAF GRAF GRAF aż się boję, zdaję sobie sprawę że może to być najtrudniejsza rzecz podczas tego projektu, zarówno pod względem algorytmicznym jak i również graficznym.

Sporo zadań, zobaczymy co z tego wyjdzie. W nadchodzącym tygodniu chciałbym również rozpocząć serię wpisów dotyczących samego gita, STAY TUNED

Vuex – wstęp

Postępy

Szybkie spojrzenie na to co wykreowało się w aplikacji. Na chwilę obecną są 4 komponenty:

unstagedFiles – komponent odpowiedzialny za wyświetlanie wszystkich plików, które nie mają wejść do kolejnego commita,

stagedFiles – analogicznie- komponent, który wyświetla pliki, które chcemy włączyć do commita,

commitDetails –  komponent odpowiedzialny za „budowanie” commita, na razie jest tylko nazwa oraz przycisk,

fileChangePreview  –  duży komponent odpowiedzialny za podgląd aktualnych zmian w plikach, zarówno tych w stage jak i unstage. W komponencie tym będziemy mieli możliwość podejrzenia zmian jak i wyłączenia z danego commita.

Każdą z tych operacji opiszę jeszcze dokładnie w kolejnym wpisie. W tym chciałbym się skupić na krótkiej relacji z mojej przygody z Vuex.

Vuex

W każdej aplikacji, w której nacisk położony jest na wysoką modularyzację (a takie są aplikacje pisane w VueJs), przychodzi potrzeba komunikacji pomiędzy komponentami. Zarówno komponentami w konfiguracji rodzic-dziecko, jak i w równoległej konfiguracji dziecko-dziecko. VueJs dostarcza nam wbudowany mechanizm komunikacji, lecz wraz ze wzrostem ilości elementów aplikacji, złożoność oraz narzut na jej implementację znacznie rośnie. Dodatkowo chcielibyśmy zapewnić „czystość” komponentom. Chcielibyśmy uniknąć tworzenia sztucznych komponentów służących wyłącznie jako pośrednicy w komunikacji.

Teoretycznie mogłem stworzyć komponent root dla powyższych komponentów i zaimplementować prostą „szynę”, ale patrząc trochę do przodu, zdaje sobie sprawę, że takich szyn i komponentów pomocniczych może być znacznie więcej. No więc co?

Rozwiązać ten problem chce Vuex. Czym jest Vuex ? Dla mnie Vuex to mechanizm zarządzania stanem aplikacji, przypomina mi trochę taki globalny singleton. Oczywiście z możliwością pełnej modularyzacji i ograniczeniem kontekstów działania.

Instalacja

Na samym początku instalujemy wszystko za pomocą npm.

Struktura plików Vuex w aplikacji jest prosta, wszystkie pliki znajdują się w folderze store.

Poniżej przestawiłem cały kod jaki jest wymagany aby uruchomić Vuex w swojej aplikacji VueJs.

W pliku main.js importujemy sobie instancję Vuex znajdującą się w pliku index.js i przekazujemy do konstruktora Vue.

Sam plik index.js również wygląda bardzo prosto. Pokrótce, na samym początku importujemy sobie wymagane referencje oraz akcję i mutację (o nich później). Jak widać zostały one wydzielone do osobnych plików ale nic nie stoi na przeszkodzie, żeby na początku umieć je w taki sposób jak state.

Należy zwrócić uwagę na tą linię:

Ważne jest aby ona była umieszczona przed inicjalizacją Vuex.

Implementacja

Poniżej opiszę poszczególne elementy, które wchodzą w skład instancji Vuex. Za przykład wezmę instancję z mojej aplikacji.

state– Nic innego jak stan naszej aplikacji.

W powyższym przykładzie, chcemy aby w skład stanu naszej aplikacji wchodziły dwie kolekcje plików.

Przekazując store do instancji Vue automatycznie mamy do niej dostęp, jako globalny obiekt, pod zmienną $store.  Jeżeli w którymś z komponentów chcielibyśmy się odwołać do tych zmiennych, należy to zrobić tak:

Naprawdę proste. Ale czym w takim razie rożni się od zwykłego globalnego obiektu zawierającego zmienne? W Vuex mamy ściśle określone zasady jakimi powinniśmy się kierować, aby zmieniać stan.

mutations- aby poprawnie zmienić stan naszej aplikacji musimy zaimplementować tzw. mutacje. Są to funkcje, które operują na stanie. Tylko w tych funkcjach implementujemy logikę, która ten stan zmienia. I teraz jeżeli mamy potrzebę zmienić stan, wywołujemy daną mutację:

Poniżej implementacja mutacji APPLY_UNSTAGED_FILES

Strict Mode – No dobra ,ale zaraz ktoś spyta

a co mi szkodzi  żeby wywołać ten kod

W tym przypadku czyścimy listę plików poza funkcją mutacji. Oczywiście to przejdzie, ale łamiemy tym samym całą ideę Vuex, równie dobrze moglibyśmy go wywalić.

Oczywiście taka implementacja może być mniej lub bardziej świadoma. Dlatego przychodzi nam z pomocą strict. Jeżeli ustawimy tę flagę na true, to Vuex podczas każdej zmianie stanu z „zewnątrz”, wyrzuci nam wyjątkiem.

Bardzo pomocne, szczególnie w przypadku gdy uczymy się Vuex lub do zespołu dołącza mniej doświadczony programista.

Akcje – główna zasada w Vuex mówi, mutacje wykonujemy synchronicznie. Oczywiście świat, jak i pewnie większość framworków w JS są asynchroniczne, ale nie chcemy z nich rezygnować. W odróżnieniu od mutacji, akcje mogą wywoływać operacje asynchroniczne.

Oczywiście, jeżeli po wykonanej akcji chcemy wywołać mutację to nic nie stoi na przeszkodzie. Wywołujemy poprostu metodę commit z odpowiednimi parametrami.

W powyższym przykładzie, zadeklarowałem akcję, która asynchronicznie odpytuje API o wszystkie pliki, a następnie wywołuje mutację i aktualizuje pliki.

Samo wywołanie akcji w komponencie również jest trywialne, wystarczy wywołać funkcję dispatch oraz przekazać nazwę akcji, którą chcemy wywołać.

W powyższym pliku możemy zwrócić uwagę na jeszcze jedną rzecz. Mianowice import pliku types.

W pliku  types.js znajdują się wszystkie nazwy akcji i mutacji jakie mamy zadeklarowane. Dzięki temu zabiegowi z kodu znikają nam wszystkie „magic strings”. Prosty rozwiązanie, ale niezwykle pomocne.

Podsumowanie

Generalnie sam mechanizm mi się podoba, jak się go opanuje to naprawdę ułatwia komunikację pomiędzy komponentami. Bardzo przydał mi się StrictMode, dzięki niemu wyłapałem nie jeden kardynalny błąd.

Oczywiście, przedstawione rozwiązanie z podziałem implementacji Vuex na osobne pliki nie uchroni nas przed zbytnim ich rozrośnięciem. Potrzebne są nam jakieś moduły. Oczywiście Vuex dostarcza nam taką możliwość. W kolejnym wpisie z tej serii opiszę ten mechanizm.

VueJS daj się poznać

Długo się zastanawiałem jaki framework dla frontendu wybrać. Na rynku mamy wielu kandydatów m.in: Angulara 1.x, Angulara 2.x, Aurelię, VueJs i klasyk- React. Oczywiście to nie wszystkie i pewnie nie wspomniałem o jakichś ważnych, ale tylko tymi miałem jakąkolwiek styczność, czy to przez petprojects czy też na prezentacjach. Jeżeli chodzi o Angulara 1.x, pisałem w nim drobne aplikacje i całkiem nieźle mi się z nim pracowało. Przyjemna separacja zależności, wsparcie dla DI, serwisy, fabryki itp. To co lubię. Wiele złych rzeczy można na jego temat wyczytać, ale ja ich nie odczułem, może dlatego, że nie robiłem w nim żadnego większego projektu.

No ale mamy przecież Daj Się Poznać i fajnie byłoby nauczyć się czegoś nowego. Angular 2.x i Aurelię widziałem na kilku prezentacjach i wydają mi się zbyt „ciężkie”. Szukałem czegoś bardziej finezyjnego, lżejszego. Został React oraz VueJs. Reacta kojarzę tylko za sprawą Facebooka, a VueJs dlatego, że od jakiegoś czasu stał się dosyć modny. Długo nie mogłem się zdecydować. Po spotkaniu na gliwickiej grupie DevDuck postawiłem na VueJs.
Tym postem z przydługim wstępem chciałbym rozpocząć „mikro” serię wpisów na temat mojej męki w świecie frontendu.
Początki? To zawsze jest trudne. A w froncie? To jest coś niesamowitego… Dla osoby, która dość długi czas siedzi w backendzie, przestawienie się jest co najmniej trudne. Ilość rzeczy, które trzeba ogarnąć jest naprawdę spora.

Tak więc VueJs. Oj, ależ inne podejście od znanego mi AngularaJS. Przede wszystkim, odejście od klasycznego MVC i warstw na rzecz architektury „wertykalnej”, architektury luźno powiązanych komponentów.

Single File Components

W pierwszej kolejności podczas nauki szukałem sposobu na kopozycje projektu, zastanawiałem się jak rozdzielać te małe moduły w systemie plików. Jeżeli komponent ma zawierać swój template HTML, obsługę w JS oraz styl, to musi być coś co to „opakuje”. Tym czymś są pliki .vue. Tworzymy jeden plik per komponent:

Segregacja komponentów

Poniżej przykład prostego komponentu:

Mamy w nim trzy podstawowe elementy:

  • template – w ramach tego tagu piszemy cały kod HTML jaki ma być wyświetlony w miejsce naszego komponentu. Ważna rzecz, taki element może zawierać wyłącznie jeden nadrzędny element, także składnia poniżej będzie powodować błędy podczas budowania paczki w webpacku.

A poniżej poprawna implementacja:

  • script – w nim umieszczamy całą logikę komponentu. Jest ściśle określona struktura takiego skryptu. Opiszę go w kolejnym wpisie.
  • style – w nim możemy „upiększyć nasz komponent”. Zwrócić należy uwagę, że nie jesteśmy wcale zmuszeni pisać w czystym CSS. Jeżeli dołączymy atrybut z nazwą składni (w tym przypadku lang=scss) oraz dołączymy odpowiedni loader w webpacku, możemy porzucić pisanie czystego CSS na rzecz SCSS, LESS czy innych.

Ciekawym dodatkiem do tego fragmentu jest możliwość zamknięcia (ang. scoped) stylu na wyłącznie ten komponent, wystarczy, że umieścimy atrybut scoped w tagu.

Dzięki temu style zawarte w komponencie będą miały wyłącznie zasięg lokalny. Oczywiście nic nie stoi na przeszkodzie aby jednym komponencie dodać style lokalne oraz globalne.

Kolejną kwestią jaką należy mieć na uwadze korzystając z tych plików to fakt, że potrzebujemy czegoś co nam to zepnie je w całość. Ja w tym przypadku korzystam z webpacka. Aby wszystko działało, musimy dodać odpowiednią sekcję do rules :

Do uruchomienia aplikacji jeszcze potrzebna jest jedna rzecz. Punkt startowy:

W powyższym przykładzie tworzona jest instancja VueJs, w której jest wywoływany komponent App. Będzie on w moim przypadku głównym komponentem aplikacji, składającym się z mniejszych części.

Musimy to jeszcze zintegrować z ASP.MVC, na którym wszystko stoi. Tak jak w większości frameworków należy wywołać na głównym widoku komponent root’a, czyli w tym przypadku „app”.

Tym sposobem, jeżeli uruchomimy sobie webpacka, a następnie odpalimy projekt powinno na ekranie pojawić się nam piękne „Hello World”!

Postępy

Bardzo dużo czasu przeznaczyłem na wertowanie dokumentacji oraz tutoriali związanych z VueJs. Także czasu na stworzenie samej aplikacji nie zostało zbyt wiele.  Do momentu pisania tego posta widok mojej aplikacji prezentuje się tak jak na załączonym obrazku:

Jak widzimy, z lewej strony kreuje się już panel do zarządzania zmianami w repozytorium. Jako, że jestem kompletnie lewy w sprawach designu to paletę kolorów biorę z VSCode, w którym to piszę.

Co dalej

Do kolejnego wpisu dalej będę zgłębiał tajniki VueJsa, ponieważ jestem dopiero na szczycie góry lodowej. Na pewno chciałbym dokończyć to co mam oraz dołożyć prosty podgląd zmian dla pliku. Zostało mało czasu do końca tygodnia, także zabieram się do pracy.

Daj się poznać

Cześć

Mam na imię Norbert, od kiedy pamiętam wszyscy do mnie mówią Norek, więc tak mnie najłatwiej namierzyć. Programuję już prawie od 5 lat, głównie jest to .NET, i postanowiłem rzucić się na głęboką wodę i wziąć udział w konkursie Daj się poznać 2017.

Tak jak dla większości uczestników, tak dla mnie motywacją do udziału w konkursie jest chęć rozwoju osobistego, poznania nowych technologii, sprawdzenia się jako blogger. Konkurs ma mi dać kopniaka w tyłek i krzyknąć „zrób coś więcej, niż 8h w pracy”. Rok temu niestety nie udało mi się wziąć udziału w konkursie i bardzo z tego powodu żałowałem. Szczególnie jak popatrzyłem ile mogłem zrobić przez te dziesięć tygodni, a ile udało mi się zrobić bez tej dodatkowej motywacji.

Oczekuje od siebie dużo jeżeli chodzi o udział. Na pewno chciałbym dotrwać do końca. Będzie to o tyle trudniejsze że nigdy w życiu nie pisałem bloga, a to wydaje mi się największym wyzwaniem. Już po tym wpisie widzę że nie jest to najłatwiejsza rzecz na świecie, podobno z biegiem czasu będzie łatwiej, zobaczymy.

Co

Celem projektu w DSP2017 jest stworzenie projektu o wdzięcznej nazwie gitWeb. Wzorując się na SourceTree oraz przepięknym GitKraken, chciałbym stworzyć swojego klienta do obsługi repozytoriów GIT. Obsługiwane będzie lokalne repozytorium z możliwością podłączenia do zdalnego repo na GitHubie.

W ramach konkursu chciałbym przede wszystkim zaimplementować takie funkcjonalności jak:

  • Inicjalizacja, klonowanie repozytoriów
  • Drzewo commitów
  • Możliwość commitowania
  • Podgląd zmian na pliku z możliwością częściowego włączenia/wyłączenia zmiany do commita
  • Branching, a więc dodawanie nowych branchy, płynne przełączanie się między branchami, usuwanie
  • Komunikacja ze zdalnym repozytorium – na pewno chciałbym zaimplementować mechanizm uwierzytelniania jak i również mechanizmy push pull.

Dodatkowo bardzo chciałbym zaimplementować prosty MergeTool. Będzie to trudne zadanie zapewne ale się postaram!

Lista spora, czasu mało, zobaczymy ile z tych funkcjonalności uda mi się zrealizować.

Dlaczego

Dlaczego taki pomysł, co mną kierowało wybierając taki projekt. Od zawsze chciałem poznać bardziej git’a coś więcej niż komendy commit i push. Chciałbym z tego stworzyć pełnowartościowe narzędzie do zarządzania swoim repo.

Chyba dwa lata temu przeczytałem książkę „TDD. Sztuka tworzenia dobrego kodu” autorstwa Kena Beck’a, jedną z ciekawszych rzeczy w niej był przykład na którym Ken pokazywał pisanie testów jednostkowych. A mianowicie pisał on testy do swojego własnego frameworka do tworzenia testów jednostkowych. Strasznie mnie to zaciekawiło. Pomyślałem sobie, fajnie było by z robić taką aplikację/kawałek kodu w podobnym stylu. Dzięki czemu na bieżąco bym wiedział czego mi w tej aplikacji brakuje, brakuje mi megreTool’a to go dopiszę. No i chodziłem z tym pomysłem i myślałem aż doszedłem do gita. Od jakiś dwóch lat w firmie w której aktualnie pracuje korzystamy z gita. I od samego początku nie podobały mi się dostępne na rynku toole do zarządzania repo. Tak wiem jestem lamą i powinienem korzystać z konsoli!! no i jeszcze z Vima !! Ale nie, nie jestem w stanie się do tego przyzwyczaić. Mając te dwie rzecz czyli chęć napisania git gui oraz „samo testującej” się aplikacji pewnego dnia stwierdziłem: to może być to!. Już w poprzedniej edycji chciałem się wziąć za to ale niestety brakło mi czasu i chyba troszkę odwagi. albo może czas był ale tego drugiego nie 😉

Jak

Co do samego projektu i jego formy. Początkowo chciałem napisać to w elektronie jak gitKraken lub VisualStudio Code, ale nie chciałem iść w 100% w JS. Postawiłem w pewnik. Aplikacja w formie SPA gdzie w backendzie będzie standard MVC + WebAPI na .NET. Do obsługi gita będę wykorzystywał bibliotekę libgit2sharp. Całość chciałbym utrzymać w metodyce TDD.

Jeżeli chodzi o frontend to tu mam troszeczkę mniej doświadczenia. Pisałem kiedyś coś w Angularze2 ale wydawał mi się za „ciężki”, dlatego postanowiłem spróbować sił w czymś mniejszym, z pośród miliona frameworków dla JS na placu boju pozostał: weterana ReactJs oraz w „miarę” nowy VueJs. Jeszcze nie wiem który z nich wybrać, moja wiedza jest zbyt mała, poczytam i zobaczę co będzie lepsze dla mojego przypadku.

Oczywiście chciałbym również wykorzystać testy jednostkowe do testowania frontendu.

Kiedy

Mamy 10 tygodni, to zarazem dużo, trzeba napisać aż 20 postów, a zarazem mało na taki projekt. Nie chciałbym zbyt mocno skakać pomiędzy backendem a frontendem więc postanowiłem to podzielić. Tydzień skupie się na backendzie a tydzień na wyglądzie i tak do końca wymiennie. Zobaczymy co z tego wyjdzie, już wydaje mi się że jednak na JS oraz Css pójdzie więcej czasu niż przypuszczam ale ważne mieć jakiś plan.