Archiwa kategorii: Średnio zaawansowane

Wpisy dla programistów (także początkujących)

Git: przedzieram się przez gałęzie

System kontroli wersji Git był tematem dwóch niedawnych wpisów na moim blogu: No i Git! Kontrola wersji służy nie tylko programistom oraz Git w Eclipse. W tym odcinku chcę na jakiś czas zamknąć temat. Skoncentruję się na kwestii odgałęzień w kodzie (branches).

Git pozwala na przyjemną i efektywną pracę z branchami. Łatwo je tworzyć, scalać i usuwać. Wczoraj zabrałam się za tworzenie szkieletu mojej konkursowej aplikacji Szafbook i wykorzystałam to jako pretekst do poeksperymentowania z odgałęzieniami.

Ogólny zarys moich działań
  1. Utworzenie nowego brancha w konsoli.
  2. W ramach brancha: przygotowanie szkieletu aplikacji Spring Boot za pomocą serwisu https://start.spring.io/.
  3. Wcielenie zmian z nowego brancha do głównej linii kodu (konsola).
  4. Utworzenie brancha przez interfejs Eclipse.
  5. W ramach brancha: prace mające doprowadzić kod do stanu, w którym się kompiluje.
  6. Wcielenie zmian z nowego brancha do głównej linii kodu (Eclipse).
  7. Ustalenie, dlaczego nie było mi potrzebne słowo rebase.
Konsola

W oparciu o informacje w odpowiednim rozdziale podręcznika, tworzę nową gałąź (o nazwie bootinit):

Powyższe polecenie jest skrótem dla dwóch innych, z których pierwsze tworzy brancha, a drugie mnie do niego przenosi:

Następnie wprowadzam swoje zmiany. W tym wypadku to wrzucenie szkieletu aplikacji webowej do katalogu projektowego oraz usunięcie niepotrzebnych mi już plików.

Po wprowadzeniu zmian polecenie git status zwraca następującą informację):

5
Wynik wywołania polecenie git status w nowej gałęzi po wprowadzeniu zmian

Chcę śledzić wszystkie te zmiany i chcę je uwzględnić w kolejnym commicie, dlatego dodaję wszystko:

Alternatywnie mogłabym zaznaczyć poszczególne zmienione i dodane pliki poleceniem git add <plik>, a usunięte pliki poleceniem git rm <plik>.

Na tym etapie postanawiam wysłać swoje zmiany do repozytorium:

Załóżmy, że moje prace w gałęzi bootinit osiągnęły taki poziom dojrzałości, że chcę scalić je z główną gałęzią kodu (master).

W tym celu muszę przejść do gałęzi, do której mam wrzucić zmiany i tam wywołać polecenie merge.

8
Merge. Kolory bardzo przyjemne

Jeśli gałąź nie będzie mi już potrzebna, mogę ją usunąć:

I już.

Eclipse

Teraz przerzucam się na pracę w środowisku Eclipse. Na początku importuję istniejący projekt z repozytorium Git.

W Eclipse narzędzia związane z kontrolą wersji znajdują się w menu kontekstowym projektu, w zakładce Team.

Kontrola wersji w Eclipse
Kontrola wersji w Eclipse

Tworzę nową gałąź korzystając z opcji wyświetlonej na powyższym obrazku: Team->Switch To->New Branch

Nazwa aktualnej gałęzi jest wyświetlana przy nazwie projektu.

Nazwa aktualnej gałęzi jest wyświetlana przy nazwie projektu.
Wersja projektu w gałęzi eclipsebranchtest

Dalej wprowadzam swoje zmiany. Przed wszystkim chcę doprowadzić aplikację do stanu, w którym kompiluje się bez błędów (za pomocą Mavena) i zawiera chociaż jeden (bezsensowny na razie) test jednostkowy.

Jedna ze zmian to oznaczenie części plików w katalogu projektu jako ignorowanych – mam na myśli pliki stanowiące wynik kompilacji. W systemie kontroli wersji chcę trzymać tylko źródła.  W menu mamy odpowiednią opcję Ignore, która jest powiązana ze zrozumiałym dla Gita plikiem .gitignore.

Praca w ramach jednej gałęzi została już umówiona w poprzednim wpisie. Załóżmy teraz, że wszystkie zmiany zostały wysłane do repozytorium, a prace w ramach nowej gałęzi eclipsebranchtest zostały zakończone.

Podobnie jak w przypadku pracy w konsoli, najpierw muszę przejść do gałęzi, do której chcę wcielić zmiany (w tym wypadku to master). Tam z menu Team wybieram opcję Merge. Pojawi się okienko przedstawione na poniższym obrazku.

Wybór gałęzi do scalenia
Wybór gałęzi do scalenia. Wybieram eclipsebranchtest

No i to by było na tyle! Mogę jeszcze usunąć niepotrzebną już gałąź w Team->Advanced->Delete Branch.

Rebase

Spodziewałam się, że podczas zabawy z branchami dotrę do momentu, w którym potrzebne stanie się słówko rebase, ale tak się nie stało. Nie pozostało mi nic innego jak RTFM. Oto, co ustaliłam (w wielkim skrócie):

Scalać można albo przy użyciu merge, albo rebase.  W zależności od dokonanego wyboru, inaczej będzie wyglądała historia zmian w projekcie. Po merge historia będzie dokładnie odwzorowywała to, co się działo – wszystkie rozgałęzienia i powroty. Dla odmiany rebase pozwala nieco przepisać historię, w taki sposób, że – mimo pracy w odgałęzieniach – wygląda ona na liniową. Nie jest to prawda historyczna, ale ta forma może okazać się znacznie bardziej czytelna.

[edit: popełniłam jeszcze jeden wpis na temat rebase]

PS.

Wrócę z tematem Gita, jak dorobię się pierwszego poważnego konfliktu w kodzie.

Git w Eclipse

W poprzednim wpisie nauczyłam się – a może przy okazji chociaż jednego Czytelnika – jak kontrolować wersje programu przy użyciu systemu Git i serwisu GitHub. Dzisiaj postanowiłam sprawdzić, jak powiązać repozytorium Git ze środowiskiem programistycznym Eclipse, w którym zamierzam rozwijać swój projekt. Szczególnie ciekawiło mnie, w jaki sposób Eclipse rozwiązuje kwestię dwóch poziomów repozytorium: lokalnego na dysku i zdalnego w chmurze (GitHub).

Gdyby na tym etapie ktoś chciał zwrócić mi uwagę, że rozwijam swój projekt od d**y strony, to uprzejmie informuję, że jestem tu imperatorem. Moim celem jest przede wszystkim nauczenie się czegoś w przyjemny sposób. Jeśli kiedyś w końcu wyjdzie z tego produkt – tym lepiej.

