16 lis 2009

14 lis 2009

TDD a czysty kod

W miarę ostatnio na blogu Holistycznie o inżynierii oprogramowania pojawił się post o TDD i czystym kodzie. Podstawowym założeniem było, że TDD nie ma wiele wspólnego z czystym kodem, że łączenie tych dwóch rzeczy przez Roberta C. Martina zwanego czasem Wujem Bobem jest manipulacją i próbą wylansowania się. I choć Wuj faktycznie jest medialny i ma parcie na LCD, to myślę, że autor jednak trochę się tu zagalopował. Pod koniec autor zamieścił co prawda wytłumaczenie o co mu chodziło w poście, ale cyniczne żarty w jego treści spowodowały, że czyta się go jak tyradę przeciw testom jednostkowym. Co więcej wskazującą na brak praktycznego doświadczenia w takim tworzeniu oprogramowania przez jej autora, więc niestety dla praktyki tej krzywdzącą.

A że internet to wolne medium, zdecydowałem się zamieścić własny pogląd na TDD i jego rolę w zapewnianiu czystości kodu, tyle, że bazującą na paroletnim doświadczeniu tworzenia w ten sposób oprogramowania.

Czysty kod to kod czytelny. Wprowadzony przez Donalda Knuth'a termin/idea/paradygmat Literate Programming oznacza prawie to samo. Kod powinien dać się czytać jak literaturę (na tyle na ile to możliwe), powinien być jak najbardziej przejrzysty dla programisty pracującego z nim. Tylko wtedy będzie można zapewnić niski poziom błędów (wiemy co robi i rozumiemy jak to robi) i wysoką utrzymywalność (kod zrozumiały dla każdego kto go czyta). To oczywiście platoński ideał kodu, ale właśnie czysty kod, tak jak opisuje go Uncle Bob w książce Clean Code zdaje się być temu ideałowi najbliższy (przynajmniej w javie). To kod, który mówi dokładnie co robi. W metodach wyższego poziomu (zwykle o szerszym dostępie) jest kod bardziej odpowiadający na pytanie "co?" a czym niżej, tym bardziej idzie w kierunku "jak?".

Czytelny kod rzadko powstaje przy pierwszym podejściu. To naturalne, że mamy jakiś pomysł - nasze przemyślenia i doświadczenie podpowiadają nam rozwiązanie i jest ono zwykle w mało czytelnej formie. Czasem jest to paręnaście/parędziesiąt linii odzwierciedlających nasz pomysł na rozwiązanie w formie jakiegoś algorytmu. I to jest nasze pole prób i błędów. Niestety bardzo często kod pozostaje na tym etapie. Tzn. działa, realizuje to o czym akurat myślał jego autor, ale nie komunikuje jasno do czego służy. Potem dodajemy kolejne metody i klasy współpracujące z tą pierwszą i tak powstaje nieczytelna aplikacja o rozmytych odpowiedzialnościach klas i metod.

Gdyby jednak nie pozostać na poziomie poligonu, tylko spojrzeć na działający kod pod kątem jego czytelności i nadać mu zrozumiałą formę, byłby on nie tylko poprawny, ale też utrzymywalny. Gdy metody mają 2-3 linijki (czasem może 5-7) dużo łatwiej o lepsze ich rozmieszczenie, o przydzielenie odpowiednim klasom, o naturalne wyłanianie się wzorców projektowych (np. strategia, dekorator czy mediator to wzorce, które same wyłaniają się przy poprawnym rozłożeniu odpowiedzialności). Nie mówię, że pojawią się nawet jak ich nie znamy (w końcu to nie czary), ale dostrzeżenie miejsca dla nich jest dużo prostsze. Stąd też refaktoryzacja jest zupełnie nieodzowną praktyką programistyczną. Uważam, że niemożliwe jest napisanie kodu (o realnej złożoności, nie akademickie przykłady) od razu"na czysto". Najpierw realizujemy więc wymaganie, a potem za pomocą refaktoryzacji (i niskopoziomowej i do wzorców projektowych) dochodzimy do czystego kodu.

Ale zaraz, kod który piszemy realizując jakąś funkcjonalność rzadko żyje sam sobie. Prawie zawsze wchodzi w interakcję z innymi komponentami. Metody nowych klas będą wołane z zewnątrz, same obiekty tworzone w różnych miejscach i na różne potrzeby. Jak więc stworzyć strukturę klasy tak, by była łatwa w użyciu? Jak osiągnąć jednoznaczność wywołań? Jak zapewnić, że nasz kod będzie wygodny w wykorzystaniu z zewnątrz? Hm. Najprościej zasymulować wykorzystanie. Wiemy czego chcemy od obiektów jakiejś klasy, więc to po prostu napiszmy. W ten sposób powstają przykłady.
Piszemy więc np.
email
  .withTitle(someTitle)
  .withBody(someTextInTheBody)
  .shouldBeSentTo(recipient)
  .withReplyToAddress(replyTo);
