Damian Nowak is a CEO at Virtkick. He's a Ruby coder, an Arch Linux hacker, and drinks good beer.
Kilka dni temu skończyłem czytać The Pragmatic Programmer – książkę skierowaną do każdego, kto ma ambicję być dobrym programistą. Naprawdę polecam jej przeczytanie. Mimo że książka została wydana w 1999 roku, tematy są nadal na czasie i zostaną aktualne jeszcze przez wiele lat. Ten wpis stanowi krótkie streszczenie książki – wyciągam wszystkie wskazówki z ramek i je opisuję. Ale na początek wstęp - kim w ogóle jest pragmatyczny programista.
Nie ma sensu pisać oprogramowania, jeśli nie robi się tego dobrze. To nie jest kwestia napisania software'u – to sztuka jego napisania.
Nigdy nie przełączaj się na autopilota.
Przyznaj się do winy, jeśli coś schrzaniłeś. Zamiast zwalać winę na innych, zaproponuj dobry “plan B”. Nie mów “nie da się tego zrobić” – powiedz, co można zrobić zamiast tego.
Badacze odkryli pewne fascynujące zjawisko. Jeśli w jakimś budynku w mieście zostanie wybita szyba, a właściciel nie naprawi tego, po jakimś czasie zostanie zbita kolejna szyba. Pojawią się też dodatki malowane sprayem. Ładny budynek stanie się ruderą.[^1]
Podobnie jest z kodem. Wchodząc do projektu, który może się pochwalić pięknym kodem, starasz się utrzymać ten stan. Po prostu nie chcesz być pierwszym, który coś popsujesz. Jeśli projekt wita Cię na “dzień dobry” czymś odrażającym, będzie Ci naprawdę ciężko pisać dobry kod. Dlatego nigdy nie zostawiaj wybitych szyb – naprawiaj, gdy tylko wykryjesz.
Wykaż się inicjatywą. Aby coś wykonać trzeba najczęściej załatwić wiele denerwujących formalności – wskazanie rzeczy do wykonania, papierki, budżet do zaakceptowania. Spróbuj tak nakręcić osoby decyzyjne, aby pomysł im się podobał. Niech poczują, że to w gruncie rzeczy ich pomysł, naprowadzając ich na Twoje rozwiązanie.
Nie bądź tak bardzo zajęty szczegółami, że przestaniesz dostrzegać ogół problemu. Kontroluj, co się dzieje wokół Ciebie.
Oprogramowanie nigdy nie będzie perfekcyjne – nie ma szansy na to. Musi być jednak good enough. Aby to osiągnąć, włącz użytkowników w proces osiągania kompromisów. Użytkownicy wskażą Ci, które rzeczy są krytyczne, a które byłyby miłym, lecz niekoniecznym usprawnieniem. Ostatecznie, użytkownicy wolą korzystać z dobrego oprogramowania dziś, niż perfekcyjnego za rok.
Wiedza informatyczna bardzo szybko się dezaktualizuje. Jeśli nie chcesz wylecieć z branży, musisz stale się poszerzać wiedzę.
Stale ucz się. Im więcej różnych rzeczy wiesz, tym lepiej. Jeśli przyjdzie Ci zmienić główną technologię, będzie to mniej bolesne. Dobrze być pierwszym, dlatego inwestuj trochę czasu w nowości. Ale bez przesady – sprawdzone technologie też są ważne.
Skoro każą… Na rok 2010 postanowiłem wybrać C++, ponieważ bardzo go nie lubię. Mam nadzieję nauczyć się go trochę bardziej, aby wiedzieć dokładnie dlaczego jest słaby. :-) Na rok 2011 już wybrałem Ruby.
Nie wierz wszystkiemu, co słyszysz. Uważaj na adwokatów, którzy wmówią Ci, że ich rozwiązanie jest najlepsze. Uważaj na marketing - pierwsza pozycja w Google nie musi oznaczać, że jest to najlepsze trafienie. Promowana przez księgarnię książka wcale nie musi być najlepsza.
Bądź komunikatywny. Nie wystarczy sam pomysł - trzeba umieć go przekazać.
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.
Buduj klasy o małej odpowiedzialności. Buduj większe rzeczy z mniejszych. Jeśli Twojego kodu nie będzie łatwo użyć ponownie[^2], inni programiści użyją ^C i ^V, duplikując kod.
Brak zależności pomiędzy niepowiązanymi rzeczami pragmatyczni programiści nazywają ortogalnością (orthogonality). Jako nieortogonalny system autorzy książki przywołują helikopter. Aby zmodyfikować jeden parametr lotu, należy wykonać kilka różnych czynności, ponieważ zmiana pojedynczego parametru ma wpływ na inne parametry[^3].
Systemami nieortogonalnymi trudno jest sterować. Projektuj więc systemy ortogonalne, bo nie warto utrudniać sobie (i innym) życia, gdy przyjdzie utrzymywać system przez następną dekadę. Praca z systemami ortogonalnymi jest bardziej produktywna i niesie z sobą mniejsze ryzyko. Oddzielaj logikę od wyglądu, dziel system na warstwy i przemyśl programowanie aspektowe.
Środowisko, w którym pracujemy jest zmienne. Nie można być pewnym, czy decyzja podjęta dzisiaj będzie sensowna jutro. Jeśli wymagania mówią o tym, że administrator w serwisie ogłoszeniowym zatwierdza tylko ogłoszenia motoryzacyjne, nie wierz temu do końca. Na pewno przyjdzie dzień, w którym dojdzie nowe wymaganie, w którym administrator zatwierdza również inny typ ogłoszeń. Może też dojść nowa rola, np. moderator – co wtedy zrobisz? W kodzie definiuj abstrakcje, zaś szczegóły definiuj w metadanych.
Każdą zmianę powinno się dać łatwo wycofać. Dlatego też korzystaj z repozytorium kodu oraz przyjmij zasadę, że jeden commit dotyczy tylko jednego zadania.
Wyobraź sobie, że obsługujesz działo balistyczne i musisz trafić w pewne miejsce. Znasz swoje swoje współrzędne, jak i celu. Problemem jednak jest to, że jest ciemna noc i nie widzisz w ogóle celu. Ażeby idealnie w niego trafić, mógłbyś analitycznie obliczyć kąt, uwzględnić kierunek wiatru, masę pocisku i jego aerodynamikę, itd. Nie jest to niestety ani łatwe, ani szybkie. Lepiej użyć amunicji smugowej, która ma to do siebie, że idealnie widać, dokąd dolatuje. Wykonasz kilka korekt i już trafiasz w cel.
Podobnie jest z projektami informatycznymi. System, który trzeba wykonać to abstrakcyjny twór w głowach użytkowników i programistów. Nie można go dotknąć, obejrzeć, ani używać. Zespół projektowy mógłby stworzyć cały system i zaprezentować go jako w pełni działający za pół roku… by okazało się, że system jest do niczego w oczach użytkowników. To jest praca na oślep w sztucznych warunkach. Używaj amunicji smugowej, tzn. często konsultuj się z użytkownikami systemu. Wdróż serwer continuous integration, ażeby najnowsza wersja aplikacji była zawsze dostępna.
Czasami nawet amunicja smugowa może nie działać – jeśli w ogóle nie wiesz, co jest celem. Wtedy można przemyśleć zbudowanie prototypu. Należy jednak pamiętać, że prototypu w żadnym wypadku się nie deployuje. Właściwy system budowany jest od nowa na bazie doświadczeń z budowania prototypu.
Jeśli istnieje niebezpieczeństwo, że management źle zrozumie ideę prototypu i Twój zespół zostanie zmuszony do jego wdrożenia, lepiej od początku budować solidny system z pomocą amunicji smugowej.
Każdy język programowania ogranicza. Rozwiązanie problemu w Lispie będzie inaczej wyglądać od rozwiązania w językach podobnych do C++. Jeśli rozwiązanie mogłoby być lepsze dzięki wprowadzeniu mini-języka bliskiego domenie problemu – nie ograniczaj się. Nie tylko kod w takim języku będzie czytelniejszy. Błędy kompilacji mogą być też lepsze – zamiast syntax error: undeclared identifier możesz wypisać dostępne polecenia, czy parametry. Możesz też rozszerzyć istniejący język, np. poprzez dodanie własnych poleceń pomiędzy kod języka C.
Istnieją narzędzia do budowania prostych języków programowania – lex, yacc, bison, javaCC.
Cokolwiek estymujesz, użyj dobrej jednostki. Jeśli powiesz, że zrobisz coś w 30 dni, to około 32 dnia klient będzie wywierał na Ciebie coraz większą presję. Gdybyś tylko powiedział o czterech tygodniach, miałbyś jeszcze tydzień spokoju. Używając mniejszej jednostki zwiększasz dokładność estymacji. Oto jakich jednostek sugerują używać autorzy ze względu na przewidywany czas trwania:
| Duration | Quote estimate in | | - | - | 1-15 days | days | | 3-8 weeks | weeks | | 8-30 weeks | months | | 30+ weeks | think hard before giving an estimate |
W rozdziale zostało zapisywanych dużo innych rad odnośnie właściwego estymowania.
Plan jest częścią projektu – niech jest zawsze aktualny. Jeśli plan będzie cały czas na bieżąco z projektem, po każdej iteracji pojawiać się będzie coraz lepsza estymacja czasu zakończenia projektu.
Każda wiedza powinna być zapisana w czystym tekście. Ciężko czytać binarne formaty bez dodatkowych programów. Nie jest łatwo pokazać czytelnego diffa takiego pliku w przeglądarce repozytorium.
Czysty tekst ma moc, ponieważ łatwo go czytać, porównywać i pisać skrypty go przetwarzające.
Używaj krótkich poleceń, zamiast długich sekwencji kliknięć w GUI. Porzuć Windowsa, jeśli jeszcze tego nie zrobiłeś. :-)
Wybierz edytor i stań się jego ekspertem. Używaj go do wszystkiego. Naucz się wszystkich skrótów klawiszowych, aby częste rzeczy wykonywać szybciej. Edytor musi być konfigurowalny, rozszerzalny (pluginy) i programowalny (proste makra).
Myślę, że w obecnych czasach pod słowo edytor można podciągnąć również IDE. Nasze komputery ciągną je już bez trudu, a oferowana funkcjonalność IntelliJ IDEA i NetBeansa jest coraz bardziej doskonała.
W obecnych czasach nie trzeba już o tym mówić.
Moje osobiste rady: jeśli stoisz przed wyborem serwera systemu kontroli wersji weź pod uwagę kilka czynników. To, że git jest super wcale nie oznacza, że jest najlepszym wyborem. Przed wybraniem serwera sprawdź, czy Twój serwer continuous integration integruje się z repozytorium. Przeglądarka kodu i aplikacja do core review również muszą to potrafić. Na CVS w ogóle nie patrz. Subversion na pewno jest bezpiecznym wyborem, mimo posiadania wielu wad. Mercurial będzie miał najprawdopodobniej oficjalne wsparcie w następnej wersji Atlassian Bamboo – nie trzeba będzie rezygnować z dobrodziejstw rozproszonych systemów kontroli wersji na rzecz SVN-a. Jeśli Twoje narzędzia potrafią rozmawiać z nowoczesnym gitem albo Mercurialem, nie wahaj się użyć któregoś z tych repozytoriów.
Debugowanie to rozwiązywanie problemu, nic więcej. Nie podchodź do tego zbyt emocjonalnie, jeśli odkryjesz, że błąd wprowadził Twój kolega. Po prostu napraw problem.
Nie panikuj podczas debugowania. Nie daj się pokonać presji, gdy release miał być wczoraj. Nie debuguj więc na szybko – znajdź i napraw prawdziwą przyczynę błędu.
Nim zaczniesz debugowanie, upewnij się, że projekt poprawnie się kompiluje i przechodzi testy. Napisz test jednostkowy, który ujawni błąd i działaj. Techniki pomocne w debugowaniu:
Autorzy opowiadają o koledze w pracy, który twierdził, że instrukcja systemowa select na platformie Solaris ma błąd. Nikt nie był w stanie przekonać go, że wszystko jest w porządku z select, mimo że innym wszystko działało. Po tygodniach pracy w końcu znalazł błąd. Oczywiście u siebie. „Select” isn’t broken!
Nie wierz w swoją nieomylność i nie zakładaj milcząco, że wszystko działa. Udowodnij w testach jednostkowych w jakim kontekście funkcjonalność działa, z jakimi danymi i w jakich warunkach brzegowych.
Przeczytaj mój wpis o testach jednostkowych.
Ponieważ cała wiedza jest w czystym tekście, łatwo ją czytać. Aby dokonywać automatycznych operacji na tej wiedzy trzeba jednak poznać jakiś język do manipulacji tekstem. Autorzy przywołują wiele różnych narzędzi i języków – od command line'owych sed i awk, po języki Perl i Python.
W tych czasach Perl odchodzi raczej do lamusa. Wspomniałbym o Ruby – zwłaszcza, że autorzy tej książki współtworzyli Programming Ruby: The Pragmatic Programmers (D.Thomas, A.Hunt, C.Fowler).
Nie warto pisać wielokrotnie tego samego – użyj istniejącego generatora kodu lub ewentualnie napisz własny. Generator nie musi generować wyłącznie kodu – XML, czysty tekst czy inne również.
Są dwa typy generatorów – pasywne i aktywne. Pasywny generator uruchamiany jest tylko raz – potem wygenerowany tekst żyje własnym życiem. Aktywny generator trzyma generowany kod zawsze up-to-date, uruchamiając się zawsze, gdy to potrzebne. Kod z aktywnego generatora najprawdopodobniej nie jest umieszczany w repozytorium kodu. Preferuj aktywne generatory, ponieważ podążają za DRY.
Pasywnych generatorów używaj do:
Aktywnych generatorów używaj do:
Nie istnieje idealne oprogramowanie. Nikomu przynajmniej nie udało się go napisać i nie łudź się, że będziesz pierwszy.
Każda klasa ma swój kontrakt, na który składają się:
Jeśli Twój język programowania ma natywne wsparcie dla kontraktów, to lepiej być nie może. Zobacz prosty przykład na Wikipedii. W przeciwnym wypadku sprawdzaj poprawność “ręcznie”. Możesz też użyć zewnętrznych narzędzi, które będą interpretowały specjalne komentarze w kodzie jako wyrażenia definiujące kontrakt.
try {
willSurelySucceed();
} catch (Exception ignored) {
// this never happens
}
Pragmatyczny programista nie zastanawia się, czy kiedykolwiek mogło by się stać niemożliwe. To tylko kwestia czasu, dlatego nie pisze takiego kodu. Wykrywaj i zgłaszaj błędy jak najwcześniej. Dobrze, jeśli do loga wpadnie informacja o szczegółach błędu. Jeśli nie da się programu uratować, zakończ jego pracę (crash early), aby nie wyrządzić większych szkód.
Już minęły te czasy, w których dziwiły asercje w kodzie produkcyjnym. Jeśli Twój język programowania pozwala włączać i wyłączać asercje – koniecznie je włącz. Jeśli asercje są zbyt mało ekspresywne, użyj asercji xUnit.[^5]
Wyjątki rzuca się w sytuacjach wyjątkowych i żadnych innych. Nigdy wyjątek nie powinien oznaczać zakończenia się operacji sukcesem.
Jeśli Twój język programowania nie obsługuje wyjątków, możesz spróbować go zhakować (widziałem makra C, które dawały try-catch). Niektóre języki mają z kolei error handlery. Można z nich skorzystać nawet jeśli język ma wbudowane mechanizmy wyjątków – np. w celu ominięcia brzydkiego kodu związanego z obsługą wyjątku RemoteException przy wołaniu zdalnych metod w Javie.
Ten, kto alokuje zasób jest odpowiedzialny za jego zwolnienie. Jeśli Twój język oferuje sekcję finally, jest to idealne miejsce do pewnego zwolnienia zasobu. W drugiej kolejności, jeśli Twój język oferuje destruktory, to zwolnij zasób tam – pamiętaj tylko usunąć obiekt.
Zwalniaj zasoby w odwrotnej kolejności do ich alokowania. Ponadto, jeśli w różnych miejscach kodu alokujesz te same zasoby, rób to wszędzie w tej samej kolejności. W przeciwnym wypadku ryzykujesz otrzymaniem deadlocka.
Good fences make good neighbors. Robert Frost, “Mending Wall”
Staraj się przestrzegać Prawa Demeter (Law of Demeter), w którym metoda obiektu może odwoływać się tylko do metod, które:
Z tym prawem wiąże się również inna zasada - zasada jednej odpowiedzialności (single responsibility principle) spisana przez wujka Boba.[^6]
No amount of genius can overcome a preoccupation with detail. Levy’s Eighth Law
Ta wskazówka wiąże się ze wskazówką 14 – używaj metadanych do definiowania szczegółów działania aplikacji. Typowe parametry to katalog instalacji, katalog domowy, język, itp. Najlepiej trzymać metadane w prostych plikach tekstowych odpowiednich dla platformy lub języka programowania. W Javie użyłbyś plików properties, a w systemie Windows plików *.ini.
Metadane nie tylko powinny być używane do prostych rzeczy, jak ustawienie katalogu domowego. Idź dalej – niech metadane decydują o wyglądzie okien, dostępnej funkcjonalności, wszystkim![^7] Plusem z tego płynącym jest zmuszenie Ciebie do wybrania lepszego podejścia. Podejścia, w którym Twój design będzie lepszy, bo moduły będą mniej z sobą powiązane[^8]. Podejścia, dzięki któremu otrzymasz aplikację w pełni elastyczną, z abstrakcyjnym designem i bez niepotrzebnych szczegółów.
Uniezależnij się od konkretnego dostawcy bazy danych. Stosuj dependency injection. Szczegóły definiuj deklaratywnie.
Rozrysuj sobie akcje na prostym diagramie i określ, które akcje mogą iść równolegle. To jeden z niewielu przypadków, gdy UML ma w ogóle szansę się do czegokolwiek przydać. Użyj activity diagram – gruba pozioma linia oznacza, że wszystkie akcje, które do niej dochodzą muszą się zakończyć.[^9]
Współbieżność w obecnych systemach jest faktem, dlatego Twoje aplikacje powinny działać w środowisku współbieżnym. Pamiętaj, że zmienne globalne oraz statyczne[^10] nie są bezpieczne dla wątków (thread safe). Oczywiste jest trzymanie stanu w polach obiektu, a nie klasy. Jeśli wykonanie akcji wymaga, aby coś wcześniej się wykonało – upewnij się, że rzeczywiście się to wykonało. Jeśli nie – poczekaj.
Zmiana widoku nigdy nie może mieć wpływu na model. Tylko takie rozwiązanie gwarantuje dobrą elastyczność.
Architektura trójwarstwowa została doceniona również poza środowiskiem pragmatycznych programistów :-)
Dobrze mieć wspólną tablicę (na mazaki), na której pojawiają się różne rzeczy związane z projektem. Zespół śledczych będzie miał tablicę, w centrum której jeden napisze Nowaker – zabójca czy niewinny. Wraz z uzyskiwaniem danych będą się tam pojawiać dodatkowe myśli (przypuszczenia), czy też odniesienia do dowodów i zeznań. Tablica jest punktem centralnym, dzięki niej członkowie zespołu mogą oddziaływać na siebie w sposób asynchroniczny. Te same zasady dotyczą tablicy w projekcie informatycznym.
Działa, nie tykać! – to moja najprostsza definicja programowania przez przypadek. Autorzy przywołują fajny przykład:
paint(g);
invalidate();
validate();
revalidate();
repaint();
paintImmediately(r);
Nic dodać, nic ująć. Oto kilka rad, jak programować rozmyślnie:
Jeśli jeszcze jej nie znasz, poczytaj o notacji dużego O i naucz się złożoności obliczeniowej podstawowych algorytmów.
Pisząc własne algorytmy zastanawiaj się nad szybkością algorytmu, jak i danymi. Jeśli dane nigdy nie będą zbyt duże, nie ma sensu implementować optymalnego algorytmu. Pamiętaj jednak, by algorytm był łatwo wymienialny na inny.
Unikaj przedwczesnej optymalizacji – jest to jedna z najlepszych rzeczy, które można zrobić, aby ubić projekt. Kod “optymalny” ciężko się czyta i nie przekazuje intencji. Jeśli już musisz optymalizować – sprawdź, czy rzeczywiście zoptymalizowałeś przy pomocy narzędzi profilujących. Jeśli nie ma zysku lub jest mniejszy od oczekiwań – wycofaj zmiany.
Refaktoryzacja to stałe polepszanie jakości kodu bez zmiany lub dodawania funkcjonalności. Boisz się zmieniać kod czegoś, co działa? W takim razie nie masz testów jednostkowych, które są podstawą refaktoryzacji. Zespół musi refaktoryzować codziennie. Warto postępować zgodnie z zasadą skauta-programisty – zawsze zostawiaj kod czystszy, niż go zastałeś. Refaktoryzuj małymi krokami – co każdy uruchamiaj testy jednostkowe, by upewnić się, że idziesz w dobrą stronę. Rozgrzebanie całego kodu najczęściej się nie udaje. (przeczytaj więcej w Refactoring to Patterns, Joshuy Kerievskiego)
Dobry design można poznać tak naprawdę po jednej rzeczy – że każdy fragment kodu idzie w bardzo łatwy sposób przetestować.
Jeśli nie przetestujesz swojego oprogramowania, zrobią to użytkownicy. Na obsługę zgłaszanych przez nich błędów stracisz o wiele więcej czasu, niż gdybyś sam je wykrył własnymi testami jednostkowymi. Testy jednostkowe muszą być częścią procesu budowania, a kod testów musi być łatwo dostępny.
Kreatory są super – kilkudziesięcioma kliknięciami potrafią wygenerować naprawdę pokaźny kod, który pisałbyś przez kilka godzin. Nim jednak umieścisz taki kod w repozytorium zastanów się, czy potrafisz go samemu od zera napisać. W przeciwnym wypadku polegniesz, gdy trzeba będzie coś dodać lub zmienić. Po prostu nie programuj przez przypadek (tip 44).
Pozyskiwanie wymagań kojarzy się z szczęśliwymi analitykami, którzy podnoszą wymagania z podłogi i umieszczają w koszyku. Nie jest to jednak takie proste – po wymagania trzeba kopać.
Tak jak rowerzysta nigdy nie zrozumie, po co mi w samochodzie silnik o mocy 225 KM, tak Ty nie zrozumiesz oczekiwań, jeśli nie wcielisz się w użytkownika systemu. Może to wymagać wielu godzin pracy z pracownikiem firmy. Dopiero wtedy zrozumiesz, jak działa firma, co jest wykonywane często (czyli jest ważne), a co prawie rzadko.
Wymagania powinny definiować, że produkt ma datę sprzedaży. Sposób reprezentacji daty to mało istotny szczegół z punktu widzenia wymagań. Część systemów z lat 80-tych przetrwała do roku 2000-go i padła ofiarą Y2K – wymaganiem była zapewne data z rokiem w reprezentacji dwucyfrowej, zamiast po prostu data.
Twój zespół musi używać spójnej konwencji nazewnictwa. Jeśli jedna rzecz ma dwa alternatywne pojęcia, wybierzcie jedno prawidłowe w ramach projektu. Utwórzcie słownik projektu – miejsce, w którym znajdzie się każde słowo z dziedziny problemu oraz jego wytłumaczenie.
Jeśli coś Ci nie wychodzi, spójrz na to z innej perspektywy. Może idziesz w całkowicie złym kierunku. Przeczytaj na Wikipedii, jak rozwiązano zagadkę węzła gordyjskiego.
Czasami widzisz, że moduł działa, ale masz przeczucie, że może się popsuć – nie ufasz temu modułowi. Nie ignoruj tego przeczucia, bo często jest ono prawdziwe. Upewnij się, że wszystko będzie działać dobrze, sprawdź drugi raz. Daj sobie czas. Rozpocznij kolejne zadanie, gdy będziesz gotowy.
The Landing Pilot is the Non-Handling Pilot until the decision altitude call, when the Handling Non-Landing Pilot hands the handling to the Non-Handling Landing Pilot, unless the latter calls go around, in which case the Handling Non-Landing Pilot continues handling and the Non-Handling Landing Pilot continues non-handling until the next call of land or go around as appropriate. British Airways memorandium, quoted in Pilot Magazine, Dec 1996
Nie popadaj w biurokrację – nie da się wszystkiego ująć w plan i spisać na papier. Zbyt duży formalizm jest Twoim wrogiem. Powyższy cytat to zapis zasad podczas lądowania samolotu. Przeczytasz 10 razy, a i tak nie zrozumiesz. Przeformułowanie jednego megazdania na kilka prostych zdań ułatwia nieznacznie zadanie. Narysowanie tabelki z trzema kolumnami (pierwsza z nazwą zdarzenia, np. decision altitude call, dwie kolumny per pilot) i kilkoma wierszami dopiero całkowicie rozwiązują sprawę. Zadanie domowe: opisz słownie sposób wiązania buta. :-)
Nie produkuj dokumentacji, która na nic się nie przyda. Jeśli dokumentacja jest potrzebna, daruj sobie szczegółów. Pozwól developerom myśleć.
Formalne metody delegują różne części procesu wytwarzania oprogramowania do różnych osób. Mamy analityków, projektantów, architektów, programistów, testerów i Bóg wie kogo jeszcze. Problem w tym, że jest to całkowicie sztuczny podział. Jak można zbudować architekturę aplikacji, jeśli nie dotyka się w ogóle kodu? Istnieją równolegle testerzy i programiści – to oznacza, że programista nie ma obowiązku pisania testów jednostkowych. Ostatecznie, jak programista ma zaimplementować dobrze przypadek użycia, jeśli słabo rozumie dziedzinę problemu?
Metody formalne powinny być narzędziem, nie religią. Pragmatyczny programista wyciąga najlepsze części z tych metod i tylko je stosuje. Tak je miesza, że powstaje własna metoda. Metodę tę cały czas kontroluje i ulepsza. Jest agile! Autorzy co prawda nie używają tego słowa, jednak książka została wydana w 1999 roku, zaś Agile Manifesto spisano w 2001 roku. Same zaś nazwiska autorów widnieją pod manifestem.
Nie wierz, że jakaś metoda formalna, reklamowana przez tę czy inną firmę, przyniesie sukces Twojemu projektowi. Sukces przynosi metoda, która pasuje idealnie do projektu. Metody pasującej do każdego projektu jeszcze nikt nie wymyślił.
Disclaimer. Tak, autorzy w tej wskazówce poprzez tool rozumieli metody formalne.
Pragmatyczne zespoły:
We wskazówce 58 napisałem, dlaczego podział na architektów, itd. jest zły. Teraz pora na dobre rozwiązanie – ludzi powinno się dzielić ze względu na funkcjonalność. Każdy członek zespołu ma swoje lepsze i gorsze strony – należy się tak podzielić, aby uwidocznić te dobre. Jedni lubią ROA i SOA, inni bazy danych, a jeszcze inni UI w Swingu. Nie chodzi o to, że “bazodanowcy” mają wyłączne prawo do grzebania w bazie. Muszą być po prostu osoby, które w tym temacie będą najbardziej kompetentne, bo im przyjdzie doglądać okolice bazy danych, encji, itp. To one będą, w razie czego, reagować na niedobre zmiany podczas przeglądu kodu[^11] oraz przekazywać wiedzę młodszym programistom.
Wszystko powinno być automatyczne. Ręczne procedury są ryzykowne, ponieważ człowiek nie jest maszyną – nie jest w stanie wykonać sekwencji operacji w dokładnie określony sposób. Raz kliknie za dużo, innym razem za mało, itd. Automatyczne muszą zwłaszcza być: testy, kompilacja, generatory kody i ciągłe buildy (w myśl continuous integration).
Testowanie jest tak ważne, że do tego tematu autorzy nawiązują najczęściej. Przy okazji tej wskazówki autorzy przypominają o kilku różnych poziomach testów – jednostkowych, integracyjnych, walidacji, wydajnościowych i usability. Na pewno znajdziesz dużo o każdym z tych testów w Internecie.
Skoro nie można napisać idealnego oprogramowania, nie ma też idealnych testów. Znajdź jakieś osoby, które będą próbowały sabotować kod w myśl zasady delete driven development[^12]. Jeśli po bezsensownej zmianie testy nadal przechodzą, coś jest nie tak – należy poprawić testy.
Jednym ze sposobów na mierzenie jakości testów jest mierzenie pokrycia kodu testami jednostkowymi. Wysokie procenty oznaczają zazwyczaj lepiej przetestowany system. Oczywiście można oszukiwać, pisząc puste testy, no ale nie o to chodzi. Code coverage to tylko wskazówka dla programisty, jakich miejsc kodu być może zapomniał przetestować.
Tak naprawdę, najważniejsze jest pokrycie przestrzeni wszystkich możliwych stanów, a nie całego kodu. Tego jednak nie da się technicznie wykonać – pozostaje biednemu programiście samemu o tym myśleć. A ma nie lada wyzwanie – sam musi pomyśleć, jakie przypadki są graniczne, podstawić dane uporządkowane, jak i nieuporządkowane itd. Więcej przeczytasz w moim artykule o testach jednostkowych.
Postępuj dokładnie w tej kolejności:
Dokumentacja powinna się znajdować bezpośrednio w kodzie. Nie obok kodu (komentarze), tylko w kodzie. Po to możemy nadawać nazwy metodom, klasom, itd. Pamiętaj, że jeszcze gorsze od bezsensownych nazw typu foo są mylące nazwy, Tak więc bądź precyzyjny, nazwa nie może sugerować rzeczy, których metoda nie robi.
Robert C. Martin w książce Clean Code idzie dalej – twierdzi, że komentarze są w znakomitej większości niepotrzebne. Jeśli jest potrzebny, to pewnie coś popsułeś i musisz się wytłumaczyć z tego, co popsułeś. Zamiast tracić czas na tłumaczenie się, po prostu napraw to.
Miarą zadowolenia klientów jest to, jak dobrze aplikacja spełnia oczekiwania klienta. Choćby system był idealny (obiektywnie i technicznie), ale klient oczekiwał czegoś innego – jesteś przegrany. Poznaj oczekiwania klienta i je zrozum. Jeśli są niewykonalne, wytłumacz i zaproponuj coś równie dobrego, lecz wykonalnego. Wskazówki 15, 51 i 52 też o tym wspominają.
Podstawą jest wpasować się w oczekiwania klienta. Jeśli Ci się to uda, idź dalej i zaskocz czymś klienta. Bądź kreatywny i wymyśl coś małego, fajnego. Lista przykładowych rzeczy:
Pisz taki kod, aby być dumnym z niego. Podpisz się nazwiskiem pod nim. Nie uciekaj przed odpowiedzialnością – jeśli coś popsujesz, przyjmij to na klatę. To przecież Ty wprowadzeniem błędu przysporzyłeś zespołowi problemów.
Kod musi mieć właściciela (właścicieli). Jeśli kod jest anonimowy, każdy umywa ręce i mówi to nie ja, to przez tamtego zrobiłem ten błąd, itd. To prowadzi z kolei do coraz większej ilości słabego, anonimowego kodu.
Moje postanowienie, jedno: być pragmatycznym programistą. :-)
Jeśli uważasz, że warto coś dodać do którejś wskazówki, zostaw komentarz.
[^1]: Broken Windows Syndrome [^2]: ponowne użycie - ale to głupio brzmi po polsku ;-) [^3]: Nie potrafię niestety przywołać dokładniej przykładu z helikopterem, ponieważ moja nieznajomość angielskiego i tematyki lotniczej nie pozwala mi przetłumaczyć nazw urządzeń w helikopterze. [^4]: W Javie Math.abs(Integer.MIN_VALUE) < 0 - fail, powinien lecieć wyjątek. [^5]: Asercje JUnit działają pomimo wyłączenia asercji w wirtualnej maszynie, dlatego najlepiej ich używać. [^6]: Robert C. Martin, autor książek Clean Code: A Handbook of Agile Software Craftsmanship i jej kontynuacji Agile Software Development: Principles, Patterns, and Practices, jak i kilku innych. [^7]: Jak u Kononowicza. [^8]: Cytat z oryginału: It forces you to decouple your design. [^9]: Osobiście uważam UML za kolejną notację, którą będą umieć czytać tylko eksperci. Sam UML mógłby pójść do piachu – jedyna sensowna czynność to modelowanie przypadków użycia, lecz można to zrobić poza UML-em. [^10]: Metody, których zmienne statyczne zmieniają stan są niebezpieczne wątkowo. To znaczy, że uruchomienie jednocześnie takiej metody w dwóch osobnych wątkach może dać nieprzewidywalne wyniki. Przykładem takiej funkcji jest strtok ze standardowej biblioteki języka C. [^11]: Code review – popatrz czym jest na krótkim filmiku na przykładzie narzędzia Atlassian Crucible. [^12]: Delete driven development :-)