Poniżej przedstawiam kroki potrzebne do powiązania mojego kodu w GitHub z projektem w środowisku Eclipse.

  1. Instalacja Eclipse, oczywiście. W moim przypadku jest to wersja Mars dla programistów Java Enterprise Edition. Nie twierdzę, że to bezsprzecznie najlepsze IDE, ale zamierzam używać go w konkursowym projekcie. Tutaj można znaleźć opisy najpopularniejszych darmowych edytorów dla Javy (po angielsku).
  2. Instalacja w Eclipse narzędzia EGit.

    Help->Install New Software
    Help->Install New Software->http://download.eclipse.org/egit/updates/
  3. Stworzenie nowego projektu w oparciu o istniejący kod.

    import
    File->Import->Projects from Git
  4. Wybranie jednej z dwóch opcji: nowe lokalne repozytorium z kodem z GitHub (clone) lub istniejące lokalne repozytorium powiązane wcześniej z GitHub (np. to, które stworzyłam w konsoli na potrzeby poprzedniego wpisu).

    clone
    Klonowanie!
  5. Podanie adresu projektu w GitHub

    clone2
    W przypadku publicznego repozytorium w GitHub wystarczy podać link
  6. Wybór gałęzi – u mnie na razie jest tylko jedna, główna.

    clone3
    Co by tu wybrać?
  7. Podziwianie zaimportowanego projektu.

    project
    Tak wygląda projekt zsynchronizowany z repozytorium Git
  8. Zmiany w kodzie. Zmodyfikowałam treść jednego pliku i dodałam jeden nowy. EGit natychmiast bardzo wyraźnie sygnalizuje te zmiany.

    project2
    Jeden plik zmieniony, jeden plik utworzony, ale jeszcze nie wersjonowany
  9. Wyświetlenie menu kontroli wersji (opcja Team w menu kontekstowym projektu). Ponieważ chcę dodać do kolejnego commita wszystkie wprowadzone zmiany, wybieram opcję Add to Index (w konsoli to git add).

    team1
    Menu kontroli wersji w projekcie
  10. Zmieniły się ikonki! Zmiany zostały oznaczone jako zaplanowane do wysyłki (staged), ale ciągle nie nastąpił commit.

    team2
    Przed commitem
  11. Wybranie z menu opcji Commit. Do wyboru mamy – i to jest moment, na który czekałam – wysłanie zmian tylko do lokalnego repozytorium, bądź wysłanie ich i lokalnie, i dalej, do GitHub.

    team3
    Wysyłamy zmiany
  12. Mniej więcej na tym etapie nastąpi wreszcie prośba o logowanie.

    credentials
    To repozytorium jest otwarte dla wszystkich do odczytu, ale tylko wybrańcom wolno wysyłać do niego kod
  13. Zdecydowałam się na wysyłkę z jednoczesnym wypchnięciem kodu do GitHub (może niezbyt rozsądnie, ale wszystko to w ramach eksperymentu). Sprawdzę teraz, czy moje zmiany są widoczne w serwisie.

    github
    Co słychać w GitHub? Wprawne oko zauważy w commicie plik, o którym nie wspominałam wcześniej. Zagadka: dlaczego tam trafił?
  14. Historię zmian mogę podejrzeć także od strony IDE.

    view
    Praca wre

Jak na razie, wszystko przebiegło bardzo intuicyjnie. W najbliższym czasie postaram się utworzyć rozłączne gałęzie kodu z konfliktującymi zmianami (tu wstaw szatański śmiech).

Więcej informacji na temat korzystania z EGit znajdziesz w instrukcji użytkownika.

PS. Mam bardzo silne skojarzenie z wyrazem eclipse (zaćmienie). W wieku wczesnonastoletnim, podczas wakacji nad polskim morzem, namówiłam tatę i siostrę na pójście do kina. Grali akurat film z młodym Leo DiCaprio, w którym kochała się połowa podstawówki. Film nazywał się Całkowite zaćmienie i traktował, jak się okazało, o zakazanej homoseksualnej miłości.  Obejrzenie tego w obecności rodzica zdecydowanie plasuje się w top 5 najbardziej żenujących sytuacji w moim życiu.

No i Git! Kontrola wersji służy nie tylko programistom

To pierwszy z dwóch wpisów, który chcę poświęcić tematowi kontroli wersji za pomocą Gita. Pierwszy będzie wprowadzający i nie pojawi się w nim słowo rebase. W drugim się pojawi, ponieważ jestem nim zaintrygowana. Obu towarzyszyć będą commity do mojego repozytorium projektowego w ramach konkursu Daj się poznać.

Wstęp (dla informatyków)

Zacznę od wyjawienia wstydliwej prawdy: nie znam się Gicie. Tak  wyszło, że w pracy najpierw używaliśmy CVS, potem SVN, potem Perforce (P4). To już i tak niezły mętlik – prosta komenda checkout w SVN i P4 znaczy coś zupełnie odmiennego : odpowiednio pobranie projektu na lokalny dysk albo otwarcie pliku do edycji wraz z zablokowaniem wersji na serwerze. Kilka razy w życiu musiałam dotknąć Gita bądź GitHuba: głównie w zadaniach domowych na Courserze bądź w ramach współpracy z zespołami, które z Gita korzystały.

Życie zmusiło mnie nawet do napisania (wraz z kompanami, co chyba jeszcze pogarsza sprawę) kodu, który automatycznie synchronizował dane w Gicie z danymi w Perforce. Istnieje, co prawda, odpowiedni plugin do P4, ale mieliśmy zbyt starą wersję serwera…

 

Wstęp (dla tych, co nie lubią „tajemniczych szyfrów”*)

System kontroli wersji to narzędzie, które pozwala śledzić zmiany wprowadzane w dokumencie. Dzięki temu zawsze możesz sprawdzić, Wiesz kto i kiedy wprowadził zmianę (czyli dodał, usunął lub zmienił linijkę tekstu). Jeśli zmiany są wprowadzane przez kilka osób, możesz łatwo (a przynajmniej łatwiej niż w innych okolicznościach) je połączyć. Możesz przywrócić dowolną wersję dokumentu – nawet taką, która w rzeczywistości nigdy nie istniała (np. zawierającą zmiany wprowadzone przez dwie osoby, a bez zmian trzeciej).

Systemy kontroli wersji służą przede wszystkim programistom, ale mogą przydać się właściwie każdej osobie pracującej z tekstem, jak dziennikarz czy tłumacz. Można zresztą pilnować w ten sposób nawet listy zakupów… Żeby jednak w pełni skorzystać z funkcjonalności takiego systemu (np. porównywanie dwóch różnych wersji) dane powinny być w postaci czysto tekstowej – takiej, jaką da się otworzyć za pomocą zwykłego edytora tekstu (jak windowsowy Notatnik).

Obecnie najpopularniejszym systemem kontroli wersji jest Git, któremu poświęcony jest ten wpis. Znalazłam nawet artykuł (po angielsku) o tym, jak wersjonować w Gicie pliki MS Word. Do domowych zastosowań wystarczy prostszy koncepcyjnie Subversion.

* Dostałam dzisiaj taką miłą recenzję: Twojego bloga czyta się jednym tchem (gdy pomijam oczywiście wpisy poświęcone jakimś tajemniczym szyfrom ;)).

 

Anegdotka

Żeby nie musieć od razu zabierać się do pracy, opowiem jeszcze anegdotkę.

Zostałam kiedyś zaproszona do wygłoszenia wykładu na temat ciągłej integracji. Połowę czasu spędziłam na live coding (yeah). Pokazałam, jak stworzyć projekt i buildy na serwerze Bamboo (w chmurze firmy Atlassian), oraz w jaki sposób można uruchomić (wyzwolić) plany. Ponieważ na moim prezentacyjnym netbooku z Windows 8 (teraz mieszka tam Linux Mint, więc powstrzymajcie kamienie) nie miałam zainstalowanego żadnego systemu kontroli wersji, wyedytowałam plik w GitHubie (kod też miał być w chmurze) przez przeglądarkę.