email.send();
Jakie są szanse utworzenia takiego API za pierwszym razem, bez tworzenia takiego przykładu? Oczywiście można pozostać przy setterach i getterach, ale o ile łatwiej taki kod czytać i zrozumieć o co autorowi chodziło!
A skoro już piszemy takie przykłady, to dlaczego nie napisać tego najpierw. Najlepiej rozpocząć myślenie o klasie właśnie przez pryzmat jej wykorzystania. Każde środowisko programistyczne pozwala automatycznie wygenerować puste definicje metod, więc kod kompiluje się od razu. A jak będziemy mieli gotowy szkielet klasy, wypełniamy ją kodem. W ten właśnie sposób prowadzimy projektowanie przykładami. Dzięki temu nasze klasy mają większe szanse na właściwe rozmieszczenie odpowiedzialności między nimi (bo przykłady definiują właśnie odpowiedzialności) a do tego metody publiczne od razu mają zrozumiałe nazwy. A po zakodowaniu ich "wnętrzności" jeszcze trochę refaktorowania i również wewnętrzna struktura klas będzie przejrzysta.

Teraz mamy już kod, który powstał dzięki przykładom. No to przykłady można wywalić i pisać nowe... Zaraz! A może za miesiąc będzie trzeba trochę to przerobić... Może będzie trzeba do emaila dodać pole BCC albo formatowanie HTML. To lepiej te przykłady zostawmy - przydadzą się za miesiąc. Dodamy nowe wywołania, zmienimy nazwy tych, które zmienią znaczenie (tak, to też refaktoryzacja). Takie przykłady będące na bieżąco są do tego dość dobrą dokumentacją tego co kod potrafi i jak to osiągnąć. Może za miesiąc do zespołu dojdzie nowy programista i z satysfakcją powiemy mu RTFE ;)

To jak już mamy te przykłady i utrzymujemy je na bieżąco, jak już definiują nam one wywołania, to dlaczego nie sprawdzać przy okazji czy te wywołania poprawnie działają? Wtedy gdy tylko ktoś zmieni implementację bez modyfikacji przykładu, ten przestanie odzwierciedlać rzeczywistość i automatycznie zgłosi to programiście. Co więcej pozwolą nam one weryfikować świadome zmiany w kodzie w przyszłości. W końcu gdy tylko będzie trzeba zmienić istniejącą funkcjonalność - powiedzmy wszystkich odbiorców maila z tej samej domeny umieścić w CC a z poza w BCC - przykłady nie spełniające tego wymagania zgłoszą nam błędy. Wystarczy je wtedy zaktualizować i kod dalej jest spójny. Albo lepiej - najpierw napisać przykład, który weźmie pod uwagę nowe zmiany, zweryfikuje poprawność ich implementacji, a potem już pewni poprawności uaktualnimy niedziałające przykłady.

I tak nasze przykłady stają się automatycznymi testami. W większości jednostkowymi, częściowo akceptacyjnymi, gdzieniegdzie integracyjnymi. W połączeniu ze środowiskiem ciągłej integracji dają z niczym nie porównaną pewność poprawności oprogramowania. Przez cały czas.

W ten sposób od kodu spisanego jak notatka, draft zawierający co prawda meritum, ale w niezrozumiałej dla nikogo oprócz autora formie, dochodzimy do programistycznego dzieła literackiego. Od cowboy-coding do metodycznego rozwijania oprogramowania. Od amatorskiego "żeby działało" do profesjonalnej pewności poprawności. To jest właśnie clean code.

Mówiąc krótko: czysty kod jest wynikiem poprawnego stosowania TDD - definicji przykładu, potem implementacji a na koniec refaktoryzacji.  TDD pewnie nie jest jedynym sposobem uzyskania tego efektu, ale nie spotkałem się przykładami dużych projektów o czystym kodzie tworzonych bez TDD.

13 lis 2009

Studio Pragmatists

Ostatnio dość mało tu treści, a dużo reklam :) Postaram się, żeby był to ostatni raz.

Jako Pragmatists postanowiliśmy (wzorem najlepszych) zrobić firmowy blog, na którym będziemy umieszczać aktualności dotyczące naszej działalności, ale co ważniejsze również techniczne artykuły i refleksje dotyczące naszej pracy. Blog nazywa się Studio Pragmatists (http://blog.pragmatists.pl).

Pierwszy wpis dotyczy organizowanego przez nas szkolenia, a dokładniej tego, że przed nami ostatni tydzień rejestracji.

5 lis 2009

Zwinne szkolenie taniej :)

Właśnie się zorientowaliśmy, że szkolenia nie podlegają opodatkowaniu VAT, więc szkolenie nagle stało się tańsze i kosztuje okrągłe 2^10zł :)

Mamy już zarezerwowaną prawie połowę miejsc, więc wszystkim chętnym polecam się pospieszyć!

1 lis 2009

Ci, którzy odeszli

Te święta to dobra okazja nie tylko na refleksję nad sobą, swoim życiem i jego celowością, nad własną rodziną i innym, którym zawdzięczamy to kim jesteśmy, gdzie żyjemy, czy jakie mamy warunki życia. Dla nas - ludzi zajmujących się oprogramowaniem - to może też być czas na refleksję nad tymi, dzięki którym jesteśmy tu gdzie jesteśmy - czas na chwilę wspomnienia wielkich matematyków i informatyków, którzy swoje zawodowe życie (a często i prywatne) oddali temu, co dziś jest naszym zawodem i naszymi narzędziami. By to co kiedyś nieosiągalne i skrajnie trudne mogło dla nas być codziennością.

Kiedyś już polecałem poniższy film, tym razem polecam przerzucić dokładnie do 74 minuty i oddać dziś tym właśnie ludziom chwilę.