Sztuka commitowania pracy

14

Sztuka tworzenia oprogramowania otoczona jest baaardzo wieloma “złotymi zasadami”. Ich przestrzeganie nie gwarantuje co prawda sukcesu, lecz może znacznie poprawić jakość efektów naszej pracy. Chociażby SOLID

Dobrze jest zdawać sobie sprawę z tego, że nie tylko na poziomie kodu możemy pozytywnie wpłynąć na kształt projektu. Tak banalna pozornie sprawa jak sposób tworzenia commitów do systemu kontroli wersji może drastycznie zwiększyć komfort pracy nad systemem oraz ułatwić jego rozwijanie.

Decentralizacja!

Niestety scentralizowane systemy kontroli wersji nadal są bardzo popularne i wykorzystywane na co dzień w wielu firmach. Oprócz bardzo wielu oczywistych wad (wydajność, uniemożliwianie efektywnej pracy offline itd, o czym dawno temu pisałem m.in. w poście “Dlaczego już nie lubię SVN“) niosą one również ukryte niebezpieczeństwo: zachęcają do korzystania z repozytorium kodu jak z serwera FTP, do którego byle jak i byle kiedy dokleja się kolejne paczki zip z datą utworzenia. Albo przynajmniej nie zachęcają do innych praktyk. A przecież starannie budowana historia projektu to zdecydowanie coś więcej!

Często spotykane zwyczaje u użytkowników TFSa czy SVNa to na przykład “wysyłam commit przed wyjściem z pracy do domu”. Albo “wysyłam commit po zaimplementowaniu całego ficzera/taska/bugfixa nawet jeśli zajęło mi to X dni”. Podczas gdy można to zrobić o WIELE lepiej, unikając “śmietnika historii”, praktycznie zerowym nakładem pracy.

O czym warto pamiętać konstruując pojedyncze checkiny?

SRP

Pierwsza i najważniejsza zasada w budowaniu commitów: każdy z nich powinien być skoncentrowany na rozwiązywaniu jednego malutkiego problemu. To znaczy: nie powinien zawierać niepowiązanych ze sobą bezpośrednio zmian dotykających wielu plików. Zmiany zgrupowane w jeden commit powinny odnosić się do jednego niewielkiego kroku jaki wykonaliśmy na drodze do zakończenia implementacji danego zadania.

Commity (sic!) powinny być zgodne z SRP

Pewnie wszyscy tutaj znają regułę Single Responsibility Principle., prawda? Skojarzenie z SRP powinno pojawić się tutaj automatycznie. TAK, chodzi dokładnie o to! Tak jak klasa w kodzie nie powinna być odpowiedzialna za wiele czynności, tak samo jedna wrzutka do systemu kontroli wersji powinna zawierać zmiany ściśle ze sobą powiązane, tworzące logiczną całość!

Wartość takich commitów, szczególnie przy code review, jest wprost nieoceniona. Idealnie byłoby, gdyby nawet zmiany dotyczące jednego pliku były traktowane osobno – w Gicie z pomocą przychodzi w tym przypadku komenda “git add –patch”.

Częstotliwość

To niesie za sobą kolejną praktykę: powinniśmy CZĘSTO commitować kod. Reguła “jeden commit dziennie” jest głupia i bezsensowna. Podczas bardzo pracowitego dnia, gdy wpadnę w szał kodowania, zdarza mi się wygenerować i ze 30 commitów podczas normalnej kilkugodzinnej pracy. I nie jest to nic dziwnego ani tym bardziej złego. Dzięki temu patrząc w historię projektu dokładnie widzę co, kiedy i dlaczego zrobiłem.

