Warsztat programisty

Laboratorium 2: Warsztat programisty

Programowanie to sport zespołowy.  Duże programy są tworzone przez zespoły, lub wiele współpracujących zespołów.  Bywa i tak, że nikt nie ma wiedzy o całym systemie, a każdy zna tylko jego fragmenty.  Jak sensownie funkcjonować w takich realiach?  Oto kilka zasad dobrego warsztatu programisty.

Kwestia stylu

Dla kogo piszemy kod? Dla komputera? Ależ skąd! Piszemy dla siebie i swoich współpracowników. Inaczej, po skompilowaniu pierwszej wersji programu, możnaby kod źródłowy wyrzucić. Tymczasem program, który jest używany, będzie wielokrotnie modyfikowany, przez nas i naszych kolegów. Modyfikacje same są zwykle niewielkie, ale wymagają przeczytania większego fragmentu kodu. Wniosek: kod źródłowy jest więcej razy czytany, niż pisany. Powinniśmy więc go tak pisać, żeby się łatwo czytało i modyfikowało, a nie żeby jak najszybciej stworzyć pierwszą wersję. 

Wcięcia

Wcięcia muszą być! Kod bez wcięć nie będzie oceniany.  Wszytkie szczegóły dotyczące wcięć wynikają z tego po co one są: wcięcia mają ułatwić szybkie dopasowanie początku i końca konstrukcji składniowej.  
Wielkość wcięć: od 2 do 4, w zależności od smaku, ale konsekwentnie stała. Minimalna wielkość wcięcia to 2, bo inaczej "schodki" nie są widoczne.  Maksymalna to 4, bo inaczej kod robi się strasznie szeroki.  
W pozostałych szczegółach, możecie dobrać Państwo styl, który najbardziej Wam odpowiada, byle konsekwentnie.  Tutaj można znaleć przykładowy dobry styl. 

Odstępy

Robimy odstępy wokół operatorów binarnych i po znakach interpunkcyjnych.  Nie robimy odstępów po wewnętrznej stronie nawiasów.  Na przykład: 2 + 2 * 4, [2; 3; 4], (2, 3 - 4). 
Odstępy w pionie (puste wiersze) też są ważne.  Między definicjami procedur robimy wiersz lub dwa odstępu.  Wewnątrz dłuższych procedur możemy też robić pojedyncze wiersze odstępu dla podkreślenia struktury kodu. 

Komentarze

W podejściu do komentarzy można spotkać dwie skrajności:

  • Kod sam się tłumaczy i komentarze są zbędne, oraz
  • Wszystko należy komentować. 

Oba podejścia są złe. Owszem, kod ma być przejrzysty i ma być widać co się w nim dzieje, ale zawsze są pewne aspekty, które z niego nie wynikają, np. co jest celem? po co coś się dzieje? co zakładamy? Z drugiej strony, umieszczanie w komentarzach "relacji na żywo" z obliczeń mija się z celem i tylko utrudnia czytanie kodu. Podstawowa zasada brzmi: Komentarze mają ułatwić czytanie i zrozumienie kodu. Odnosi się to tak samo do innych programistów, z którymi współpracujemy, jak i do nas samych tylko za jakiś czas. Oto kilka typowych miejsc, w których warto umieszczać komentarze:

  • deklaracje typów danych (lub ogólniej struktur danych) -- co reprezentują? jakie są założenia co do dopuszczalnych wartości (tzw. niezmienniki typów danych),
  • definicje procedur -- co zakładamy o argumentach, czego można oczekiwać jako wyniku, za co procedura "odpowiada",
  • interfejs modułu (o mudułach dokładniej będzie za tydzień) -- to co powinien wiedzieć użytkownik modułu.

Identyfikatory

Identyfikatory powinny być znaczące. Jeśli z nazwy identyfikatora wynika czym jest argument, to często nie trzeba nic więcej o nim pisać w komentarzach. 
Identyfikatory (nazwy stałych i procedur) powinny zaczynać się z małej litery.  Wieloczłonowe nazwy proponuję łączyć podkreśleniem: taki_sobie_identyfikator.  

Testy

Program, który jest rozwijany podlega ciągłym zmianom.  Jak sprawdzić, czy kolejne zmiany nie wprowadzają nowych błędów do programu?  Testować!  Warto utrzymywać bogaty zestaw testów, które po każdej zmianie w programie możemy łatwo uruchomić. Testy nie zagwarantują nam poprawności kodu, ale odpowiedni zestaw testów potreafi wykryć większość błędów.
Pisząc kod, powinniśmy odrazu tworzyć dla niego testy.  Dla każdej funkcjonalności / procedury piszemy testy.  Mamy jakąś elementarną procedurę wykorzystywaną w naszym kodzie -- piszemy dla niej testy. Mamy procedurę integrującą kilka procedur pomocniczych w coś większego -- piszemy dla niej testy.  Tak jak procedury tworzą hierarchię, tak testy tworzą swoistą piramidę.  Powiniśmy mieć wiele prostych testów dla elementarnych procedur (unit testy), oraz mniejszą liczbę testów-scenariuszy symulujących sposób użycia całego systemu. 
Jak pisać testy w Ocamlu?  Najlepiej wykorzystać operację assert <warunek>;. (Uwaga na ";". Narazie nie wnikamy w imperatywność.) Sprawdza ona, czy warunek jest spełniony i jeśli nie, przerywa obliczenia.  Gdy uruchamiamy kod "dla użytkownika", możemy wyłączyć sprawdzanie asercji opcją -noassert.  Testy mogą być po prostu asercjami na poziomie definicji (tj. nie zagnieżdżonymi wewnątrz definicji).  Umieszczanie asercji wewnątrz definicji procedur też ma sens, ale to już coś innego niż testowanie.