Na koniec organizator tego spotkania (który zresztą również bierze udział w konkursie) zwierzył mi się, że o ile treść wykładu zawierała informacje już mu znane, to nie miał pojęcia, że można zmieniać kod w GitHubie z poziomu przeglądarki i to właśnie z tego wykładu wyniesie.

Treść wykładu można znaleźć na blogu: Ciągła Integracja: anioł stróż dobrego programisty i Ciągła Integracja: jak skonfigurować proces budowania na serwerze Bamboo?

 

Git: cechy charakterystyczne

  • System rozproszony: programiści utrzymuję własne lokalne repozytoria i tylko czasami wysyłają kod do repozytorium głównego.
  • Łatwość tworzenia i scalania gałęzi (branches) do pracy nad różnymi wersjami czy funkcjonalnościami.
  • Staging Area: można wygodnie wskazać, które spośród wprowadzonych zmian mają się znaleźć w commicie, nawet jeśli dotyczą tego samego pliku.
  • Darmowe narzędzie z otwartymi źródłami.

githubheart

Do rzeczy: jak wersjonować projekt w Gicie i przechowywać w ogólnodostępnym repozytorium GitHub?

W 12 krokach:

1. Zainstalować Git w wersji dla odpowiedniego systemu ze strony https://git-scm.com/downloads.
2. Utworzyć konto na stronie GitHub: https://github.com/
3. Utworzyć repozytorium dla projektu

W tym celu należy wybrać na stronie opcję New Repository i wybrać pożądane opcje (repozytorium publiczne lub prywatne, puste lub z plikiem Readme.md; można dodać informację o licencji oraz ignorowanych typach plików). W ten sposób powstało repozytorium mojego projektu.

(GitHub oferuje dodatkowe możliwości, jak strony Wiki czy możliwość zgłaszania błędów – ale o tym nie będziemy tu rozmawiać.)

4. Pobrać kod, tworząc przy okazji lokalne repozytorium

W wybranej przeze mnie lokalizacji wywołuję komendę:

Zawartość katalogu szafbook:

Jeśli przyjrzymy się dokładniej, znajdziemy jeszcze ukryty katalog z informacjami dla systemu kontroli wersji:

Nie będziemy tam zaglądać.

5. Status

Na wszelki wypadek sprawdźmy teraz status plików projektowych i upewnijmy się, że działamy na aktualnej wersji gałęzi MASTER (główna i domyślna gałąź repozytorium):

6. Pora coś popsuć! Zmiana w kodzie

Wprowadziłam zmianę w pliku README.md. Po jej zapisaniu:

7. Dodanie pliku

Utworzę jeszcze nowy plik w tym samym katalogu. Nazwę go test.txt. Po zapisaniu:

Git pokazuje mi, co zmieniłam, a także informuje mnie, że jeśli chcę odwzorować te zmiany w repozytorium, muszę użyć komendy add.

8. Staging: pomiędzy zmianą a commitem

Ponieważ chcę zapisać zmiany w repozytorium, użyję komendy add:

9. Wysłanie zmian do repozytorium

Uwaga 1: Na razie do mojego repozytorium na dysku, a nie do centralnego repozytorium GitHub!)
Uwaga 2: Na tym etapie git może poprosić o podanie informacji o użytkowniku.

W domyślnym edytorze tekstu otwarty zostanie plik z informacjami na temat aktualnego zestawu zmian, gdzie należy wprowadzić objaśniający komentarz.

Alternatywnie mogę podać ten komentarz od razu przy wywołaniu polecenia commit:

10. Historia zmian

Historię zmian możemy śledzić za pomocą komendy log:

11. Wysłanie zmian do centralnego repozytorium GitHub

  • origin to repozytorium, z którego oryginalnie pobrałam kod (czyli mój projekt w GitHub, zapamiętany przy wywołaniu polecenia clone)
  • master to nazwa gałęzi w repozytorium

Uwaga: gdyby w międzyczasie ktoś zmienił kod w głównym repozytorium, musiałabym najpierw zaakceptować jego zmiany – system nie pozwoliłby mi wysłać moich. W tym wypadku jestem jednak jedyną osobą z prawem zapisu, więc mam pewność, że nie natknę się na tego typu kłopoty.

12. Podejrzenie zmian

Na tym etapie moje zmiany są już widoczne w portalu GitHub! Hurra!

 

Więcej?

Więcej informacji: np. jak cofnąć wprowadzone zmiany, odkopać starszą wersję, poradzić sobie ze zmianami wprowadzonymi przez różnych użytkowników, znajdziesz w podręczniku na oficjalnej stronie Gita. Jesteśmy teraz w rozdziale Git Basics.

Dam się poznać: mój projekt open source

Zgodnie z zapowiedzią w poprzednim wpisie (o prokrastynacji), postanowiłam wziąć udział w konkursie Daj się poznać organizowanym przez Macieja Aniserowicza z bloga devstyle.

Oznacza to, że w ciągu najbliższych dwóch miesięcy (lub do odwołania) dwa razy w tygodniu będę publikowała tu wpisy na temat mojego projekciku open source. Zrobi się trochę bardziej technicznie. Mam nadzieję, że nie pociągnie to za sobą exodusu Czytelników zaglądających tu w poszukiwaniu lżejszych tekstów okołoprogramistycznych. Postaram się zachować przyjazny ton.

Na co mi to?

Jedno słowo: motywacja.

Cztery słowa: najlepiej pracuję pod presją.

Wielu przyjaciół naiwnie wierzy w moje nieprzebrane pokłady ambicji i samozaparcia. W rzeczywistości, jak uczeń z oślej ławki, robię coś tylko wtedy, kiedy wiem, że ktoś na pewno mnie sprawdzi. W związku z tym pracę nad każdym większym przedsięwzięciem (nie przepadam za słowem „projekt”, chociaż akurat tu akurat pasuje) rozpoczynam od poszukiwań sprawdzacza.

Bądź moim sprawdzaczem.

Jeśli ktoś pokusi się o merytoryczne komentarze i podpowiedzi: tym lepiej dla mnie.

A na co mi sam blog? O tym chyba tutaj: O mnie.

Mój pomysł: szafbook

szafa
To jest moja garderoba. Praca nad dzisiejszym wpisem wymagała ode mnie między innymi uprzątnięcia sterty prania zalegającego na oknie i na desce do prasowania.

Opis problemu

  1. Mam dużo ubrań, a ciągle chodzę w tych samych, bo nie pamiętam, co jest w dolnych pokładach mojej szafy.
  2. Mam trochę ubrań na wymianę – z których wyrosłam 😉 ja lub moje dziecko.

Rozwiązanie i zarys funkcjonalności

Wymyśliłam sobie aplikację, która umożliwi mi skatalogowanie zawartości szafy oraz jej  przeszukiwanie, a także udostępnienie jej zawartości znajomym i ewentualną wymianę bądź wypożyczenia.

Szkic wymagań:

  1. Użytkownik zarządza jedną lub więcej szafami.
  2. Dodaje do szafy ubrania wraz ze zdjęciami i krótkim opisem. Ubrania są umieszczane w odpowiedniej kategorii (ręcznie lub poprzez analizę tekstu, a w bajkowej wersji full-wypas także przez analizę obrazu). Metadane muszą, oczywiście, zawierać rozmiar.
  3. Użytkownik może udostępnić szafę do przeglądania komuś ze swoich znajomych.
  4. Możliwe są wymiany i wypożyczenia.
  5. Dostępne jest semantyczne wyszukiwanie, np. „wszystkie czarne topy” albo „szalik pasujący do tego swetra”.

