6 min read
Wersjonowanie? Changelog? jak to okiełznać

UWAGA Post archiwalny, przeniesiony ze starego bloga. Linki mogą nie działać, dane mogą być przeterminowane, autor może się wstydzić tego co napisał.

Jednym z elementów oprogramowania, na który rzadko zwracamy uwagę, jest kwestia wersjonowania i prowadzenia tzw. changelogu. Warto zauważyć, iż jest to bardzo istotna część, i nawet będąc wyłącznie użytkownikiem oprogramowania na pewno nie raz wersjonowanie dało nam w kość, a przykładów jest mnóstwo - ostatni dobry Winamp to ten z wersją 2.X.X, uTorrent wyłącznie w wersji 2.2.1 i ani wersji nowszej, kod który wspiera wyłącznie Pythona 3.6+. Zastanawialiście się czasem, jaki wpływ te cyfry mają na oprogramowanie? Dlaczego Grand Theft Auto San Andreas przestało wspierać modyfikacje akurat od wersji 2.0.0 oraz przede wszystkim jak to okiełznać w swoim projekcie?

Wersjonowanie

Z definicji jest to przypisywanie unikalnego ciągu cyfr ( Python 2.7, youtube_dl 2018.12.31) lub/oraz unikalnej nazwy wersji ( Android Oreo, macOS Mojave) do konkretnego stanu oprogramowania. Pozwala to w prosty sposób śledzić postępy zmian w oprogramowaniu, a także zabezpiecza nas przed niepowołanym działaniem pisanych przez nas aplikacji chociażby poprzez kontrolę zależności.

Schemat

Najistotniejsze elementy każdej konwencji wersjonowania to:

  • Major: Zmiany które nie są kompatybilne wstecznie, nie wspierają dotychczasowego API
  • Minor: Zmiany kompatybilne wstecz, często dodające nowe rozszerzenia
  • Micro/Patch: Zmiany dotyczace naprawy bugów
  • Modifier: Opcjonalny tag wykorzystywany w produkcji (np. dev, alpha, beta, rc1…)

Każda z nich inkrementowana jest w górę, w zależności od naniesionych zmian.

Konwencje

Wyróżniamy kilka konwencji wersjonowania, z których najpopularniejsza to Semantic Versioning1 w której wersjonowanie występuje w formacie MAJOR.MINOR.PATCH ( nazywanej też BREAKING.FIX.MINOR ). Drugą równie popularną konwencją wersjonowania jest Calendar Versioning2 gdzie wersjonowanie wykorzystuje informacje o dacie. Taka konwencja wykorzystywana jest przy wersjonowaniu chociażby Ubuntu czy youtubedl. Konwencje mniej popularne to 0-based Versioning3 w której zasada jest bardzo prosta, _MAJOR nigdy nie powinien być większy od 0 (projekt jest zawsze w fazie developmentu, czyli nie możemy w tym przypadku mówić o stabilności) czy Sentimental Versioning4 który wymaga aby postrzegać nieco inaczej kwestie wersjonowania (dla przykładu wersjonowanie TeX dążące do liczby π).

Skupimy się na Semantic Versioning ( w skrócie SemVer ) ze względu na popularność tej konwencji, co wcale nie oznacza, że jest ona najlepsza. Wprowadzając SemVer do naszej aplikacji musimy zapamiętać, że wersjonowanie zaczynamy od wersji 0.1.0, ponieważ nie zaczynamy projektu naprawą buga a wprowadzeniem nowej funkcjonalności. Drugą ważną cechą jest to, że do wersji 1.0.0 jest faza developmentu co oznacza, że nie powinieneś bać się dużych zmian w kodzie, czy API. W momencie gdy uznasz, że Twój projekt jest stabilny na tyle by trafić na produkcję czy do klienta, należy wtedy wypuścić wersję 1.0.0. I najistotniejsze, w zależności od typu wprowadzonych zmian inkrementuje odpowiednio Major, Minor bądź Patch o jeden.

Changelog