Code review

Dwie najpopularniejsze techniki pisania kodu, mające na celu wyeliminowanie błędów, to code reviews i pair-coding.  Pair-coding polega na pisaniu razem w parach.  Jedna osoba pisze kod, a druga śledzi i komentuje ten proces.  Obie muszą rozumieć co się dzieje i być przekonane, że powstający kod jest poprawny.  Code review polega na tym, że kod, lub zmiana w kodzie, napisana przez jednego programistę jest przeglądana przez innego programistę.  Przeglądający może zgłaszać zastrzeżenia i wskazywać co należy poprawić.  Zasadniczo reviewer nie wprowadza poprawek sam, z wyjątkiem drobiazgów.  Tę właśnie technikę zastosujemy na Laboratorium. 

Systemy kontroli wersji

Jeśli pracujemy w zespole wieloosobowym nad programem składającym się z wielu plików, to jak synchroizować wyniki swojej pracy?  Do tego właśnie służą systemy kontroli wersji.  System kontroli wersji pamięta:

  • aktualną wersję programu, 
  • aktualną "oficjalną" wersję programu (release), 
  • poszczególne zmiany wprowadzone przez programistów, 
  • kto wprowadził dany fragment kodu.

Starsze systemy kontroli wersji (RCS) wymagały blokowania plików w celu wprowadzania zmian w nich.  Nowsze systemy kontroli wersji (Subversion, Git) pozwalają na równoczesne wprowadzanie zmian w tych samych plikach przez wielu programistów.  W praktyce dobrze się to sprawdza.  Jednak zmiany takie nie zawsze można automatycznie scalić.  Czasami konieczna jest interwencja człowieka. 

Git

Git jest aktualnie chyba najpopularniejszym systemem kontroli wersji.  Git jest systemem rozproszonym.  Na każdym komputerze biorącym udział w tworzeniu programu znajduje się repozytorium zawierające kompletną (choć niekoniecznie aktualną) informację o historii wersji programu.  Repozytoria mogą wymieniać między sobą informacje o wprowadzanych zmianach.  Zwykle każdy programista ma jedno repozytorium lokalne na swoim komputerze, oraz istnieje jedno zdalne repozytorium, służące synchronizowaniu zmian. 
W Git'ie konkretna wersja programu to commit. Commit ma poprzedni commit i wiadomo jakie zmiany względem niego wprowadza.  Jeden poprzedni commit może mieć wiele następnych commitów (fork).  Możliwy jest też commit o dwóch poprzednich commitach (merge), scalający zmiany wprowadzone od ich wspólnego poprzednika. 
Zwykle myślimy w kategoriach gałęzi (branch), a nie commit'ów.  Branch to wskaźnik na konkretny commit.  W miarę wprowadzania zmian do kodu, wskaźnik ten jest przesuwany na kolejne commity. Zwykle mamy główną gałąź (trunk/master) oraz wiele gałęzi pobocznych, tworzonych na potrzeby opracowania poszczególnych zmian i łączonych potem z główną gałęzią. 
Git bardzo dobrze wspiera przeglądy kodu.  Typowy cykl opracowania zmiany w kodzie wygląda następująco:

  • Autor:
    • utworzenie feature branch'a: git branch xxxx; git checkout xxxx
    • dodanie kodu / zmiany w kodzie, 
    • potwierdzenie zmian w repozytorium: git commit ....
    • przesłanie zmian: git push
  • Reviewer:
    • pobranie zmian: git pull; git checkout xxxx; git merge 
    • wprowadzone zmiany: git diff master..xxxx
    • przegląd kodu, testy, itp. 
    • włączenie zmian do głównej gałęzi: git checkout master; git merge xxxx

Istnieje wiele serwisów oferujących przechowywanie zdalnego repozytorium, np. GitHub, BitBucket. Można też do tego wykorzystać współdzielony system plików, choć właściwa kontrola praw dostępu wymaga pewnie utworzenia mnóstwa grup.  Jeżeli będziecie Państwo przechowywać swoje programy zaliczeniowe w repozytoriach, to proszę zadbać, żeby nie były one publicznie dostępne (przynajmniej dopóki nie minie ostateczny termin oddawania programów).