Technologie

Przyznam, że kusiła mnie szaleńcza idea stworzenia aplikacji webowej w Haskellu. Ostatecznie doszłam jednak do wniosku, że wolę uzupełnić swoją coraz bardziej dziurawą wiedzę na temat Javy. To mój ulubiony język programowania, a nie dotykałam go właściwie od dwóch lat (ponieważ wybór zajęć determinowała dziedzina, a nie preferencje programisty). W tym czasie tworzyłam za to kod w: Perlu, Pythonie, C++, Rubym, R.

Dlatego mimo że JEE to temat nudny i oklepany, tym właśnie chcę się zająć. Wśród rzeczy, których chcę użyć, nauczyć się, przypomnieć sobie lub o nich napisać, są:

  1. Java 8,
  2. jakiś framework webowy, pewnie Spring Boot,
  3. microservices (?),
  4. Maven,
  5. jakieś narzędzie do analizy statycznej,
  6. JUnit,
  7. Git/GitHub,
  8. jakieś darmowe narzędzie do ciągłej integracji, pewnie Jenkins,
  9. jakieś narzędzie do przetwarzania danych RDF/OWL,
  10. któraś nierelacyjna baza danych,
  11. Android w dalekiej, dalekiej przyszłości 🙂

Mój pomysł podoba mi się, bo:

  1. Mogę rozwiązać istniejący problem (być może tylko mój).
  2. Bardzo rzadko mam okazję pisać coś od zera.
  3. Zorientuję się, co się dzieje w Javie.
  4. To przedsięwzięcie da się przekuć w serię przyjaznych wpisów na temat różnych technologii.
  5. Wszystko to trochę wygłup. Zawsze w cenie.

Mój pomysł nie podoba mi się, bo:

  1. Temat jest „dziewczyński”.
  2. Technologie, których chcę użyć, są pospolite.
  3. Od strony technicznej to nic odkrywczego.
  4. Wątpię, żebym w czasie trwania konkursu była w stanie wyprodukować aplikację wartą pokazania.

Informacje techniczne

  1. Wpisy konkursowe będę oznaczać tagiem daj się poznać.
  2. GitHub: https://github.com/1nk4/szafbook
  3. www: http://szafbook.pl/

PS.

Zespół cieśni nadgarstka to najwyraźniej przypadłość nie tylko czynnych programistów i pracowników linii produkcyjnych, ale także młodych matek noszących coraz cięższego stwora.

erlich
Pora zabrać się do roboty! Nie mam profesjonalnych ortez, jak Erlich. Zamiast nich używam ochraniaczy do jazdy na rolkach. Naprawdę pomagają.

10 migawek z kursu programowania funkcyjnego w Haskellu

Od dwóch miesięcy dość rzadko udaje mi się mieć wolne obie ręce. Przyczyna jest dobrze widoczna na załączonym obrazku 🙂 W grudniu udało mi się jednak ukończyć kurs  Introduction to Functional Programming w portalu edX.  Poniżej moje notatki i wspomnienia. Przykłady prezentowane w materiałach z kursu są napisane w Haskellu (i rzeczywiście tego języka chciałam się nauczyć), ale autorzy stawiają sobie za cel przedstawienie uniwersalnych zasad programowania funkcyjnego. Wiele zadań domowych można było wykonać w wybranym innym języku funkcyjnym, na przykład w Scali.

Czasem uda nam się trochę popracować!
Czasem uda nam się trochę popracować przy biurku z podnoszonym blatem!
1. Funkcje można definiować na kilka różnych sposobów.

Żeby nie było, że „programowanie funkcyjne” to tylko szumna nazwa: w Haskellu tę samą funkcję można zdefiniować na kilka sposobów, w zależności od potrzeb i osobistego stylu.

Poniżej kilka różnych definicji tej samej prostej funkcji. Dla zabawy zdefiniujmy funkcję przyjmującą jeden argument. Funkcja zwróci dodatnią wartość logiczną wtedy i tylko wtedy, gdy przekazany jej argument liczbowy będzie poprawną odpowiedzią na wielkie pytanie o życie, wszechświat i całą resztę.

Definicja z użyciem wyrażeń warunkowych:

Inne wyrażenia warunkowe (guarded expressions):

Dopasowanie do wzorca:

Najprostsza definicja świata, czyli zwykłe wyliczenie wartości:

Zachęcam do potestowania.

2. Lambdy to tylko funkcje bez nazwy.

Programowanie funkcyjne wtargnęło przemocą do wielu popularnych języków, więc nie spodziewam się zaszokować tłumów tą informacją. Ci jednak, którzy jeszcze nie wyszli poza świat obiektów, mogą odetchnąć z ulgą: lambdy to tylko funkcje bez nazwy. Przydatne w różnych okolicznościach, na przykład kiedy funkcja ma być użyta tylko raz i ma bardzo krótką definicję.

W Haskellu lambdy wyglądają tak (backslash, zwany też ukośnikiem wstecznym, ma w zamierzeniu przypominać grecki znak λ):

A tak, dla porównania, wyglądają w Javie:

Skąd taka nazwa? Inspiracją i matematyczną podstawą języków funkcyjnych jest rachunek lambda.

3. Praca z listami to sama przyjemność.

Na obronie pracy magisterskiej zadano mi pytanie, po czym na pierwszy rzut oka poznać kod napisany w języku Lisp. Właściwa odpowiedź jest dość prosta – po listach!

W Haskellu również listy przetwarza się bardzo przyjemnie. Można wygodnie dopasowywać je do wzorców, dzieląc listy na głowę i ogon, jak w tej poniższej definicji:

Użycie:

Jak widać w ostatnim przykładzie, wzorzec (head:tail) nie zostanie dopasowany do pustej listy.

Można także bardzo wygodnie generować listy, nawet nieskończone, przy użyciu dość intuicyjnej składni tzw. list comprehension. Oto przykład, trudniejszy w słownym opisie niż w implementacji: lista kwadratów tych liczb od 1 do 10, które są podzielne przez 2.

Ładne, prawda?

4. Brak stanu i inne wymagania.

Nie ma jednej obowiązującej definicji programowania funkcyjnego. Według wielu, jest to wręcz najgorzej zdefiniowany paradygmat programistyczny.

Jednym z najistotniejszych wyznaczników kodu funkcyjnego jest brak stanu – ta sama funkcja wywołana z tymi samymi argumentami powinna zawsze zwracać tę samą wartość, najlepiej bez efektów ubocznych (tj. zmieniania czegokolwiek w tle, np. wartości pól obiektu). W dużej mierze właśnie ta właściwość spowodowała renesans popularności języków funkcyjnych. Dlaczego jest to aż tak istotne?

  • Programując w ten sposób, niejako „w prezencie” dostajemy kod doskonale poddający się zrównoleglaniu. Osobne wątki nie muszą co chwilę zsynchronizować zmieniającego się, współdzielonego stanu.
  • Kod składający się z funkcji bez efektów ubocznych jest nieporównywalnie prostszy w zrozumieniu i utrzymaniu.
  • Wyniki raz wywołanych funkcji można zapamiętać w cache, by przy kolejnym wywołaniu z tymi samymi argumentami, po prostu sięgnąć do już wyznaczonej wartości.

Inne ważne cechy języków funkcyjnych to możliwość definiowania: funkcji wyższego rzędu (funkcji, które zwracają inne funkcje lub przyjmują inne funkcje jako argumenty) oraz, oczywiście, lambd.

