Czym są systemy kontroli wersji kodu i jakie są najważniejsze błędy?
Systemy kontroli wersji
Po co używać systemu kontroli wersji? Choćby po to, aby mieć dostęp do historii zmian kodu. A po co historia zmian? Aby sprawdzić dlaczego rok temu dodaliśmy pewną linijkę w kodzie. Albo kto usunął plik. Albo przypomnieć sobie procedurę dodawania nowej kontrolki do aplikacji. Albo móc powrócić do poprzedniej wersji kodu w przypadku dużego problemu odkrytego tydzień po oddaniu kodu klientowi.
W skrócie, systemy kontroli wersji pozwalają na tworzenie kopii zapasowych kodu wraz z adnotacją o autorze, dacie oraz krótkim komentarzem o samych zmianach.
Wsród wielu podziałów systemów kontroli wersji, najpopularniejszym jest chyba klasyfikacja według modelu repozytorium. Otóż można wyróżnić, historycznie pierwsze, systemy scentralizowane, gdzie jest jeden serwer przechowujący kod, zaś użytkownicy povierają tylko niezbędny fragment do pracy. Do systemów scentralizowanych należą: Subversion (SVN), TFS i wiele zabytkowych systemów, np. Microsoft Visual SourceSafe (VSS). Niestety, każda próba wykonania operacji na repozytorium, np. dodania nowej wersji, wymaga dostępu do serwera, przez co jest wolne i czasami zawodne.
Drugą kategorią są systemy rozproszone, do których należą git i Mercurial. W tym przypadku każdy użytkownik przechowuje u siebie całą kopię repozytorium, dzięki czemu wszyscy są uniezależnieni od systemów zewnętrznych. Praca z repozytorium jest wówczas dużo szybsza poza trzema problemami:
- Pierwsze zgranie repozytorium może być bardzo czasochłonne, ponieważ trzeba skopiować całą zawartość. Przy dużych projektach mogą to być gigabajty danych i kilka godzin czekania! Pewnym rozwiązaniem jest zgranie kopii repozytorium od kolegi/koleżanki pendrivem albo szybkim połączeniem sieciowym.
- Wiąże się z tym drugi problem - potrzeba dużo więcej miejsca na dysku na przechowanie projektu.
- Ze względu na możliwość pracy offline zdarza się, że programiści trochę się zagalopują i wgrywają na serwer swoje zmiany po wielu dniach pracy. I wtedy mogą się pojawić konflikty w kodzie, ale o tym później.
Wspomniałem o serwerze w systemach rozproszonych. Otóż, mimo że systemy tego nie wymagają, najczęściej jest stosowany serwer, który przechowuje finalną wersję kodu, z której za pomocą systemów ciągłej integracji (CI) budowane są aplikacje.
Błąd 1: brak systemu kontroli wersji
Też tak kiedyś pracowałem 😉. Rzadziej jedna, ostateczna wersja kodu bez żadnych kopii zapasowych, częściej backup w postaci ręcznie ZIP-owanych katalogów z kodem. Czasem nawet ZIP-y okraszałem słowem opisu poza datą wykonania. Powiedzmy, że było to dopuszczalne kilkanaście lat temu. Ale obecnie instalacja i konfiguracja systemu kontroli wersji to kilka minut roboty, zaś sama inicjalizacja i utrzymanie repozytorium w najgorszym przypadku to są minuty.
Polecam od pobrania instalatora np. git i zainstalowania go z domyślnymi opcjami.
Kiedy już chcemy zacząć używać systemu kontroli wersji w wybranym projekcie, możemy w dowolnej chwili uruchomić z linii poleceń komendę git init. Gotowe! Oczywiście, samo repozytorium będzie puste, trzeba będzie jeszcze do niego dodawać pliki. Warto również pomyśleć o kopii kodu na serwerze. Może to być serwer w firmie albo jeden z hostingów (przykładowo GitHub albo GitLab). W hostowanych repozytoriach można mieć darmowo albo za niewielką opłatą repozytoria prywatne, do których dostęp będą miały tylko wskazane przez nas osoby z przedsiębiorstwa.
Co ważniejsze, wersja z serwerem umożliwia pracę nad jednym projektem przez zespół kilku deweloperów, co raczej jest podstawą 😉.
Błąd 2: brak przeglądu kodu
Kiedy już mamy wdrożony system, warto pójść krok dalej i wprowadzić politykę przeglądu kodu przez innego programistę w celu jego akceptacji. Taka statyczna (wzrokowa) analiza kodu pozwoli na wykrycie ewentualnej błędnej logiki, luk bezpieczeństwa, błędnych założeń lub przeoczenie wymagań na dużo wcześniejszym etapie tworzenie oprogramowania.
Ponadto spojrzenie na problem przez inną parę oczu ułatwi świeżą ocenę rozwiązania, które autor tworzył przez kilka dni i zafiksował się na swoim podejściu. Być może istnieje inne, prostsze, bezpieczniejsze lub skuteczniejsze, o które przeoczył. Sam przegląd kodu, po angielsku zwany code review, nie wymaga zazwyczaj dużego zaangażowania czasowego drugiego programisty, a na pewno czas temu poświęcony opłacalny jest dla przedsiębiorstwa.
Jak wiadomo, im wcześniej zostaną wykryte błędy, tym tańszy (nawet kilka rzędów wielkości) będzie koszt ich naprawienia. Bo kiedy niewykryty błąd powróci po kilku tygodniach albo miesiacach z produkcji, po pierwsze stracimy trochę reputacji w oczach klientów, po drugie autor rozwiązania może nie być już dostępny w projekcie, zaś po trzecie nawet autor patrząc na swoje rozwiązanie po dłuższym okresie nie będzie pamiętał co i dlaczego w nim napisał. Ponowne wdrożenie w problem programisty, przejście przez kolejne fazy testów, wreszcie wdrożenie poprawki to czas i koszt, których czasem można uniknąć poprzez najprostszy przegląd kodu.
Błąd 3: niewygodne procedury
Postanowiliśmy robić code review. Jak? Ile dodatkowej pracy wymaga się od drugiego programisty? Czy będzie musiał natychmiast przerywać swoją pracę czy może zrobić przegląd kodu grupowo pod koniec dnia? To zależy od pilności zadań i projektu oraz oczekiwanej jakości projektu i na te pytania trzeba będzie sobie samemu odpowiedzieć. Z pewnością warto zacząć od poświęcenia minimalnej ilości czasu na początku, aby nie zniechęcać do tego pomysłu. Z czasem będzie można go trochę wydłużyć, aby polepszyć zwrot z przeglądu kodu.
Warto jednak mieć na uwadze coś równie istotnego: narzędzia. Jeżeli programiści nie będą mieli łatwego dostępu do komentowania kodu, akceptowania go i odrzucania, będą mniej skłonni do podejmowania się tego wysiłku. Szczęśliwie, wiele repozytoriów online posiada wbudowane mechanizmy do przeglądu kodu, przez co proces ten jest maksymalnie uproszczony.
Błąd 4: za duże commity
Skoro postanowiliśmy robić przeglądu kodu i posiadamy odpowiednie narzędzia, chcemy aby przeglądy były autentyczne a nie wymuszone. Zaangażowanie pracowników możemy w pewien sposób kontrolować i doceniać, jednak ważne jest, aby zapewnić podanie im takich treści do przeglądu, które będą w stanie ogarnąć w czasie krótkiego slotu czasu. Jak to osiągnąć? Oczywiście przez podział!
Z różnych względów warto dzielić duże zadania do wykonania na mniejsze. Mniejsze zadania łatwiej oszacować, można zrównoleglić pracę w zespole, łatwiej też samemu programiście osiągnąć skupienie i rozwiązać mniejszy problem. Przykładowo, jedno duże zadanie “Dodać ekran logowania do aplikacji” można podzielić na podzadania typu: “Przygotować API”, “Utworzyć ekran dla wersji desktop”, “Dodać responsywność”, itp.
Drugą kategorią podziału są commity, które programiści tworzą podczas pracy. Commity, czyli nazwane zestawy zmian, które prowadzą do kompletnego rozwiązania. Są tacy developerzy, którzy potrafią 3 dni pracy i zmiany w dziesiątkach plików upakować w jednym commicie. Z pewnością wystarczy to do tego, aby mieć punkt w historii kodu, który w razie niepowodzenia będzie można wycofać.
Jednakże, jeżeli przekażemy drugiemu programiście taką paczkę zmian do weryfikacji, przejrzenia i skomentowania… Osoba taka najprawdopodobniej przescrolluje pobieżnie i odpisze, że wszystko jest ok. Kto byłby w stanie skupić się na tak rozległych zmianach i zrozumieć je bez poświęcania na to połowy swojego dnia pracy?
Rozwiązanie tego problemu jest trywialne: należy dzielić swoją pracę na mniejsze, możliwe do ogarnięcia commity. Co to oznacza w praktyce? Zamiast jednego commitu pt. “Nowy ekran” może być potrzeba stworzenia kilku: “Stworzenie komponentu formularza”, “Zamiana istniejących formularzy na komponent”, “Stworzenie nowego ekranu”, “Zmiana konfiguracji”, “Dodanie biblioteki Bootstrap”, “Ostylowanie ekranu”, itp.
Nawet jeżeli ostatecznie do przeglądu kodu przekażemy taki sam rozmiar zmian, w wersji podzielonej będzie on znacznie łatwiejszy do przetrawienia, ponieważ poszczególne zmiany będą opisane, łatwiej więc zrozumieć intencję autora i wyłapać problemy. Ponadto, poszczególne zmiany będą miały znacznie mniejszy rozmiar, będą więc przyjemniejsze do analizy. Można też przegląd sobie podzielić na kilka sesji, przerywając po kilku commitach.
Jedna gałąź kodu
Gałąź kodu (ang. branch) jest nazwaną historią zmian w projekcie. Gałęzi możemy tworzyć wiele, co ważne w systemie git, w przeciwieństwie do np. SVN, samo tworzenie gałęzi nie klonuje wszystkich plików, przez co jest operacją bardzo szybką i oszczędną.
Poza główną gałezią master, tworzoną na starcie, warto aby każdy programista realizując swoje zadanie tworzył własną tymczasową gałąź, nazwaną tak jak np. identyfikator zadania. Po co? Po to, aby programiści nie wchodzili sobie w drogę dokonując zmian w tym samym czasie. Po to, aby można było codziennie wysłać swojeg zmiany na serwer jako kopię zapasową przy przedłużających się zadaniach. Po to wreszcie, aby można było wysłać swoją paczkę zmian do przeglądu kodu i całościowego dodania do głównej gałęzi w przypadku akceptacji. Unikamy wtedy sytuacji, kiedy część rozwiązania znajdzie się w głównym branchu i uniemożliwia innym pracę albo pozostawia “śmieci” do końca projektu, bo ktoś zapomniał wykasować wszystkie swoje zmiany w przypadku, gdy zadanie zostało wycofane.
Chwila moment, wykasować zmiany?!
Nadpisywanie zmian
Pomyliłem się? Popełniłem wstydliwy błąd w kodzie? Nadpiszę historię commitów i nikt nie zauważy. Zresztą po co tworzyć kolejny commit tylko z taką poprawką?
Zachęcam do tego. Im mniej śmieci tym łatwiej się czyta kod. Ale tylko w swoim branchu. Jeżeli nadpiszemy wspólny branch, wszyscy pozostali w zespole mogą mieć problem podczas wysyłania swoich zmian.
Podsumowując: nadpisywanie współdzielonej gałęzi jest zakazane, nadpisywanie prywatnej gałęzi rekomendowane.
Kilka sposobów nadpisywania zmian można przeczytać w moim blogu: git add interactive, patching and branching.
Konflikty w kodzie
Kiedy osoby pracują nad jednym projektem, należy starać się tak przydzielać zadania, aby żadne dwie w tym samym czasie nie pracowały nad tym samym fragmentem kodu. Nie zawsze jest to uniknione i praca taka oczywiście jest możliwa, ale w miarę możliwości należy unikać.
Ponieważ będzie wówczas dochodziło do konfliktów zmian. I jeden, ten odrobinę późniejszy programista, będzie zmuszony dokonać złączenia swoich zmian ze zmianami, które właśnie wprowadził ten szybszy. Systemy kontroli wersji ułatwiają rozwiązywanie konfliktów, ale nierzadko konflikty są tak zawiłe, że trudno jest się połapać co kto zmienił i jaka powinna być ostateczna wersja. W swojej historii wielokrotnie miałem takie sytuacje, aż w końcu postanowiłem stworzyć skuteczne narzędzie, które ułatwi mi ogarnięcie tego chaosu.
Szczegóły opublikuję wkrótce, póki co zapraszam do kontaktu.