„Był raz kupiec tak bogaty, że mógł wybrukować talarami całą ulicę. Ale nie zrobił tego, bo używał pieniędzy w inny sposób. Ile razy dał talara, dostawał z powrotem trzy. Był to w istocie dobry kupiec, ale mimo to musiał umrzeć.”
Latający kufer, Hans Christian Andersen
Kuferek (trunk) to gałąź w nieco już archaicznym systemie kontroli wersji SVN, w której znajduje się kod będący podstawą do realizacji nowych zmian. Są różne szkoły, ale znam nie więcej jak dwie podstawowe i jedną wariację odnoście tego co powinno znajdować się w kuferku:
- Najświeższy kod
- Stabilny kod
- Coś pomiędzy
Jaka różnica pomiędzy A, B i C? Postaram się wyjaśnić podług swojego najlepszego zrozumienia.
Zacznijmy od definicji. Paczką będziemy nazywali zbiór kodu lub kodów wynikowych wraz z konfiguracją i zmianami po stronie np. bazy danych, który zostanie zainstalowany na danym środowisku. Załóżmy, że paczka produkcyjna to taka, co zaraz trafi na system produkcyjny, a paczka kolejnego wydania to taka paczka, która zanim trafi na produkcję musi się jeszcze wytestować na środowisku(-ach) testowym(-ych). W jakimś miejscu (ściślej gałęzi w systemie VCS) należy trzymać kod, który zostanie zapakowany, jako paczka produkcyjna lub stanie się fragmentem paczki kolejnego wydania.
Artykuł dotyczy już dosyć przestarzałej metody zarządzania kodem poprzez VCS, które stopniowo wypierane jest przez DVCS takie jak GIT czy Mercurial. Wiele organizacji oparło swoje repozytoria o SVN i raczej nieprędko przeniesie się na GITa, natomiast niejednokrotnie w organizacjach wiedza dotycząca tego jak organizować kod w repozytorium, nawet w oparciu o SVN, bywa dosyć fragmentaryczna.
Co o kuferku mówi Wikipedia? Cytuję: „…In the field of software development, trunk refers to the unnamed branch (version) of a file tree under revision control. The trunk is usually meant to be the base of a project on which development progresses. If developers are working exclusively on the trunk, it always contains the latest cutting-edge version of the project, but therefore may also be the most unstable version. Another approach is to split a branch off the trunk, implement changes in that branch and merge the changes back into the trunk when the branch has proven to be stable and working. Depending on development mode and commit policy the trunk may contain the most stable or the least stable or something-in-between version.”
Słowo komentarza, „The trunk is usually meant to be the base of a project on which development progresses” – oznacza to tyle, że trunk jest czymś co wiemy, że jest dobre i możemy na tym pracować, żeby dostarczać coś nowego. Podejście A. można odczytać w tym stwierdzeniu: „If developers are working exclusively on the trunk, it always contains the latest cutting-edge version of the project, but therefore may also be the most unstable version”.
Opcja B. ukryta jest w zdaniu: “Another approach is to split a branch off the trunk, implement changes in that branch and merge the changes back into the trunk when the branch has proven to be stable and working.”
A opcję C. można odszukać w “something-in-between”
Zadziwiające jest, jak duży wpływ ma wybór podejścia do tego co siedzi w kuferku na proces zarządzania kodem i na to, jak będziemy organizowali sobie pracę. Każde podejście ma swoje wady i zalety, postaram się pokazać co dzieje się dla poszczególnych podejść i kiedy warto wybrać konkretne z nich.
Wyraźne różnice rysują się wtedy, gdy wyobrazimy sobie, że organizacja gotowa jest na prowadzenie prac nad kilkoma wydaniami równolegle, ale jednak nie przedstawię tego w szczegółach, gdyż zagmatwałoby to tylko opis.
A. Najświeższy kod
Podejście z najświeższym kodem w kuferku działa dobrze wtedy gdy implementujemy małe zmiany, a nad kodem pracuje bardzo dobrze zgrana grupa developerów (lub najlepiej zgrana, czyli jeden). Zmiany wprowadzane są bezpośrednio do kuferka (commit lub merge z gałęzi). Wydanie w tym przypadku realizowane jest z gałęzi kolejnego wydania (release) powstałej poprzez pączkowanie w jakimś momencie z kuferka, wszelkie poprawki do wykrytych błędów również implementowane są w gałęzi kolejnego wydania, gdy kod jest stabilny wypuszczany jest na produkcję i odkładany w gałęzi stable oraz wprowadzany do kuferka.
Stable jest głównie po to, by umożliwić dostarczanie krytycznych poprawek oraz udostępniać najbardziej stabilny kod. Co dzieje się z kuferkiem? Kuferek żyje dalej, dogrywane są kolejne zmiany.
Na poniższym obrazku mamy development podzielony na fazy:
- COMMIT – faza wprowadzania zmian do gałęzi z gałęzi poszczególnych zmian funkcjonalnych F1, F2…
- RELEASE-FIXING – faza przygotowania wydania
Wariacja A1. Wiele zmian przygotowywanych równolegle
Wyobraźmy sobie, że zmiany jakie chcemy dostarczyć są skomplikowane i pracochłonne, oraz że takich zmian jest dużo i nie do końca wiadomo, które z nich staną się fragmentem kolejnego wydania. Każda ze zmian otrzymuje swoją gałąź, która powinna być na bieżąco synchronizowana z kuferkiem.
Kłopotem może okazać się samo wprowadzanie zmian do kuferka, bo jeżeli zmian chcących się wprowadzić w tym samym czasie do kuferka będzie dużo, to kto szybszy ten lepszy… szczególnie gdy używamy jako repozytorium systemu typu DVCS, który wymusza wcześniejszą synchronizację przed wypchnięciem zmian.
Duże zmiany to duże problemy, dlatego przy wprowadzaniu dużych zmian do kuferka lądujemy z podejściem C.
Wariacja A2. Wiele wydań przygotowywanych równolegle
Kuferek pączkujemy w kolejną gałąź kolejnego wydania w dowolnym momencie, pozwalamy się dogrywać poprawkom, testujemy a potem wprowadzamy do kuferka. Więcej jak jedno wydanie przygotowywane równolegle będzie kosztowało nas bardzo dużo wysiłku, gdyż musimy wyrównywać każdą gałąź kolejnego wydania gałęzią wydania poprzedniego w przypadku poprawek i zmian, które są tam nanoszone.
Za: Umożliwia w miarę łatwe wprowadzenie równoległego przygotowania do wydań. W przypadku małych zmian i zgranego zespołu developerów prawie zawsze najświeższy kod jest gotowy do wypuszczenia na produkcję, czyli daje możliwość realizowania podejścia Continuous Delivery.
Przeciw: Trudno wycofywać zmiany z planowanego wydania. W przypadku dużych zmian kod w kuferku jest zawsze niestabilny, co powoduje, że przy wydaniach równoległych będziemy analizowali sporo wspólnych błędów dla wszystkich gałęzi. Narastający bałagan w kuferku.
Kiedy stosować: Implementacja małych zmian. Mała, zgrana grupa programistów implementująca zmiany.
B. Stabilny kod
Wyobraźmy sobie, że trunk = stable. No dobra, to gdzie robimy zmiany? Trzeba się rozpączkować. Do gałęzi kolejnego wydania powstałej w jakimś momencie dokładamy zmiany, które pochodzą ze swoich gałęzi rozwoju aktualizowanych na bieżąco z kuferkiem po każdej zmianie kuferka. Gdy zmiany zostaną zintegrowane i staną się stabilne to stają się nowym kuferkiem lub wprowadzamy je do kuferka. Niestety wprowadzanie zmian do kuferka może być błędogenne. Dlatego tak naprawdę w kuferku w tym podejściu jest „prawie” stabilny kod. Gałąź stable występuje jako referencja dla użytkowników zewnętrznych, którzy chcieliby lokalnie budować bieżący stabilny kod.
Wariacja B1. Wiele zmian prowadzonych równolegle
Jeżeli w kuferku ma być prawie zawsze stabilny kod, to niestety ale musimy wszelkie zmiany integrować i testować i gdy będą stabilne przenosić do kuferka lub go zamieniać z gałęzią kolejnego wydania o ile jesteśmy pewni, że nic w tzw. międzyczasie do kuferka nie trafiło. Kłopotem jest integrowanie zmian w osobnej gałęzi. Jeżeli zmiany integrowane będą w jakimś stopniu od siebie zależne, to nigdy nie wiemy dokładnie ile czasu zajmie integracja. Jeszcze gorzej, jeżeli okaże się, że zmiany jakie integrujemy nie mogą być ujęte w kolejnym wydaniu, np. z powodu błędnej implementacji w stosunku do wymagań. W celu uczynienia procesu przewidywalnym trzeba zmiany dobierać do wydania tak by współdzieliły jak najmniej kodu, a wręcz były od siebie jak najmniej zależne (np. nie używały tego samego kodu, nawet jeżeli go nie modyfikują). Jednocześnie należy zmiany do wydania dobierać też pod względem realizowanego zakresu – tu metod estymacji rozmiaru zakresu pewnie jest kilka. Ja znam tylko jedną jaką widziałem w zastosowaniu – metodę punktów funkcyjnych i szczerze mówiąc, wiele z tym zachodu.
Wariacja B2. Wiele wydań przygotowywanych równolegle
Podobnie jak w przypadku A. Przygotowanie więcej jak jednego wydania równolegle będzie kosztowało dużo wysiłku, gdyż trzeba wyrównywać gałęzie release+1 z release i trunk, release z trunk oraz niestety rozwiązywać pewnie te same problemy w release+1 i oczekiwać na poprawki co w release. Dodatkowo najlepiej by funkcjonalności pomiędzy release+1 i release współdzieliły jak najmniej kodu.
Za: W kuferku zawsze jest stabilny kod, więc wiemy gdzie jest aktualna wersja produkcyjna, czyli łatwo implementować małe zmiany produkcyjne.
Przeciw: Brak wczesnej i przewidywalnej czasowo integracji kodu podczas przygotowania wydania. Utrudnione tworzenie równoległego wydania (powinniśmy czekać z rozpączkowaniem do czasu zintegrowania kodu w gałęzi poprzedniego wydania)
Kiedy stosować: Częste implementacje małych zmian dla produkcji prowadzonych równolegle do długich i ociężałych wydań realizowanych przez luźno współpracujące ze sobą zespoły.
C. Coś pomiędzy
Istnienie takiego podejścia jest kolejnym dowodem na to, że rzeczywistość raczej nie lubi skrajności i białe nie do końca jest białe, a czarne nie do końca jest czarne. Czasem kuferek jest czymś doskonalszym, a czasem czymś wręcz przeciwnie. Zmiany dokładane są ze swoich gałęzi do kuferka w określonym czasie powodując jego destabilizację, gdy już uda się doprowadzić kuferek do porządku, to wtedy tworzymy wydanie i gałąź stable. Czym to się różni od A? Tym, że kuferek dłużej może być niestabilny i tym, że zmiany są raczej większego kalibru niż w przypadku A, jednak zasada jest bardzo zbliżona do A.
C1. Wiele zmian prowadzonych równolegle
Wyobraźmy sobie taki scenariusz gdzie zmiany do kolejnego wydania najpierw sekwencyjnie dogrywają się ze swoich gałęzi developerskich do kuferka, integrują się i testują tak długo, aż staną się stabilne. Wtedy powstaje gałąź stable z kuferka. Natomiast do kuferka mogą być dogrywane kolejne zmiany do następnego wydania.
C2. Wiele wydań przygotowywanych równolegle
Kłopot polega na tym, że teoretycznie nie powinno się zaczynać kolejnego wydania zanim kuferek nie osiągnie stabilności. Łamiąc tą zasadę pączkujemy kuferek w jakimś momencie do gałęzi kolejnego wydania i tam dogrywamy zmiany. Zmiany testowane są w gałęzi kolejnego wydania i na bieżąco są odświeżane zmianami z kuferka. Gdy zmiany z kolejnego wydania są już stabilne, to albo stają się nowym kuferkiem i gałęzią stable, albo dogrywane są do kuferka i dopiero po wytestowaniu stają się gałęzią stable. W tzw. międzyczasie poprzednie wydanie opuściło już kuferek.
Za: Czasami porządek w kuferku, co zmniejsza liczbę błędów do analizy w przygotowaniu wydań równoległych.
Przeciw: Czas zablokowania możliwości budowy gałęzi kolejnego wydania z kuferka, w momencie pracy nad uzyskaniem jego stabilności.
Kiedy stosować: Gdy implementowane zmiany przygotowywane są dłużej jak jedno wydanie – wtedy możemy odświeżyć się gdzieś po drodze w miarę stabilnym kuferkiem. Gdy zależy nam na wcześniejszej integracji w kuferku zanim zaczniemy przygotowanie do wydania oraz gdy nie zależy nam na prowadzeniu wielu wydań równolegle.
Podsumowanie
Powyższy tekst jest próbą przedstawienia trzech głównych podejść do branchowania w nadal popularnych systemach VCS. Niestety nie ma metody idealnej, jednak świadome decydowanie o tym w jakiej sytuacji jaka metoda będzie dla nas najlepsza może prowadzić do dobrych rezultatów. Podsumowując: najświeższy kuferek gdy pracujemy nad małymi, dobrze zdefiniowanymi zmianami w zgranym zespole; stabilny kuferek warto wybrać, gdy potrzebujemy od czasu do czasu szybko dołączyć jakąś zmianę do systemu produkcyjnego i wprowadzamy duże funkcjonalności; coś pomiędzy, może przydać się gdy potrzebujemy wczesnej integracji kodu, oraz gdy prowadzimy prace nad bardzo długotrwałymi zmianami, często dłuższymi niż jedno wydanie.
Myślę, że pomimo dosyć obszernych opisów wiele jest jeszcze do powiedzenia w tym temacie. Chętnie dowiedziałbym się jakie Waszym zdaniem podejście się sprawdza i kiedy. Czy może lepiej przestać sobie zawracać głowę VCS i migrować do DVCS? Zapraszam do komentowania i dzielenia się opiniami!
Dobry artykuł, fajnie wszystko rozpisane i poukładane. Dla mnie po przejściu na DVCS w zasadzie nie ma już odwrotu – ale te systemy nie są panaceum, dalej trzeba umieć rozróżnić między kodem stabilnym a rozwijanym, więc nadal zarządzanie wersjami jest konieczne.
Co do tekstu – mam tylko nadzieję, że ten kuferek to tak dla śmiechu, a autor zdaje sobie sprawę że „trunk” oznacza też „pień”, co w tym przypadku (zwłaszcza w zestawieniu z „branches” – gałęziami) jest lepszym odpowiednikiem? (trzecia opcja to „trąba słonia”, ale to już oczywiście całkiem odpada.)
No nie do końca do śmiechu, kiedyś pracowałem w organizacji, gdzie właśnie „kuferkiem” określano trunk. Każda firma tworzy własną nomenklaturę, często nawet slung, z resztą pień brzmi tak szorstko… W pewnej organizacji na pendrive mówili „syfek”. „Daj syfka” osóby postronne wprowadzało w zakłopotanie.
Dobry artykuł, chociaż dość długi. Bardzo fajne ilustracje. Ja chciałbym by w projektach w których uczestniczę główna gałąź nie była polem minowym tylko zachowała dość wysoką stabilność. Grubsze zmiany powinny być wykonywane na gałęziach, a te mniejsze na głównej gałęzi – trunk/master. Bardzo ważną kwestią są też same zmiany – commity. Według mnie powinny być 'atomowe’, dotyczyć konkretnej zmiany, a nie być zbitkiem zmian niezwiązanych ze sobą.
Ja osobiście korzystam z git svn co pozwala mi pracować na ulubionym gicie, gdy firma dalej korzysta z svn.
Bardzo ciekawy artykuł :), ja także miałem problemy z wyborem podejścia, dodam, że używam SVR do zarządzania projektami analitycznymi („program” w UML). Obecnie „coś co się sprawdziło” u mnie używa „gęsto” tagowania. Proces wygląda tak:
– jedna osoba (w zasadzie ja ;)) opracowuje model wysokiego poziomu,
– kolejna osoba (lub zespół) dostaje do opracowania szczegóły komponentów,
– jeżeli jedna osoba pracuje nad całym projektem, używa wyłącznie tagów
– jeżeli nad projektem pracuję więcej niż jedna osoba: dostają dedykowane gałęzie
– gałęzie są integrowane cyklicznie w celu weryfikacji całości i wyznaczenia dalszych zadań
– każda wersja, która idzie do klienta jest otagowanym punktem integracji
Zapewne jest pewna różnica pomiędzy projektowaniem a kodowaniem, ale moim zdanie nie aż taka wielka… :)
po tym artykule muszę przemyśleć kwestie używania gałęzi przez jedną osobą… np. poboczne pomysły … scenariusze na inna okazje