5. Upierdliwa obsługa operacji I/O.

Czytanie z wiersza poleceń w Javie nigdy nie należało do przyjemności. Musimy pootwierać piętrowe strumienie, czytać z nich, sprawdzając, czy zostało coś jeszcze do pobrania, a na koniec pamiętać o ich zamknięciu (ok, w Javie 7 pojawił się interfejs AutoCloseable, wystarczy tylko pamiętać o jego użyciu).

Haskell pokazuje nam jednak, w pewnym sensie, że może być jeszcze gorzej. Dlaczego? Ponieważ odczytanie ciągu znaków z wejścia wymaga złamania nagięcia ograniczeń, które stanowią o sile języków funkcyjnych.

Funkcja, za pomocą której będziemy pobierali znaki z wejścia, siłą rzeczy musi zwracać odmienne wyniki dla tego samego argumentu. Co gorsza, musi też w tle zmieniać stan naszego świata (przy kolejnych wywołaniach znaki są konsumowane i inny znak czeka w kolejce na odczytanie).

Dla zachowania czystości kodu, a właściwie w celu oddzielenia kodu czystego od „skażonego”, operacje I/O w Haskellu są opakowywane w akcje. Całość „nieczystego” przetwarzania odbywa się wewnątrz akcji, z której na koniec możemy wyciągnąć czystą wartość za pomocą operatora <-.

Wygląda to stosunkowo niewinnie:

ale warto mieć świadomość, co dzieje się pod spodem. W rzeczywistości dotykamy tu najbardziej chyba przerażającego elementu języka, czyli monad. Kod powyżej czyta linię ze standardowego wejścia i wypisuje ją bez zmin na standardowe wyjście.

6. Brzydkie słowo na „m”.

Monady.

Tysiące tutoriali w Internecie. Długie wpisy (po polsku na przykład ten świetnie przygotowany i napisany tekst Jacka Laskowskiego), godzinne nagrania wideo. Duma lub poczucie wyższości osób, które wreszcie to zrozumiały, więc oficjalnie potrafią programować funkcyjnie.

Offtopic: słowo „monada” kojarzy mi się przede wszystkim ze zbiorem opowiadań „Zamknięty świat”, co dowodzi chyba, że nie była to lektura dla dziecka w podstawówce.

Wytłumaczenie, czym są monady, to temat na osobny, długi wpis – jak ten, który linkuję powyżej. W dużym uproszczeniu (zresztą moje rozumienie tego tematu to nadal właśnie duże uproszczenie): monady to kontenery, które pozwalają na tworzenie łańcuchów operacji, porównywanych do linii produkcyjnych, gdzie każde kolejne stanowisko dodaje coś od siebie. Za pomocą monad można symulować zmiany stanu i dodawać efekty uboczne. Monada obowiązkowo musi definiować dwie operacje, nazywane najczęściej return (włożenie wartości do kontenera) i bind (wyjęcie wartości w celu wstawienia jej do kolejnej funkcji zwracającej monadę; w Haskellu służy do tego operator >>=).

Podstawy teoretyczne monad to teoria kategorii. Po drodze trzeba jeszcze zrozumieć, czym są funktory i monoidy. Zainteresowanym polecam lekturę rozdziału poświęconego monadom w (dostępnej za darmo) książce Learn You a Haskell for Great Good!. Sama wrócę tam jeszcze wiele razy.

7. Istnieją frameworki webowe dla Haskella. Serio.

Można pisać aplikacje webowe w Haskellu. Istnieją gotowe frameworki. Więcej niż jeden! Osobiście znam kogoś, kto napisał w tym języku sklep internetowy.

8. Wyjście poza imperatywną strefę komfortu otwiera oczy raz na zawsze.

Kurs, przynajmniej na początku, wydał mi się bardzo prosty. Uznałam wstępnie, że Haskell ma zaskakująco niski próg wejścia – bo przecież nie programowałam wcześniej w żadnym języku funkcyjnym. Podczas rozmów z innymi kursantami dotarłam jednak do innej przyczyny: przez kilka lat sporo programowałam w Prologu. Wygląda na to, że kiedy raz podejmie się wysiłek przestawienia się na inny sposób myślenia – w tym wypadku nieimperatywny, nieobiektowy i mniej algorytmiczny – o wiele łatwiej wnika się w kolejne dziwactwa.

9. Programowanie funkcyjne, nie funkcjonalne!

Łatwo wpaść w pułapkę tłumaczenia angielskiej nazwy functional programming jako „programowanie funkcjonalne”. Nawet polska Wikipedia dopuszcza ten termin, a przecież jest on pozbawiony sensu! Functional znaczy „oparty o funkcje” (w sensie matematycznym), po polsku „funkcyjny”. „Funkcjonalny” to „związany z działaniem” lub „dobrze spełniający swoją rolę”. O programowaniu funkcyjnym można co prawda powiedzieć, że jest funkcjonalne… 😉 Jednak w pełni poprawna jest tylko pierwsza nazwa.

10. Można wziąć kasę za kurs online, w którym po wiedzę o najtrudniejszych zagadnieniach odsyła się do zewnętrznych stron

Kurs Haskella był pierwszym kursem online, za który zapłaciłam – i dobrze się stało, ponieważ bez takiej motywacji na pewno odpadłabym po którejś nieprzespanej nocy.

Tym boleśniej odczułam fuszerkę, na jaką pozwoliła sobie ekipa z Uniwersytetu Technicznego w Delft. Wiedza przekazana w ramach kursu nie była wystarczająca do odrobienia zadań domowych w jego drugiej części. Co gorsza, w wykładach pominięto właśnie te bardziej problematyczne zagadnienia, począwszy od tego „czym, u licha, jest ta monada?”.

Opisy niektórych zadań domowych rozpoczynały się niepozornym poleceniem przeczytania treści zawartych na zewnętrznej stronie, a zewnętrzne strony wyglądały jak, nie przymierzając, strony man w Linuksie. Suchar.

Ciągła Integracja: jak skonfigurować proces budowania na serwerze Bamboo?

Ten wpis jest uzupełnieniem artykułu Ciągła Integracja: anioł stróż dobrego programisty, gdzie obiecałam pokazać konfigurację procesu budowania na konkretnym przykładzie serwera Atlassian Bamboo.

Co jest potrzebne? Parę słów na temat konfiguracji serwera i agentów

Żeby zdefiniować proces budowania przy użyciu Bamboo musimy mieć dostęp do:

  1. Serwera Bamboo, który będzie zarządzał naszymi procesami budowania (inaczej buildami lub planami budowania).
  2. Agentów Bamboo, czyli serwerów roboczych, które będą realizowały zadania zlecone przez Serwer Bamboo.

Kod serwera i agentów jest napisany w Javie. Serwery robocze, które chcą pełnić rolę agentów Bamboo i przyjmować zadania związane z procesem budowania, muszą zarejestrować się na serwerze.

Bamboo można, po wykupieniu licencji, zainstalować na własnym serwerze – z taką instalacją mam do czynienia na co dzień w pracy. Druga możliwość to skorzystanie z chmury firmy Atlassian. Wszystkie użyte w tym wpisie zrzuty ekranu pochodzą z wersji demonstracyjnej w chmurze (Bamboo Cloud). Główny serwer Bamboo działa wówczas w chmurze firmy Atlassian, natomiast agentów (serwery robocze) musisz uruchomić w chmurze Amazon EC2 w oparciu o obraz systemu dostarczony przez Atlassiana. Architekturę tego rozwiązania przedstawia poniższy rysunek.

