GIT: Release branch w podejściu Trunk Based Development

Nagrałem screencast, gdzie tłumaczę jak podchodzić do release’owania kolejnych paczek gdy korzystamy z Gita (a w szczególności z Trunk Based Development).

Strona, z której korzystałem to https://learngitbranching.js.org/. Można na niej pouczyć się branchowania bo ma interaktywne tutoriale. Ja korzystam z tej opcji bez tutoriali czyli https://learngitbranching.js.org/?NODEMO

(wiem, że są szumy, ale „Done is better than nothing”, kupię mikrofon)
https://youtu.be/TzJzy8nMqHk

trunk based development i release branches
Tak wygląda historia na końcu

Komendy, które były wpisywane w „konsoli”:

git checkout -b develop
git commit
git tag ver-2.1
git branch rel-2.1 // drogi się rozchodzą, QA testuje, team dev’ów tworzy kod
git commit // kolejny feature
// bug
git commit
git tag fix-1
// bug 2
git commit
git tag fix-2
git checkout rel-2.1
git cherry-pick C4
git cherry-pick C5
git checkout develop
git commit // kolejny feature
// Minął miesiąc(2), a może sprint. Decydujemy się na kolejny release
git commit
git tag ver-2.2
git branch rel-2.2
git commit // kolejny feature
// QA znalazł buga
git commit
git tag fix-3
git checkout rel-2.2
git cherry-pick C9
// gdy 2.2 poszło na produkcję to
git branch -D rel-2.1
commity bez opisu oznaczają ze tu się dużo mogło dziać.

Wstępna konfiguracja gita

Powiedzmy, że przypadkiem szykujesz się do DevWarsztatów z gita 😉 i coś tam trzeba zainstalować.

Konsola

Konsolę ściągamy ze strony https://git-scm.com/downloads dla odpowiedniego systemu. Klikamy wszędzie „Dalej” 🙂

Podstawowe ustawienia

Ustawiamy swoje dane

git config --global user.name "Krzysztof Morcinek"
git config --global user.email "your-email@gmail.com"

Domyślnym edytorem jest vim, którego wiele osób nie zna. Polecam więc zmienić na np notepad.

git config --global core.editor notepad

GitHub

Zakładamy konto na GitHubie https://github.com/join

Source Tree

Nieobowiązkowe, można ściągnąć z https://www.sourcetreeapp.com/ (trzeba zarejestrować email, to jedyne wymaganie). Wersje tylko dla Mac i Windows.

Kdiff3

Instalujemy: https://www.google.com/?q=kdiff3+download

Konfiguracja (zrobimy też to na warsztatach):

Na Windowsie dodajemy na dole pliku. ~/.gitconfig (może trzeba ścieżkę dostosować):

[merge]
tool = kdiff3
[mergetool "kdiff3"]
path = c:/Program Files/KDiff3/kdiff3.exe
[diff]
tool = kdiff3
guitool = kdiff3
[difftool "kdiff3"]
path = c:/Program Files/KDiff3/kdiff3.exe

Na Linuxie dodajemy na dole pliku. ~/.gitconfig:

[merge]
    tool = kdiff3
[diff]
    guitool = kdiff3

Gdzie chowasz swoje hacki?

Jest bug! 😦

function foo(){
    applyResultToDataTable();
    // more code
}

Wywołanie takiej funkcji powoduje błąd w rozmwiarze stylowania i tabelka z danymi jest niewidoczna. Na jednym środowisku działa na innym nie działa. Bug trudny do zlokalizowania/zrozumienia/naprawienia.

Ale można przyhaczyć 🙂 Okazuje się, że „malutki” timeout załatwia sprawę:

setTimeout(function(){
    applyResultToDataTable();
    // more code
}, 100);

Hacki się mszczą, ale teraz musimy dostarczyć paczkę

Dzisiaj zakładamy, że takie hacki się mszczą (dlaczego to osobny ciekawy temat). Kolejne założenie to takie, że w tym akurat momencie tak już zrobimy. Brak czasu lub skilla nie pozwala. Co więć dalej zrobić?