Czym jest changelog5 ? To rejestr/dziennik zmian, wykorzystujący plik ‘CHANGES’ ( w zależności od nazewnictwa może to być CHANGES, CHANGELOG czy HISTORY) który zawiera uporządkowaną chronologicznie listę zmian dla każdej kolejnej wersji projektu. Prowadzenie dziennika zmian pomaga użytkownikom oraz deweloperom poznawać zmiany wprowadzone w kolejnych wersjach projektu.

Konwencje

Tak jak konwencji wersjonowania tak i tutaj mamy wiele konwencji, często bardzo różnych od siebie, indywidualnych dla projektu. Jedną z ciekawszych, którym warto się przyjrzeć, jest Keep a Changelog6 zakładający dodawanie wpisów w odpowiednio otagowanych sekcjach dla każdej wersji. Sekcje które są wykorzystywane to:

  • Added dla nowych funkcjonalności
  • Changed dla zmian w istniejących funkcjonalnościach
  • Deprecated dla funkcjonalności wkrótce do usunięcia
  • Removed dla usuniętych funkcjonalności
  • Fixed dla poprawek błędów
  • Security w przypadku luk bezpieczeństwa

W pliku należy utrzymać sekcje Unreleased na szczycie, aby śledzić nachodzące zmiany, co może pomóc następnie w ustaleniu kolejnej wersji projektu (czy też nawet zautomatyzowaniu tego procesu!).

Wpisy

Pamiętajmy, iż dzienniki zmian są dla ludzi, nie dla maszyn, dlatego też powinniśmy przyjąć odpowiednią formę pisania o tym, co zostało wprowadzone. Wpisy powinny dokładnie i w prosty sposób opisywać zmiany, oraz dodawać kontekstu zmianie (tak jakbyśmy mówili drugiej osobie o zmianach). Unikamy zaczyniania wpisów od słów ‘Added’ czy ‘Fixed’, skupmy się na tym co zostało zrobione, i nie przesadzajmy ze szczegółami!

Przy wpisach warto wykorzystywać odpowiednie patterny dla wpisów7 jak:

  • ‘You can now …’ - nowe funkcjonalności bądź naprawione błędy
  • ‘X now/no longer does Y when Z’ porównanie różnic między wersją Y a X z dodaniem kontekstu Z
  • ‘X now/no longer does Y. This means you no longer /now need to do Z.’ ukazanie różnic między Y a X które powodują zmianę użytkowania Z

Pomoże to łatwiej zrozumieć przekaz naszywch wpisów oraz wprowadzi ład do dziennika zmian!

Wprowadzenie zmian w aktualnym projekcie

Jeżeli Twój projekt jest już w fazie developmentu a do tej pory nie prowadziłeś wersjonowania czy dzienniku zmian, bądź robiłeś to bez konkretnej konwencji oto co możesz zrobić:

  • Ustal wersję 0.1.0 dla projektu w aktualnej fazie.
  • Jeżeli projekt miał już kilka istotnych zmian które chciałbyś odnotować nic nie stoi na przeszkodzie aby stworzyć tagi dla poprzednich commitów i stworzyć dla nich wpisy w changelogu.
  • Gdy projekt posiada już dziennik zmian który jest dość archaiczny, nie ma problemu aby poprawić go, przejrzeć historię commitów i dodać wpisy.

I pamiętaj, nic nie jest idealne na początku, a nową wiedzę warto zawsze wprowadzić w życie aby ją utrwalić.

Tworzenie historycznych tagów

Jeżeli zdajemy sobie sprawę z tego, że nasz projekt posiadał jakieś konkretne etapy które chcielibyśmy odnotować w bardzo prosty sposób możemy stworzyć tagi dla konkretnych commitów w historii poprzez:

git checkout master
git tag -a vX.Y.Z -m 'PROJECT vX.Y.Z' COMMIT_HASH
git push origin vX.Y.Z

Więcej na temat wersjonowania przeczytacie tutaj, a dziennika zmian tutaj.

Footnotes

  1. https://semver.org/

  2. https://calver.org/

  3. https://0ver.org/

  4. http://sentimentalversioning.org/6

  5. https://en.wikipedia.org/wiki/Changelog

  6. https://keepachangelog.com/en/1.0.0/

  7. https://www.youtube.com/watch?v=L3yAD319DiU