Damian Nowak is a CEO at Virtkick. He's a Ruby coder, an Arch Linux hacker, and drinks good beer.

Jak być pragmatycznym programistą wg Andrewa Hunta i Davida ThomasaSeptember 2010

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.

Pragmatyczny programista

Chapter 1: A pragmatic philosophy

Tip 1: Care about your craft.

Nie ma sensu pisać oprogramowania, jeśli nie robi się tego dobrze. To nie jest kwestia napisania software'u – to sztuka jego napisania.

Tip 2: Think! About your work.

Nigdy nie przełączaj się na autopilota.

Tip 3: Provide options, don’t make lame excuses.

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.

Tip 4: Don’t live with broken windows.

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.

Tip 5: Be a catalyst for change.

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.

Tip 6: Remember the big picture.

Nie bądź tak bardzo zajęty szczegółami, że przestaniesz dostrzegać ogół problemu. Kontroluj, co się dzieje wokół Ciebie.

Tip 7: Make quality a requirement issue.

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.

Tip 8: Invest regularly in your knowledge portfolio.

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.

Tip 9: Critically analyze what you read and hear.

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.

Tip 10: It’s both what you say and way you say it.

Bądź komunikatywny. Nie wystarczy sam pomysł - trzeba umieć go przekazać.

Chapter 2: A pragmatic approach

Tip 11: DRY - Don’t repeat yourself.

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

Tip 12: Make it easy to reuse.

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.

Tip 13: Eliminate effects between unrelated things.

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.

Tip 14: There are no final decisions.

Ś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.

Tip 15: Use tracer bullets to find the target.

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.

Tip 16: Prototype to learn.

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.

Tip 17: Program close to the problem domain.

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.

Tip 18: Estimate to avoid surprises.

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.

Tip 19: Iterate the schedule with the code.

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.

Chapter 3: The basic tools

Tip 20: Keep knowledge in plain text.

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.

Tip 21: Use the power of command shells.

Używaj krótkich poleceń, zamiast długich sekwencji kliknięć w GUI. Porzuć Windowsa, jeśli jeszcze tego nie zrobiłeś. :-)

Tip 22: Use a single editor well.

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.

Tip 23: Always use source code control.

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.

Tip 24: Fix the problem, not the blame.

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.

Tip 25: Don’t panic when debugging.

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:

Tip 26: „Select” isn’t broken.

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!

Tip 27: Don’t assume it - prove it.

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.

Tip 28: Learn a text manipulation language.

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).

Tip 29: Write code that writes code.

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:

Chapter 4: Pragmatic paranoia

Tip 30: You can’t write perfect software.

Nie istnieje idealne oprogramowanie. Nikomu przynajmniej nie udało się go napisać i nie łudź się, że będziesz pierwszy.

Tip 31: Design with contracts.

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.

Tip 32: Crash early, Tip 33: If it can’t happen, use assertions to ensure that it won’t.

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]

Tip 34: Use exceptions for exceptional problems.

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.

Tip 35: Finish what you start.

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.

Chapter 5: Bend, or break

Tip 36: Minimize coupling between modules.

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]

Tip 37: Configure, don’t integrate.

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.

Tip 38: Put abstractions in code, details in metadata.

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.

Tip 39: Analyze workflow to improve concurrency.

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]

Tip 40: Design using services.

Tip 41: Always design for concurrency.

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.

Tip 42: Separate views from models.

Zmiana widoku nigdy nie może mieć wpływu na model. Tylko takie rozwiązanie gwarantuje dobrą elastyczność.

architektura trójwarstwowa

Architektura trójwarstwowa została doceniona również poza środowiskiem pragmatycznych programistów :-)

Tip 43: Use blackboards to coordinate workflow.

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.

Chapter 6: While you are coding

Tip 44: Don’t program by coincidence.

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:

Tip 45: Estimate the order of your algorithms.

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.

Tip 46: Test your estimates.

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.

Tip 47: Refactor early, refactor often.

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)

Tip 48: Design to test.

Dobry design można poznać tak naprawdę po jednej rzeczy – że każdy fragment kodu idzie w bardzo łatwy sposób przetestować.

Tip 49: Test your software, or your users will.

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.

Tip 50: Don’t use wizard code you don’t understand.

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).

Chapter 7: Before the project

Tip 51: Don’t gather requirements - dig for them.

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ć.

Tip 52: Work with a user to think like a user.

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.

Tip 53: Abstractions live longer than details.

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.

Tip 54: Use a project glossary.

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.

Tip 55: Don’t think outside the box - find the box.

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.

Tip 56: Listen to nagging doubts – start when you are ready.

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.

Tip 57: Some things are better done than described.

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ć.

Tip 58: Don’t be a slave to formal methods.

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.

Tip 59: Expensive tools don’t produce better designs.

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.

Chapter 8: Pragmatic projects

Pragmatyczne zespoły:

Tip 60: Organize teams around functionality.

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.

Tip 61: Don’t use manual procedures.

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).

Tip 62: Test early. Test often. Test automatically, Tip 63: Coding ain’t done ‘til all the tests run.

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.

Tip 64: Use saboteurs to test your testing.

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.

Tip 65: Test state coverage, not code coverage.

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.

Tip 66: Find bugs once.

Postępuj dokładnie w tej kolejności:

  1. Znalezienie błędu.
  2. Napisanie testu jednostkowego, który uwidacznia błąd. Testy nie przechodzą.
  3. Naprawa błędu.
  4. Ten konkretny błąd już nigdy nie wróci. :-)

Tip 67: English is just a programming language. Polski też! Tip 68: Build documentation in, don’t bolt it on.

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.

Tip 69: Gently exceed your users’ expectations.

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:

Tip 70: Sign your work.

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.

Podsumowanie

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 :-)

Damian Nowak
CEO & Ruby Developer