Poboczną korzyścią z takiego postępowania jest fakt, że przez większość czasu pracuję na “czystym” kodzie. Tzn zmiany wykonane w stosunku do poprzedniego commita są bardzo niewielkie, do ogarnięcia w ciągu kilku sekund. Nie potrafię opisać jak olbrzymi komfort daje to programiście. Sam bardzo się zdziwiłem, gdy doświadczyłem tego po raz pierwszy. Chcielibyście w prawie każdej chwili pracy nad projektem mieć uczucie “nieważne co zrobię – nie mogę nic zepsuć, bo w razie czego cofnę się do poprawnego kodu sprzed kilku MINUT (a nie GODZIN czy DNI)“? To już wiecie jak. Naprawdę bardzo, bardzo polecam. To jak wyzwolenie dev-umysłu:).

Stosujesz TDD? Rozbuduj cykl: red -> green -> refactor -> commit

Byłem w szoku jak mocno mi tego brakowało gdy przez kilka dni pracowałem na “czystym” TFSie, gdzie podobna praktyka jest po prostu niemożliwa (bo commity od razu idą na serwer). W każdej chwili mogę zerknąć na “diff” w stosunku do poprzedniego commita i w kilka sekund zorientować się w aktualnej sytuacji – co już zrobiłem, a co mi zostało do zrobienia. I nie widzę przed oczami całych ekranów zmodyfikowanego kodu, przez co diff stałby się bezużyteczny.

Stosujesz TDD? Zmodyfikuj swój cykl pracy rozszerzając go do: red -> green -> refactor -> commit.

Kiedyś jako wadę takiej praktyki przedstawiono mi fakt, że może to dosłownie “zarżnąć” build server skonfigurowany tak, aby wykonywał build czy nawet deploy dla każdego checkina… No i faktycznie, jeśli w takiego TFSa, przyzwyczajonego do 10 buildów dziennie, wrzucić w ciągu 5 minut 50 commitów to pewnie dopiero następnego dnia byłby dostępny dla reszty zespołu (oczywiście nie tylko TFS sobie z tym prawdopodobnie nie poradzi, wszystko zależy od hardware na jakim serwer stoi). Ale to nie jest problem programisty – to jest problem do rozwiązania dla administratora (np można skonfigurować build server tak aby uruchamiał się cyklicznie a nie dla każdej wrzutki; oczywiście cywilizowane build servery wspierające DCVS potrafią budować kod przy każdym “push” a nie “commit” co eliminuje tą kwestię).

20 commitów lokalnych != 20 commitów wysłanych na serwer

Plus trzeba mieć na uwadze fakt, że 20 commitów lokalnych wcale nie musi przekładać się na 20 commitów wysłanych na serwer. Przed wykonaniem PUSH trzeba przejrzeć swoje zmiany (“git log origin/master..master“) i w razie potrzeby scalić niektóre commity. Szczegółowość niezbędna przy pracy bieżącej wcale nie musi być konieczna w perspektywie globalnej historii projektu, wtedy niezastąpiona jest komenda “interactive rebase” z Gita.

Commit messages