1
Architektura Bamboo w wersji pochmurnej (http://devopscloud.net/2011/03/25/setting-up-a-windows-ami-for-use-with-elastic-bamboo/)

Przy okazji drobna uwaga z kategorii troubleshooting: spędziłam parę godzin na próbach zrozumienia, dlaczego, mimo że moja instancja w chmurze EC2 bez problemu uruchamia się na żądanie serwera, nie realizuje żadnych wyznaczonych przez serwer zadań. Wreszcie okazało się, że odpowiedni obraz systemu z zainstalowanym agentem Bamboo jest dostępny tylko, jeśli jako lokalizację wybiorę region „US East (N. Virginia)”. W przeciwnym razie uruchamia się czysty system, niezdolny do współpracy z serwerem Bamboo. Być może od czasu moich prób problem został już rozwiązany.

Struktura Planu Bamboo

Proces budowania (ang. build) w świecie Bamboo jest określany mianem Planu Budowania (ang. Build Plan).

Tabela poniżej przedstawia elementy tworzące strukturę takiego planu.

Projekt
  • Może zawierać wiele planów.
  • Wyświetla zbiorczy raport dla wszystkich swoich planów.
  • Łączy się z innymi aplikacjami (np. JIRA do zarządzania zadaniami, Crucible do przeglądów kodu).
Plan
  • Dzieli się na Etapy, które wykonywane są sekwencyjnie.
  • Definiuje domyślne repozytorium kodu.
  • Określa, kiedy uruchomić proces budowania (wyzwalacze planu).
  • Wysyła powiadomienia o wynikach kolejnych przebiegów.
  • Pozwala definiować zmienne planu.
Etap (Stage)
  • Dzieli się na Porcje Pracy, wykonywane równolegle na wielu agentach jednocześnie (jeśli są dostępne).
  • Etap (tj. wszystkie Porcje Pracy) musi się zakończyć powodzeniem, żeby wykonany został kolejny etap.
Porcja Pracy(Job )
  • Steruje wykonaniem listy Zadań, sekwencyjnie na tym samym agencie.
  • Zbiera wymagania poszczególnych zadań, by wybrać agenta z odpowiednim zestawem umiejętności (ang. capabilities).
  • Definiuje artefakty.
Zadanie (Task)
  • Zadanie to atomowa jednostka pracy, np. pobranie kodu z repozytorium, wywołanie polecenia Maven, uruchomienie skryptu.

Kompletna dokumentacja w języku angielskim jest dostępna na stronie https://confluence.atlassian.com/display/BAMBOO/Bamboo+Documentation+Home.

Utworzenie planu

Po pierwsze, w nowszych wersjach firma Atlassian ukryła przed użytkownikami link do Bamboo. Jeśli masz już konto w Jira/Bamboo Cloud lub dostęp do firmowej instalacji, oto jak przejść do panelu Bamboo:

Link do konfiguracji Bamboo
Link do konfiguracji Bamboo

Tworzenie planu:

Tworzenie nowego planu (procesu budowania)
Tworzenie nowego planu (procesu budowania)

Po utworzeniu planu możemy przejść do jego konfiguracji (na rysunku widać plan, który został już kilka razy uruchomiony – kolorowe ptaszki i wykrzykniki to historia jego kolejnych przebiegów):

5
Historia wyników i przejście do konfiguracji planu

Repozytorium kodu źródłowego

Plan ma na celu testowanie, budowanie i być może wdrażanie kodu źródłowego. Kod powinien być przechowywany w dobrze zorganizowanym repozytorium. Bamboo pozwala połączyć się ze wszystkimi najpopularniejszymi repozytoriami (w przypadku rozwiązań niestandardowych można jeszcze uciec się do pomocy pluginów). Repozytoria kodu źródłowego (w widocznym na rysunku przykładzie jest do publiczne repozytorium GitHub) konfiguruje się w zakładce Repositories:

Dodanie definicji repozytorium kodu źródłowego
Dodanie definicji repozytorium kodu źródłowego

Wyzwalacze

Wspomniałam w poprzednim wpisie, że każdy proces budowania ma swoje wyzwalacze. Możesz chcieć uruchamiać go ręcznie, automatycznie o określonej porze, albo po zmianie w wybranym repozytorium kodu. Jeśli zakończyłeś już konfigurację repozytoriów, możesz przejść do zakładki Triggers i tam zdefiniować wyzwalacze:

Kiedy uruchomić plan?
Kiedy uruchomić plan?

Ręczne uruchomienie planu przez wyznaczone osoby (zakładka Permissions pozwala na definicję uprawnień) zawsze jest możliwe, dlatego tej opcji nie ma na rozwijanej liście. Do dyspozycji masz: odpytywanie repozytorium o zmiany (sam wyznaczasz interwał dla tych zapytań), reakcję na sygnał z repozytorium, planowanie w oparciu o składnię cron oraz możliwość prostszego wskazania konkretnej godziny, o której plan ma zostać wykonany.

Powiadomienia

Skoro wiesz już, kiedy będzie uruchamiany plan, możesz podjąć decyzję kto, kiedy i jakim kanałem ma być powiadamiany o jego wynikach. Podstawowe źródło informacji to oczywiście raport widoczny poprzez interfejs www serwera,  gdzie wyniki odpowiednich planów „palą się” na czerwono bądź zielono. Warto wprowadzić wśród programistów dyscyplinę zerkania tam przynajmniej rano, po przyjściu do pracy. Oprócz tego możesz jednak stworzyć proste reguły powiadamiania konkretnych osób – np. mailem – o powodzeniu bądź niepowodzeniu procesu. Służy do tego zakładka Notifications.

Dobra rada – nie przesadzaj z powiadomieniami, ponieważ w końcu programiści przekierują je do katalogu spam. Informuj tylko te osoby, które naprawdę powinny wiedzieć o awarii lub samym fakcie uruchomienia danego planu.

Kto powinien wiedzieć o wynikach?
Kto powinien wiedzieć o wynikach?

Tworzenie etapu (Stage)

Domyślnie plan składa się z jednego etapu (Default Stage). Jeśli etapów będzie więcej, zostaną wykonane sekwencyjnie, przy czym niepowodzenie jednego z etapów zatrzyma wykonanie kolejnych. Dodatkowe etapy możesz tworzyć, korzystając z przycisku Create stage w zakładce Stages.

Tworzenie etapu (stage) w procesie budowania
Tworzenie etapu (stage) w procesie budowania

Tworzenie porcji pracy (Job)

Etap składa się z jednej lub więcej porcji pracy. Na powyższym rysunku widać etap zawierający dwie porcje pracy (Job1 i Job2) oraz możliwość dodania kolejnych (Add Job).

Uwaga! – porcje pracy są wykonywane równolegle; ich kolejność na liście nie ma najmniejszego znaczenia (Bamboo sortuje je alfabetycznie). Jeśli masz dyspozycji tylko jeden serwer roboczy, to oczywiście zostaną wykonane jedna po drugiej – w kolejności, na którą nie masz wpływu.

Każdej porcji pracy przypisane jest kilka właściwości:

  1. Zadania (patrz niżej)
  2. Artefakty (również niżej)
  3. Wymagania

Wymagania to oczekiwania wobec serwera roboczego, który może podjąć się wykonania danej porcji pracy (i zdefiniowanych w niej zadań). Możemy zażądać dostępności określonych bibliotek, nałożyć wymagania na nazwę serwera itp. Wymaga to pewnej konfiguracji po stronie agenta Bamboo. Serwer Bamboo sprawdza wymagania w czasie rzeczywistym i od razu informuje, ile agentów potrafi wykonać daną porcję pracy.

W poniższym przykładzie żaden agent nie spełnia wymagań (zerowych), ponieważ nie została uruchomiona żadna instancja w chmurze 🙂

Definicja wymagań porcji pracy wobec serwera roboczego (agenta Bamboo)
Definicja wymagań porcji pracy wobec serwera roboczego (agenta Bamboo)

Tworzenie zadania (Task)

Zadanie to najmniejsza, atomowa jednostka pracy w planie Bamboo. Zadania są definiowane w ramach jednostek pracy i są wykonywane sekwencyjnie. Uwaga – niepowodzenia są raportowane na poziomie jednostek pracy: nie dostaniesz czytelnej informacji, które z zadań spowodowało problem (chyba że w dobrze zorganizowanych logach lub na postawie informacji o tym, który test nie przeszedł).

Bamboo predefiniuje wiele zadań. Pobranie danych z repozytorium, wykonanie budowania przy użyciu narzędzia takiego jak Maven – w takich sytuacjach musisz tylko wypełnić odpowiedni formularz, np. wskazując odpowiednie ze zdefiniowanych repozytoriów.

Poniższy zrzut ekranu przedstawia widok jednego z dwóch zadań w ramach jednostki pracy. Jest to predefiniowane zadanie pobrania kodu z repozytorium kodu źródłowego.

Zadania w ramach porcji pracy
Zadania w ramach porcji pracy

Jeżeli musisz zrobić coś niestandardowego – jak niezalecane specjalnie wysłanie przeprowadzonych przez Twój plan zmian w kodzie do repozytorium – możesz poszukać odpowiedniego pluginu (lub samodzielnie go napisać), albo użyć zadania Script, w którym samodzielnie napiszesz kod dla odpowiedniej powłoki w systemie operacyjnym agenta:

Niestandardowe zadanie: zawsze można napisać skrypt!
Niestandardowe zadanie: zawsze można napisać skrypt!

Artefakty

Artefakty, czyli dodatkowe produkty planu budowania, jak wspomniałam już wcześniej, są dostępne na etapie jednostek pracy (jobs), ale wygenerować je trzeba przy użyciu zadań. Kod przedstawiony na powyższym rysunku zapisuje do pliku wartość jednej ze zmiennych Bamboo (ich lista oraz zasady definiowania własnych są dostępne tutaj). Możemy oznaczyć ten plik jako artefakt planu. Artefakty można pobierać (także przez przeglądarkę) oraz współdzielić pomiędzy etapami (a w nowszych wersjach serwera również pomiędzy planami).

Oto, jak wygląda gotowy do pobrania artefakt:

Artefakt gotowy do pobrania po udanym przebiegu planu
Artefakt gotowy do pobrania po udanym przebiegu planu

Uwaga! – przy definiowaniu artefaktów należy pamiętać o tym, że ścieżkę do nich podaje się względem katalogu roboczego danej jednostki pracy. Jeśli korzystasz ze ścieżek bezwzględnych na serwerze, możesz sprawdzić wartość zmiennej bamboo.build.working.directory.

Testy

Testy mają kluczowe znaczenie dla całego procesu ciągłej integracji. Technicznie sprowadzają się jednak do zwykłego, predefiniowanego zadania Bamboo: odczytu wskazanego przez Ciebie (w katalogu roboczym) pliku JUnit/XML.

Troubleshooting (na podstawie własnych, bolesnych doświadczeń):

  1. Jeśli w planie były nieprzechodzące testy, a w kolejnym przebiegu testów brak, Bamboo uzna to za błąd kompilacji.
  2. Kilka wersji Bamboo przejawiało następujący błąd: katalog z wynikami testów nie był odświeżany, jeśli plan został wykonany „zbyt szybko”.
  3. Jeśli chcesz dopuścić nieprzechodzące testy (np. na potrzeby TDD), musisz skorzystać z opcji dodania ich do kwarantanny.

Podsumowanie

Rysunek przedstawia podsumowanie kilku ostatnich przebiegów w planie budowania Bamboo, wraz z informacją o przyczynie uruchomienia planu i o liczbie testów:

Podsumowanie ostatnich przebiegów planu budowania wraz z informacją o liczbie testów i przyczynie uruchomienia procesu
Podsumowanie ostatnich przebiegów planu budowania wraz z informacją o liczbie testów i wyzwalaczu

Dla każdego z przebiegów możesz bardzo intuicyjnie doklikać się do informacji o testach, commitach do repozytorium i odpowiedzialnych za nie osobach. Wspominałam już o możliwości synchronizacji Bamboo z innymi produktami firmy Atlassian. Możesz, na przykład, utworzyć zadanie w Jirze w oparciu o nieudany przebieg:

Możliwość utworzenia zadania w Jirze w oparciu o nieudany przebieg procesu budowania
Możliwość utworzenia zadania w Jirze w oparciu o nieudany przebieg procesu budowania

Miłej zabawy!

Ciągła Integracja: anioł stróż dobrego programisty

Gdybym miała wskazać jedną ważną umiejętność, której brakuje większości absolwentów informatyki (i pokrewnych kierunków) aplikujących do pracy u nas w biurze, z pewnością byłaby to Ciągła Integracja. Wygląda na to, że programy studiów nie obejmują tego tematu, a prowadzący zajęcia nie przykładają do niego specjalnej wagi. Tymczasem w moim doświadczeniu niewiele rozwiązań skutkuje tak zauważalnym wzrostem produktywności pracy, a także codziennego zadowolenia programistów, jak ta właśnie praktyka. Na szczęście, luki w wiedzy nie tak trudno uzupełnić, choć diabeł, jak zwykle, tkwi w szczegółach.

Ciągła Integracja (CI) – co to?

Definicją służy, jak zawsze niezawodny, Martin Fowler:

Ciągła Integracja (ang. Continuous Integration) to praktyka programistyczna, w której członkowie zespołu często scalają wyniki swojej pracy – z reguły każdy robi to przynajmniej raz dziennie. W tej sposób każdego dnia powstaje kilka zintegrowanych wersji kodu, które są sprawdzane przez automatyczny proces budowania (i testowania).

Jest to również jedna z 12 praktyk Programowania Ekstremalnego.

Zasadę dobrze przedstawia poniższa ilustracja, pochodząca ze strony firmy Atlassian.

Ciągła Integracja: architektura na przykładzie narzędzia Atlassian Bamboo

W wielkim skrócie: programiści pracują nad bazą kodu, na bieżąco aktualizując zawartość wspólnego repozytorium. Niezależnie od niego działa serwer Ciągłej Integracji, który co jakiś czas (np. po każdym wysłaniu porcji kodu do repozytorium, albo zawsze o 2 w nocy) pobiera kod z repozytorium, który następnie testuje, kompiluje i, jeśli wszystko poszło jak trzeba, przygotowuje do wdrożenia. W przypadku wykrycia błędów niezwłocznie informuje osoby zaangażowane w projekt o problemie.

Po co to?

  1. Ciągła Integracja zmniejsza ryzyko związane z integracją na samym końcu projektu – błędy, niekompatybilność interfejsów, trudny do oszacowania czas na poskładanie całości. Z własnego doświadczenia znam przypadek, w którym sprzeczka o to, po której stronie interfejsu jest problem i kto ma przyjść do pracy w sobotę, żeby naprawić go przed poniedziałkową prezentacją, doprowadziła do odejścia „pokrzywdzonej” osoby z pracy.
  2. CI ułatwia naprawę błędów: ich szybkie wykrywanie sprawia, że łatwiej zlokalizować przyczynę  – wiadomo, co było ostatnio modyfikowane i jaka wersja działała poprawnie.
  3. CI chroni przed niespodziankami wynikającymi z różnic pomiędzy środowiskiem deweloperskim a produkcyjnym (np. inne środowisko uruchomieniowe danego języka, niestandardowe biblioteki).beavis
  4. CI umożliwia demonstrowanie aplikacji i konsultację z klientem w dowolnym momencie dzięki stałej dostępności ostatniej działającej wersji.
  5. CI ułatwia refaktoryzację (po każdej „kosmetycznej”  zmianie możemy szybko sprawdzić, czy wszystko gra).
  6. Ciągła integracja zdejmuje z programistów obowiązek wykonywania wielu powtarzalnych, nierozwijających (a jednak trudnych!) czynności.

(Niezgrabne) terminy

Terminy związane z Ciągłą Integracją trudno zwięźle przełożyć na języki polski. Najwięcej kłopotów sprawia najważniejszy z terminów, czyli build.

Proces budowania (build) to dużo więcej niż sama kompilacja. Często obejmuje:

  • kompilację
  • testy
  • inspekcję kodu
  • wdrożenie

Jeśli wykorzystujesz serwer CI tylko do kompilacji, nie powinieneś nazywać swojego procesu Ciągłą Integracją.

Narzędzie do budowania (build script lub build automation tool) pozwala na zdefiniowanie procesu budowania uruchamianego „jednym kliknięciem”. Nie musi wiązać się z serwerem Ciągłej Integracji: może działać w ramach IDE lub konsolowo. Przykłady: Maven, Rake, make.

Integracyjny proces budowania (integration build) to proces budowania uruchomiony na osobnym, specjalnie do tego przeznaczonym serwerze.

Prywatny proces budowania (private build) to proces budowania uruchomiony przez programistę lokalnie, przed wysłaniem kodu do repozytorium (lub w prywatnej, należącej do danego programisty gałęzi kodu w repozytorium).

Serwer Ciągłej Integracji (schematycznie przedstawiony na pierwszej ilustracji w tym wpisie) usprawnia definiowanie integracyjnych procesów budowania i  odpowiada za ich uruchamianie w określonych okolicznościach (zwróć uwagę na akapit o wyzwalaczach).

Dobre praktyki

Cechy, którymi powinno charakteryzować się środowisko Ciągłej Integracji:

  1. Jedno wspólne repozytorium kodu, zawierające wszystko, co potrzebne do zbudowania i uruchomienia systemu na czystej maszynie. Oczywiście zdarza się, że nad projektem pracuje kilka rozproszonych zespołów odpowiadających za oddzielne komponenty i preferujących różne systemy kontroli wersji. Ciągła Integracja jest wtedy jeszcze bardziej potrzebna!
  2. Automatyczne budowanie – żadnych wyskakujących okienek i ręcznego kopiowania plików!
  3. Automatyczne testy podczas budowania – to, że program się skompilował i da się go uruchomić, nie znaczy jeszcze, że wszystko z nim w porządku. Warto sprawdzać nie tylko wyniki testu, ale także procent pokrycia kodu testami.
  4. Codzienne wysyłanie i scalanie (działającego!) kodu przez programistów. Integracja to komunikacja – programiści widzą, nad czym pracują inni i jak im idzie.
  5. Inspekcje kodu. Przeglądy kodu, przeprowadzane przez innych programistów, mogą nawet doprowadzić do niewpuszczenia kodu na serwer. Ich podstawową wartością jest jednak wymiana wiedzy.
  6. Uruchomienie procesu budowania po każdej zmianie kodu w repozytorium i natychmiastowa reakcja na awarie
  7. Szybkość procesu budowania – natychmiastowa informacja zwrotna. Automatyczne testy nie powinny ciągnąć się przez godzinę. Jeżeli nie ma możliwości optymalizacji działania systemu ani dołożenia większej liczby maszyn, można stosować techniki takie jak randomizacja testów lub staged tests, w których najistotniejsze, szybkie testy przesuwa się na początek, żeby autor kodu wiedział, czy może (wstępnie) kontynuować pracę.
  8. Testy w środowisku jak najlepiej udającym produkcję. Drobiazgi, takie jak inne numery portów, potrafią doprowadzić do prawdziwej katastrofy.
  9. Ciągła dostępność wyników testów oraz najnowszej zbudowanej wersji aplikacji. Wszyscy programiści powinni wiedzieć, co dzieje się w głównej gałęzi repozytorium. Informację tę można dystrybuować na różne sposoby: powiadomienia (np. mailowe), ekrany wyświetlające status (zielony = wszystko gra, czerwony = awaria), lava lamps… Wyobraźnia nie zna granic 🙂

220px-Series_of_build_lights

Ciągłe Wdrażanie, czyli jeden krok dalej

Oprócz terminu Continuous Integration możesz natknąć się jeszcze na Continuous Deployment, Continuous Release, Continuous Delivery (dosłownie to: ciągłe wdrażanie, wydawanie, dostarczanie).

Ciągłe Wdrażanie to to Ciągła Integracja rozszerzona o wdrażanie z częstotliwością odpowiadającą integrowaniu – udostępniamy klientom każdą wersję, która przeszła testy automatyczne.

Omawianie szczegółowych różnic pomiędzy przytoczonymi tu terminami zdecydowanie wykracza poza ambicje tego artykułu.

Wyzwalacze, czyli kiedy budować?

Większość serwerów Ciągłej Integracji pozwala na zdefiniowanie następujących „wyzwalaczy” procesu budowania:

  • ręczny – na żądanie programisty,
  • planowy – o określonej porze (np. tzw. „nightly build”),
  • odpytanie repozytorium – w reakcji na zmianę kodu źródłowego,
  • zdarzeniowy – np. w reakcji na zmianę kodu źródłowego (ale to repozytorium informuje) lub zakończenie innego procesu budowania.

Który serwer wybrać?

Serwerów Ciągłej Integracji jest na rynku bardzo, bardzo dużo. Najpopularniejszy jest w tej chwili otwarty serwer Jenkins (fork projektu Hudson, to historia na osobny artykuł). Inna popularna opcja to Bamboo – narzędzie komercyjne, którego siła tkwi w pełnej integracji (sic!) z innymi ułatwiającymi życie rozwiązaniami firmy Atlassian. Bardziej szczegółowe porównanie można znaleźć nawet w Wikipedii.

Co z tego zapamiętać? tl;dr

Ciągła Integracja pozwala programiście być programistą. Programista zajmuje się tworzeniem kodu, a nie powtarzalnymi operacjami niezbędnymi do scalenia kodu i przygotowania wydania.

Dzięki CI natychmiast (ewentualnie następnego ranka po beztroskiej nocy) otrzymujemy informację o tym, że coś poszło nie tak, dzięki czemu możemy reagować na bieżąco.

W następnym odcinku

W kolejnym odcinku pokażę, jak zdefiniować proces budowania na serwerze Bamboo.

Gdzie znaleźć więcej informacji?