Mam masę rozgrzebanych tematów. Ego się pisze, ale brakuje mi trochę czasu i trochę więcej motywacji. Gdzieś tam jest jeszcze temat drzewek Merkle. Jeszcze książka się pisze…

Po prostu czasami nie da się wszystkiego naraz. Dlatego dzisiaj temat trochę z pogranicza programowania i pożycia projektowego.

Czym jest greenfield?

Jeżeli ktoś w naszej branżuni mówi, że „dostał mu się greenfield”, to oznacza jedno. Ma to szczęście, że zaczyna z pustym repozytorium. Zespół zaczyna od zera. Może swobodnie kształtować architekturę, dobierać narzędzia i tworzyć kod według Najlepszych Praktyk Rynkowych™. Niby idealnie, ale nie do końca. Jeżeli projekt jest tworzony na potrzeby klienta, który nie ma doświadczenia ani zaplecza, to rzeczywiście mamy dużą swobodę. Jedynym ogranicznikiem jest tak naprawdę budżet klienta i nasz profesjonalizm. Profesjonalizm rozumiem tutaj jako pewną etykę pracy, czyli niewciskanie klientowi rozwiązań, których nie potrzebuje.

A co w przypadku gdy greenfield jest realizowany u dojrzałego klienta? Wtedy pojawia się więcej ograniczeń. Ma on wypracowane m.in. standardy kodu, architekturę i procedury. Do tego dodać należy całą masę drobnostek jak np. nazewnictwo gałęzi w repozytorium albo szablon wiadomości wypychanego kodu. W tym przypadku nie mamy do czynienia z greenfieldem. Raczej jest to taki brownfield. Niby robimy coś nowego, ale nie do końca. Przed nami nie ma łąki, a ugór.

Jest jednak jeszcze trzecia możliwość. Klient może chcieć zdefiniować od nowa swoje zasady. Ma przestarzałą infrastrukturę, istniejące rozwiązania osiągnęły kres możliwości technologicznych, a organizacja rozrosła się i musi szybko znaleźć nową drogę. W tym przypadku możemy mówić o „prawdziwym” greenfieldzie. Organizacja jest na tyle duża i zmotywowana (lub zdesperowana), że praktycznie znika ograniczenie budżetu. Oczywiście nie oznacza to, że możemy robić wszystko, ale mamy naprawdę duże pole do popisu.

Po raz drugi robię tego typu projekt. I dzisiaj będzie o „otoczce”.

Co każdy projekt mieć powinien, a niewiele ma?

No właśnie… Projekt jest co do zasady zadaniem zespołowym. Zespół tworzą nie tylko osoby techniczne, ale też tzw. „biznesowi”, którzy poza głównym celem, jakim jest realizacja projektu, muszą też „zaliczać” kolejne cele cząstkowe. Te cząstkowe cele, to oddawanie kolejnych elementów aplikacji lub systemu. Sama praca programistyczna w tym miejscu jest drugoplanowa, ponieważ liczą się artefakty. Te znowuż najłatwiej jest opisać metrykami. Ergo, projekt powinien mieć pewne metryki.

Metryki mogą być różne, począwszy od prymitywnego pokrycia kodu testami, poprzez złożoność cyklomatyczną, a np. na regresji kończąc. Trochę tego jest. Jednak wszystkie te metryki można stosunkowo łatwo zebrać i na upartego łatwo też je spełnić. Nawet na chama.

Poza metrykami istnieje też pewna klasa wymagań, które są trudniejsze w opisaniu. Istnieją pewne formalizmy, których zadaniem jest zapewnienie, że projekt będzie w prawidłowy sposób funkcjonował w ramach organizacji. Bełkot? A co powiesz np. na schemat nazywania commitów, by można było z nich generować listę zmian? Albo sławetny „JĘZYK WSZECHOBECNY”, którego zadaniem jest zapewnienie, że wszyscy zaangażowani w projekt mają taką samą definicję różnych bytów?

Conventional Commits