Na pewno można dodać opis:

// Workaround for columns width = 0px; after loading data to DataTables
setTimeout(function(){
    applyResultToDataTable();
    // more code
}, 100);

Kolejny tydzień, kolejny ten sam bug

Kod żyje, innego dnia dowiadujemy się że testy na innym widoku znalazły ten sam problem. I znów w innym miejscu dopisujemy:

// Workaround for columns width = 0px; after loading data to DataTables
setTimeout(function(){
    applyResultToDataTable();
    // more code
}, 100);

Wróc! Najczęściej tak się nie zdarza, najczęściej inny developer nie zna akurat „tego” hacka. Brak komunikacji i przesunięcie w czasie sprawiły, że nikt mu o nim nie powiedział. Więc dopisuje na nowo tego samego (po straceniu czasu na rozkminę), albo znajdzie nowy sposób załatania (i znowu straci czas). Kolejny smutny hack 😦

Bądź EXPLICIT

Ja sugeruję aby takie drogi na skróty od razu opisywać osobnymi metodami.

/*
 * Workaround for columns width = 0px; after loading data to DataTables
 */
function addTimeoutToFixNarrowDataTables(action){
    setTimeout(function(){
        action();
    }, 100);
}
function foo(){
    addTimeoutToFixNarrowDataTables(function(){
        applyResultToDataTable();
        // more code
    }
}

Czy nazwa addTimeoutToFixNarrowDataTables() jest właściwa? Tutaj będzie bardzo wiele opinii. Ja wychodzę z założenia, że lepiej wpisać tutaj wiele rzeczy, bo ktoś szukając rozwiazania swojego problemu w przyszłości znajdzie przynajmniej jedno słowo (korzystając z Search Everywhere). Zgadzam się, że w normalnym kodzie nie chciałbym takich dużych metoda, ale to jest właśnie Hack.

A gdzie umieścić taką metodę? Można gdzieś w klasie z której jest wołana, może jest bazowy kontroller lub serwis z którego kod potrzebujący może wywołać hacka. Ja bym jednak stworzył osobną klasę nazywającą się YouNameItHacks (ControllerHacks, DataTablesHacks, itd). Takich rzeczy nie należy ukrywać. Ktoś może zmarnować dużo czasu zanim znajdzie „dlaczego nowo pisany moduł na takiego dziwnego buga…”.

explicit hacks in visual studio solution

Jak nazwać? Gdzie umieścić? Podzielcie się waszymi pomysłami w komentarzach.

[Git] Czasem dopiero po chwili wiesz jak dobrze nazwać

Tak najpierw nazwałem commity:
Install angular
AngularJS TypeScript typings
Update jQuery definitions for TypeScript

A później samo do mnie doszło że lepszymi nazwami będzie:
Install angular with TypeScript typings (z połączenia dwóch pierwszych)
Update jQuery definitions for TypeScript

Dzięki funkcjonalności rebase interactive możemy pozmieniać nazwy (posklejać commity), aby było to bardziej sensowne. Opisuję jak to wyklikać z GUI.

[GIT] Szybkie przechodzenie między repozytoriami w konsoli

Idziemy do katalogu z zainstalowanym GITem do folderu etc (C:\Program Files\Git\etc).

Jest tam plik bash.bashrc.

Plik edytujemy jako administrator i na końcu dodajemy aliasy:

To samo można uzyskać na wiele innych sposobów (ten akurat działa w mojej konfiguracji systemu). Ten tutaj sprawia, przy otwieraniu konsoli gitowej dodane zostaną do basha aliasy. Dzięki temu możemy wyjść poza to co można dodać w .gitconfig i możemy korzystać z łączenia z komendami konsolowymi takimi jak grep w przykładzie.

Co gdy każdy programista ma inne lokalne ustawienia aplikacji?

Całkiem normalną sytuacją jest, gdy każdy sam sobie instaluje server bazy danych (powiedzmy SQL Server). Wtedy u każdego ta nazwa może być różna. Ktoś zainstalował wersję SQL Express, ktoś inny ma już kilka wersji SQL (2008, 2012, 2014, 2016, …) różnie nazwanych, u kogoś innego w poprzednim projekcie instancja musiała się konkretnie nazywać.

Podmiana w configu i ignorowanie

Prędzej czy później dochodzimy więc do sytuacji, że aby odpalić taką aplikację lokalnie każdy sobie podmienia connection string i robi tak, żeby System Kontroli Wersji nie widział tego jako zmiany, czyli żeby nie poszło z następnym commitem.

Wspólny Setup

Inne rozwiązanie to jakiś wspólny setup stawiania całego środowiska. Kiedyś było to klonowanie maszyny wirtualnej, teraz będą to kontenery lub skrypty, które to lokalnie ustawią. Jeśli jedynym problem, który próbujemy rozwiącać jest ta nazwa instancji servera, to takie podejście to duuuże przekombinowanie.

Proste rozwiązanie w kodzie

To wersja najprostsza, ale widać całą ideę. Dodajemy nazwę maszyny do nazwy connection stringa:

Niezmienne rzeczy jak providerName zostały usunięte.

W ostatnim projekcie na 4 developerów były 3 różne nazwy.

Czy warto korzystać z float zamiast double (oszczędność pamięci)

TLDR: Nie, nie warto.

618px-IEEE_754_Double_Floating_Point_Format.svg
Obrazek z https://en.wikipedia.org/wiki/Double-precision_floating-point_format

Wszędzie double

Wszędzie gdzie potrzebna jest wartość zmienno-przecinkowa używam double, ponieważ:

  • nie chce mi się rozkminiać, które wartości mogą być mniej dokładne (idąć dalej, co to znaczy mniej dokładne i kto o tym decyduje) – szkoda czasu.
  • gdyby system miał czasem float a czasem double – powodzenia w zabawie z konwersją, bo żeby je pomnożyć lub dodać to zostaną rozszerzone do double, a potem trzeba skrócić do float – szkoda czasu.
  • gdyby system miał wszędzie float – to by oznaczało, że ktoś świadomie przehandlował pamięć w zamian za dokładność – przypadek bardzo graniczny – w większości przypadków tak nie będzie, szkoda czasu.

Jeden raz użyłem float

Jedyny przypadek gdy w moim kodzie świadomie użyłem floata był gdy zczytywane były dane z mikrokontrolera i one rzeczywiście były floatem.

Float nie jest szybszy niż double

Jeśli już chcemy zchodzić tak głęboko to z Float vs Double Performance wynika:


On x86 processors, at least, float and double will each be converted to a 10-byte real by the FPU for processing. The FPU doesn’t have separate processing units for the different floating-point types it supports.

ale znajdzie się przypadek szczególny, gdzie większy rozmiar doubla wpłynie na rozmiar danych wysyłanych jednorazowo do procesora i cache… itd.

Czy mnie to w ogóle powinno obchodzić?

Wchodząc na pole takich optymalizacji warto się cofnąć i zastanowić czy naprawdę każdy developer w zespole rozumie co to wszystko oznacza? Czy znajome są każdemu pojęcia bottleneck, false-sharing, branch prediction? Wiem, że w pisaniu aplikacji biznesowych nie każdy je zna i nie są one każdemu potrzebne. Wtedy jedynym drogowskazem jest pisanie w sposób możliwie prosty – wszędzie double.

A jak się ma do tego decimal?

Decimal służy do czegoś innego (głównie kwoty pieniężne), więc celowo pomijam go w tej dyskusji.

Dzielenie z resztą bez rzutowania na float

Aby dzielenie odbyło się z resztą operacja musi się odbyć na typach double a nie na int, czyli żeby wynik nie był zaokrąglony do inta. W praktyce dzielenie na intach:

int result = 4/3; // result == 1
Kiedyś rzutowałem w ten sposób:
int rowsNeeded = (int)Math.Ceiling((double)Sources.Count / 2);
A od dziś dzięki Grzesiowi będę zmieniał drugą liczbę na explicit double (2D):
int rowsNeeded = (int)Math.Ceiling(Sources.Count / 2D);

Analogicznie dla decimal (literka M)

decimal amount = 4M;

[StyleCop] Jakie severity (Warning czy Error) dla problemów zgłaszanych przez StyleCop?

Opiszę pomysł, który zobaczyłem w projekcie i bardzo mi się spodobał. Nie będę opisywał możliwych modyfikacji, które sobie wyobrażam. Rozumiem też, że będąc w innym projekcie (Context is King!) musiałbym go dostosować.

Errory tylko w Release

Gdy piszemy kod to zazwyczaj jesteśmy w „Debug”, i w takim trybie nie chcemy dostawać Errorów do StyleCopa. Powodów może być wiele: kod experymentalny, kod przeklejony z internetu, z poprzedniego projektu, itp. Albo po prostu każdy ma inną wrażliwość na to jak formatować kod. I lokalnie niech się każdemu kompiluje i pokazuje Warningi, ale niech się odpala.

Jednak w kodzie produkcyjnym, którym dzielimy się z wszystkimi, gdzie przechodzi Build Script na serwerze Continuous Integration – chcemy mieć czysto. Dlatego mamy drugi ruleset dla trybu „Release”, w tym drugim wszystkie reguły traktowane są jako Errory. W skrypcie jest więc osobny krok, który odpowiada za zbudowanie solucji w trybie „Release”.

Przykład ze skryptu PowerShellowego (używającego Psake):
https://gist.github.com/kmorcinek/20e30e2c1455d26ac40e2d749bfc61cf

Przykładowe rulesety (wycinki)

Debug:

<Rule Id="SA1405" Action="Warning" />
<Rule Id="SA1406" Action="Warning" />
<Rule Id="SA1407" Action="None" />

Release:

<Rule Id="SA1405" Action="Error" />
<Rule Id="SA1406" Action="Error" />
<Rule Id="SA1407" Action="None" />

Domyślnie wszystko jest Warningiem

Dzięki temu plik z regułami jest krótszy. Odstępstwo od reguły będzie Warningiem. W pliku *Debug.ruleset to co chcemy ignorować dodajemy jako:

<Rule Id="SA1407" Action="None" />

Jednak w pliku *Release.ruleset musimy severity tych wszystkich domyślnych reguł potraktować jako „error”.

Co za tym idzie najlepiej w obydwu plikach mieć explicite wszystkie reguły. Wtedy ręcznie w obydwu naraz zmieniamy gdy chcemy zmienić podejście do którejś reguły.

Konflikty korzystania z Configuration

Minusem jest to, że Configuration (Debug/Release) nie jest do takich zastosowań. Może być też używana przez inne narzędzia. Obecnie w projekcie gryzie się to z BenchmarkDotNet. Jest on odpalany tylko w trybie „Release” i wtedy StyleCop musi być na czysto. Więc nachodzą na siebie, ale da się z tym żyć, nie jest problematyczne.

Zalogowanie ustawień podczas startu applikacji

Zawsze przychodzi ten moment gdy trzeba rozwiązać buga na produkcji i jedyne co mamy to logi aplikacji. Okazuje się oczywiście, że nie ma wszystkich informacji w tych logach. Nie ma nawet całkiem podstawowych informacji.

Gdy nic nie widzisz

Spotkałem się z sytuacją gdzie był robiony request do serwisu, ale nie wiadomo było do którego dokładnie (brak urla w logach). Innym razem pliki były gdzieś przekopiowywane, ale nie było wiadomo gdzie. Dlaczego doszukanie się tych informacji jest takie trudne (choć nie powinno)?

search for bug in code

Przykładowo aplikacja wchodzi w skład większych bytów, jest uruchamiana prze inne aplikacje itd. Zdarza się, że w takich systemach nieporządek jest czymś powszechnym. Cykl releasowania jest skomplikowany na tyle, że nie możemy dorzucić kilku logów w problematycznych miejscach i wypuścić update’u.

Zaloguje wszystko raz, a dobrze

Moja propozycja, zrób jeden obiekt Settings, który zbiera wszystkie ustawienia, początkowe. Bardzo możliwe, że taki obiekt już jest (Od Krzyśka Seroki: W przypadku ASP.NET Core mamy IConfiguration). Podczas startu aplikacji trzeba te wszystkie ustawienia zebrać (z linii komend, z pliku/ów konfiguracyjnych, z bazy, wygenerowane ścieżki np. na podstawie UserName). To podejście jest dobre samo w sobie bo robimy to raz a serwis, który je udostępnia jest zarejestrowany w kontenerze Dependency Injection jako singleton.

Jeśli takiego obiektu nie mamy to możemy go stworzyć nawet dynamicznie. Możemy nazywać explicite pole lub pozostawić domyślne nazwy:

Następnie serializujemy ten obiekt do JSONa i zapisujemy w logu. Pełny przykładowy kod:

JSON po serializacji:

Bezpieczeństwo!

Trzeba jeszcze pamiętać o ukryciu ewentualnych haseł. Najprościej zrobić to nullując odpowiednie pola. Wiem, wiem, wiem, przyjdzie Junior, zapomni i hasła wyciekną… Przecież dzielicie się wiedzą w projekcie i macie Code Review, co nie?

Testivus – kompletne starożytne nauki na temat testowania

Moje motto na temat Unit Testów i wielu innych rzeczy:

Less Unit Testing Dogma
More Unit Testing Karma

Nie ma co pisać wstępów, trzeba przeczytać krótkiego i zwięzłego PDFa The Way of Testivus

Good advice on developer and unit testing, packaged
as twelve cryptic bits of ancient Eastern wisdom.

Testivus Unit Testing

Mnie to olśniło, rzadko zdarza się dorwać coś, co jest tak fajnie przygotowane i aż się prosi o wydrukowanie.

Podzieliłem się moim odkryciem tak, że wydrukowałem w kolorze (dzięki Sylwia Lasek 🙂 ), zbindowałem i rozdałem osobom zainteresowanym tematem aby je zainspirować.

Trafiłem na Testivusa szukająć przykładów dlaczego pułap Code Coverage nie powinien być żadnym celem. Na SO znalazłem wytłumaczenie What is a reasonable code coverage % for unit tests (and why)?

Static – dobry, zły i brzydki

Prywatne pola static – są bardzo NIE OK.
Metody static – są OK.

I o ile jest generalnie zgoda, że nie powinno się używać static w polach to chciałem pokazać, że metody będące statyczne są dobre. Czy powinny być statyczne to zależy od kontekstu. Ja stosuję to tak, że kiedy mogę to staram się przekazać argumenty i uczynić metodę statyczną. Przykłady takich metod z mojego kodu:

Przypadki, gdy zdarzają się publiczne właściwości:

  • ServiceLocator – generalnie antypattern, ale czasem inaczej się nie da i gdy wiem co robię to pozwalam sobie na to.
  • Puste obiekty, gdy sa immutable i mogą być reużywane, przykład EventArgs:
  • … (można dodać w komentarzach)

Przypadki, gdy można uniknąć statycznych publicznych właściwości:

  • Singleton => można rejestrować jako „AsSingleton” w kontenerze IoC.
  • Jest klasa (serwis), która ma stan, i takich instancji jest wiele w aplikacji. Dodatkowo potrzebuję dostęp do jakiejś statycznej kolekcji. Najprościej jest utworzyć jedno pole static, którego będą reużywać wszystkie instancje. Nie robiłbym tak. Lepiej jest tą część, która wydaje się być statyczna wydzielić do innego bytu i przekazać jako zależność, a w kontenerze zarejestrować jako AsSingleton (punkt wyżej).