Sama paczka kodu podpisana nazwiskiem autora oraz datą nie jest wystarczająca. Bez pisania sensownych “commit messages” daleko nie zajedziemy. Tutaj trzeba znaleźć złoty środek, aby z jednej strony wystarczająco dobrze opisać wprowadzone zmiany, a z drugiej – nie duplikować informacji zawartych w narzędziu do zarządzania projektem. Ja zwykle staram się zawierać tam informacje takie jak: typ taska, jego ID oraz krótki opis zmian (jakie i dlaczego). Jeśli to nie wystarcza, po dwóch enterach rozwodzę się trochę bardziej. Dla Gita istnieją bardziej szczegółowe praktyki (tutaj można zacząć o tym czytać). Sam się zdziwiłem jak niesamowicie upraszcza to wypuszczanie kolejnych wersji oprogramowania wraz z “release notes”: wystarczy wrzucić log do pliku, usunąć mało znaczące wpisy i… już! Przykład: [bug#2876] clicking on “OK” button works in IE 8.

Log z kontroli wersji to Release Notes!

Nadzieja

Mam nadzieję, że weźmiecie sobie ten post do serca i spróbujecie jeszcze bardziej ulepszyć proces commitowania kodu. Uwierzcie: to naprawdę robi różnicę. Pierwotnie, bezpośrednio po przesiadce z SVN (brrr…) na Gita, byłem bardzo zaskoczony naciskiem kładzionym w Gicie na częste commity i staranne budowanie historii. Szybko przekonałem się, że to nie są bezpodstawne dyrdymały tylko faktycznie istotne zagadnienia. Traktowanie repozytorium jak kupy kodu do której dokleja się kolejne paczki i nigdy nie zerka wstecz to jak kodowanie jednym palcem. Po co skoro można lepiej?

Wanna proof? Zerknij na moje zachwyty nad komendą git bisect!

Mając do dyspozycji tak zaawansowane narzędzia nie redukujmy ich roli wyłącznie do backupu kodu. Repozytorium oferuje o wiele, wiele więcej. A solidnie skonstruowana, dobrze opisana historia projektu rozbita na malutkie kawałeczki pozwala na bardziej komfortową pracę nad rozwojem projektów… Zachęcam zresztą do lektury moich zachwytów nad komendą git bisect: bez opisywanego tutaj sposobu commitowania kodu takie sztuczki stają się o wiele mniej użyteczne.

Niech życie będzie lepsze,
Procent, the Committer

Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

14 Comments

  1. Fajny wpis!!! Dzięki.

    Próbowałeś już może używać najnowszego TFS'a 2012, gdzie w końcu pojawia się opcja lokalnego repozytorium. Wiem, że pewnie nie ma tyle fajnych tricków co Git, ale może warto się temu przyjrzeć?

  2. barozi, dzieki:)
    nowy tfs nie ma lokalnego repozytorium – ma 'local workspaces' co sprowadza się do tego że VS pozwala na edytowanie plików gdy jesteś offline:) ciężko to nazwać lokalnym repozytorium, bo taka możliwość bez łaski powinna być dostępna od zawsze, nie możemy mówić o "lokalnej kontroli wersji" jeśli nie da się stworzyć lokalnego commita

  3. Praktycznie wszystko co napisałeś stosuje się również do SVN. Używając od paru lat do własnych projektów Mercuriala, a SVN (z serwerem CCNET do zapuszczania kompilacji i testów) w pracy zauważyłem dziwny paradoks. Mercurial przyzwyczaił mnie do b. częstego commitowania, z czego nie rezygnuję w firmie, ale SVN zmusza mnie do większej dyscypliny. Np. dzielenia implementacji nawet dużych "ficzerów" na małe kroki. Do pilnowania "Open Closed Principle" – nawet rzeczy – nowe klasy/metody. Rozdzielania refaktoringu od naprawy bugów/rozszerzania funkcjonalności. Staram się nigdy nie "odjeżdżać" z kodem na więcej niż kilkadziesiąt minut pracy od działającej wersji produkcyjnej (słabość SVN w utrzymywaniu/scalaniu wielu gałęzi, powoduje, że stosujemy często model trunk == produkcja, branch tylko przy wystawce – jeśli wszystko jest ok to obywa się bez scalania). "Policjant" w postaci serwera CI na mnie działa :). Podsumowując, można nawet z centralnym repozytorium osiągać kilkadziesiąt commitów dziennie.

  4. orientman,
    Tak, można, ale nie spotkałem się z takim podejściem u ludzi pracujących z svn czy tfs – jesteś pierwszy:). Dodatkowym utrudnieniem jest to, że w DVCS przed wysłaniem kodu można zrobić sobie review i szacher-macher z lokalnymi commitami, a w svn/tfs wszystko od razu idzie w świat. Więc wymaga to większej uwagi… no ale fakt, da się na pewno.

  5. Zgadzam się wszystkimi czterema kończynami, częste małe commity z jedna funkcjonalnością są nawykiem z którego nie ma później powrotu. U mnie szczególnie ten nawyk się nasilił gdy na dobre zacząłem pracować z code reviews. Działało to w dwie strony – z jednej nie chciałem powodować konfuzji u innych programistów czytających mój kod a z drugiej sam nie znosiłem czytać wielkich changesetów które robiły 100 rzeczy.

    Poza tym częste commity narzucają pewną klarowność w nawykach myślowych – gdy tylko przy pisaniu kawałku kodu łapię się na tym że myślę "o, przy okazji zrobię jeszcze ten refactoring" zaraz zapala mi się czerwona lampka i wracam do poprzedniego toku myślowego a ten rafactoring zapisuje sobie gdzieś z boku na liscie todo by później wykonać wraz z innym commitem.

  6. @procent z 10 grudnia 2012 12:38
    "orientman,
    Tak, można, ale nie spotkałem się z takim podejściem u ludzi pracujących z svn czy tfs – jesteś pierwszy:). "

    Musisz rozszerzyć swoją listę: przeciętny dzień z svn u mnie to kilka-kilkanaście commitów, rekordowe to po kilkadziesiąt. Oczywiście wszystkie opisane mniej więcej w zgodzie z powyzszymi wytycznymi.

    Oczywiście z powstania powyższego tekstu można się tylko cieszyć. A z doświadczenia wiem, że niektórych naprawdę ciężko przekonać do napisania w komentarzu czegokolwiek (oczywiście wiem, że można to wymusić technicznie), a już czegokolwiek sensownego jeszcze trudniej.

  7. A propos gita – mam jedną zagwozdkę, która mnie niepokoi. Może to nie zdarza się aż tak często, ale uważam to jednak za wtopę.
    Otóż, gdy modyfikujemy *znacznie* plik i przy okazji zmieniamy jego nazwę, to może zawieść sprytne wykrywanie zmiany nazwy. I git może potraktować to jako usunięcie starego pliku i dodanie nowego. Wiadomo, można użyć git mv, które to polecenie zasugeruje rozbicie tego na 2 commity. Więc bedziemy mieli najpierw commit z rename, później z modyfikacją. W historii(do oglądania akurat używam TortoiseGit) można zaznaczyć follow rename i wszystko bangla.

    Teraz wyobraźmy sobie, że robimy rebase paru(niekoniecznie tylko tych dwóch) w wyniku, którego te 2 commity zostają scalone. I w historii mamy – usunięcie i dodadanie nowego pliku! Tracimy historię. Napotkałeś coś takiego? Można jakoś temu zaradzić??

    A w ogóle fajny blog, szkoda tylko, że ostatnio notki takie rzadkie. Praca w korpo jednak wciąga? :-)

    • Piotr Tokarski on

      Hmm ale same z siebie commity się nie scalą nie ;) ?

  8. wjtk606,
    Ciekawy problem, nie spotkałem się z taką kwestią. Nie ma co winić Gita że nie rozpoznaje rename jeśli zmienia się większość treści pliku – w końcu w Gicie zawsze chodzi właśnie o zawartość. A co do rebase – to raczej zwykle powinno być robione świadomie, więc i tutaj rozwiązaniem jest wykonywanie operacji które faktycznie chce się wykonać i których rezultat jest oczekiwany.

    A co do bloga – dzięki:) Jak widać ostatnio staram się wznowić pisanie. Praca nie przyczyniła się do przerwy, raczej zwykłe życie w ekstremalnych odsłonach (w sensie narodziny i śmierci).
    A Predica to nie korpo – ani pod względem wielkości firmy ani pod względem kultury. W korpo bym tak długo nie wytrzymał.

  9. Napotkałem to raczej w wyniku testów, niż realnej pracy, ale jakiś tam drobniutki problem jest. W rzeczywistości ryzyko takiego przypadku jest pewnie minimalne.

    Ok, więc będę czekał i czytał! :-)

  10. Pingback: kłołt Commit Driven Development ankłołt | Maciej Aniserowicz o programowaniu

  11. Piotr,
    Chyba nie rozumiem:). Więc odpowiem na dwa sposoby:
    1) w gicie można scalać commity np komendą “git rebase -i”
    2) przy wysyłaniu commitów gitem do TFS zostaną one scalone jeśli użyjemy komendy “git tfs checkintool”, nie zostaną scalone przy pomocy “git tfs rcheckin”