Zajmę się jednym, dla mnie obecnie najciekawszym w tym momencie zagadnieniem. Jako osoba odpowiedzialna za zespół (do czego to doszło) chcę mieć sformalizowany dziennik zmian. Po każdym sprincie chcę mieć możliwość wygenerowania dokumentu, który będzie można we w miarę tani sposób porównać z wymaganiami, specyfikacją, backlogiem, czy zamówieniem klienta i odhaczyć zakończone zadania. Najłatwiej jest wziąć historię zmian w kodzie i na tej podstawie wygenerować odpowiedni dokument. Jednak taki generator musiałby być albo bardzo cwany, by zrozumieć wiadomości i jakoś je obrobić, albo należy wypracować jakiś standard wiadomości. Co do zasady reguły pracy powinien uzgadniać zespół, ale znacznie łatwiej jest posłużyć się pewną sztuczką.

Gdy dochodzi do dyskusji na temat jakiegoś standardu pracy, to chcemy szybko ją zakończyć (bo zazwyczaj są to dyskusje jałowe jak sędzina Pawłowicz). Jak to zrobić? Dać zespołowi wybór mamy standard A lub B, lub siedzimy i opracowujemy własny. Ostatnia opcja choć kusząca, bo pozwala na prokrastynacjęW, to wymaga przygotowania narzędzi. Jakieś integracje, jakieś klikanie w CI, GitLabie czy innej JIRZe. Gotowe rozwiązania zazwyczaj mają gotowe narzędzia i integracje. Jednym takich gotowców jest Conventional Commits. Obok wersjonowania semantycznego jest to rozwiązanie dające najwięcej „standardowego kopa” w projekcie.

CC – kilka słów o regułach

Ogólna zasada jest taka, że wiadomość przy wypychaniu zmian wpisuje się w szablon:

Listing 1. Szablon wiadomości

(Typ)[zakres][!]: (Tytuł)

[Długi opis]

[Stopka]

Elementy w [] są opcjonalne, w () obowiązkowe. Ponadto Typ jest zestandaryzowany i jest zawężony do kilkunastu wartości takich jak feat, fix, chore, test, itp. Zakres odpowiada za uszczegółowienie obszaru, którego dotyczy zmiana. Może to być na przykład security, api, front, konfiguracja, itp. Przydatne jeżeli projekt ma naturę monolityczną lub jeden zespół utrzymuje wiele modułów w ramach jednego repozytorium. Następnie mamy Tytuł, czyli krótki opis zmian. Po nim następuje opcjonalny dłuższy opis, który może być wymagany w pewnych sytuacjach. Przykładowo możemy tam zamieścić linki do dokumentacji, czy też niektóre organizacje wymagają jakiegoś dłuższego opisu w konkretnym formacie. Na koniec mamy stopkę, która spełnia dwa zadania. Jeżeli zaczyna się od frazy BREAKING CHANGE:, to mamy do czynienia z czymś co łamie kontrakt. Identyczną rolę pełni wykrzyknik, który można umieścić po Typie i Zakresie. Stopka jest też miejscem, gdzie można umieścić gitowe trailery. Ciągi te będą interpretowane w specyficzny sposób. Próbkę tego typu zachowania mamy na githubie, gdzie można zamykać zadnia umieszczając ich numer poprzedzony słówkiem close.

Jak widać, możemy wykorzystać istniejący standard, by znormalizować wiadomości. Ale jak wymusić jego użycie?

Kontrola podstawą zaufania

Powyższe słowa są przypisywane Leninowi, choć historycy nie są co do tego zgodni. Podobne słowa przypisuje się też Stalinowi i stąd wiele problemów, ze źródłem. Maksyma to choć wydaje się sprzeczna, to dość dobrze oddaje ludzką naturę. Ufamy ludziom, że będą zachowywać się odpowiedzialnie i nie chcą sobie ani bliźnim zrobić krzywdy, a jednocześnie staramy się projektować rzeczy w bezpieczny sposób. Ludzie mogą popełniać błędy, a naszym zadaniem jest tak projektować procesy, urządzenia czy interfejsy, by minimalizowały szansę popełnienia błędu, a w przypadku jego wystąpienia ograniczały negatywne skutki.
Pracując z gitem podstawowym narzędziem, które może posłużyć do zapewnienia nam bezpieczeństwa, są wyzwalacze – hooks.

CC – walidacja wiadomości

Poniższy kod pozwalana sprawdzenie wiadomości i w razie czego wyświetla odpowiednią informację:

Listing 2. hook sprawdzający wiadomość

#!/usr/bin/env bash

set -eou pipefail

# Create a regex for a conventional commit.
convetional_commit_regex="^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z \-]+\))?!?: .+$"

# Get the commit message (the parameter we're given is just the path to the
# temporary file which holds the message).
commit_message=$(cat "$1")

# Check the message, if we match, all good baby.
if [[ "$commit_message" =~ $convetional_commit_regex ]]; then
   echo -e "\e[32mCommit message meets Conventional Commit standards...\e[0m"
   exit 0
fi

# Uh-oh, this is not a conventional commit, show an example and link to the spec.
echo -e "\e[31mThe commit message does not meet the Conventional Commit standard\e[0m"
echo "An example of a valid message is: "
echo "  feat(login): add the 'remember me' button"
echo "More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary"

exit 1

źródło

Jeżeli wiadomość nie pasuje do formatu, to całość zwróci błąd, a proces commitowania zostanie przerwany. No dobra, ale jak wymusić użycie tego rozwiązania?

Współdzielone hooki

Git przechowuje hooki w katalogu .git/hooks. Zatem nie są one współdzielone w zespole. Wystarczy jednak utworzyć katalog w projekcie i tam umieścić skrypty. Następnie katalog ten będziemy współdzielić jak inne pliki w projekcie. Ale to oczywiście nie wszystko. Kolejnym krokiem jest wskazanie gitowi nowego katalogu. W tym celu należ zmienić konfigurację w następujący sposób:

Listing 3. Nowa konfiguracja gita

git config core.hooksPath .hooks

W tym przypadku nasze skrypty znajdują się w katalogu .hooks. Ale czy to wszystko? Nie do końca. Jeżeli ktoś nie zmieni konfiguracji gita, to oczywiście nie będzie podlegać weryfikacji (pomijam włączenie weryfikacji na serwerze) i będzie nam bruździł w repo. Pozostaje wymusić taką konfigurację w inny sposób.

Wymuszenie konfiguracji

Opisana tutaj metoda jest „krzywa” tzn. opiera się o zewnętrzne narzędzia, w tym przypadku służące do budowania projektów. Na czym polega? Skonfigurujmy nasz projekt tak by każde uruchomienie mavena/mixa/npma/sbt czy czego tam używasz spowodowało wykonanie powyższego polecenia konfiguracyjnego gita. Przykładowo w mavenie będzie to wyglądać tak:

Listing 4. Dodatkowa konfiguracja mavena


<plugin>
    <artifactid>exec-maven-plugin</artifactid>
    <groupid>org.codehaus.mojo</groupid>
    <executions>
        <execution>
            <id>Git setup</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>exec</goal>
            </goals>
            <configuration>
                <executable>${basedir}/.hooks/setup.sh</executable>
            </configuration>
        </execution>
    </executions>
</plugin>

Przy czym skrypt setup.sh może ustawiać też inne elementy w projekcie. O tym kiedy indziej (na św. Nigdy pewno). Dodatkowo należy tę konfigurację zamknąć w dodatkowym profilu, który nie będzie uruchamiany np. przy tworzeniu kontenerów dockera.

Podsumowanie

Tworząc projekty greenfield zazwyczaj wpadamy w środowisko, w którym są już ustalone pewne zasady. Mamy narzucony stos technologiczny, jakiś szablon architektury i procedury. Niezwykle rzadko możemy jednak trafić na projekt, który poza budową rozwiązania od zera, ma też za zadanie ustanowić (nowe) reguły dla całej organizacji. W tym momencie musimy podejmować decyzje o tzw. duperelach. Przy czym, jak zwykle, trudniejszym elementem w tej zabawie jest wymuszenie przestrzegania reguł. Można, a wręcz należy, zaufać ludziom. Jednocześnie warto wprowadzić elementy (automatycznej) kontroli. Ich zadaniem jednak nie jest jebanie po ludziach (dlatego mechanizmy te nie mogą zbierać metryk), ale ograniczenie potencjalnych strat.

Na dłuższą metę będziemy w stanie znacznie sprawniej zarządzać techniczna stroną projektu. Skupić się na ciekawszych aspektach niż klepanie raportów dla kierownictwa i klientów.