poniedziałek, 9 listopada 2009

Czysty kod to przetestowany kod?

Clean Code to skądinąd szczytna idea, na fali której kilka osób chce się wylansować ostatnimi czasy. Przyjrzymy się jej dzisiaj nieco bliżej aby sprawdzić czy nie kryje się za nią przypadkiem merytoryczna próżnia...

Ewangelizatorem tej idei jest nasz ulubiony Wujek Dobra Rada - Uncle Bob. Polecam wszystkim jego prezentację pt Craftsmanship and Ethics.

Uncle B z pasją opowiada o profesji i etyce w naszej branży. Właściwie to przekonuje, że już najwyższy czas aby wykrystalizowały się one bo póki co jeszcze się ich nie dorobiliśmy. Tutaj zgadzam się z nim bez 2 zdań. Metodyka byle-jak-byle-działało-ale-miało-zaokrąglone-rogi-na-gui to aktualnie standard.

Jako że są to idee bardzo mi bliskie to pierwsza część prezentacji jest na prawdę budująca. Dalej Uncje B twierdzi, że drogą do profesjonalizacji i nieskazitelnego kodu jest Test Driven Development. "Nie wolno Ci napisać ani linijki produkcyjnego kodu bez napisania uprzednio testu!" grzmi wujaszek! Kropka.

Postępuj tak a w nagrodzę po śmierci będą czekać na Ciebie w niebie zastępy dziewic - do tego nimfomanek rozumiejących maszynowy kod w reprezentacji binarnej. Marzenia...



...ale chyba nie będzie tak łatwo.

TDD owszem jest techniką, która ma swoje przeznaczenie i zastosowania ale nie dajmy się zwieść, że rozwiązuje wszystkie problemy. Co najwyżej niektóre.

Sensowna krytyka tego typu rozumowania znajduje się w typ poście: It's OK Not to Write Unit Tests. Wujkowi Dobra Rada chyba tylko wydaje się, że rozprawił się z adwersarzem przy pomocy racjonalnych argumentów: Excuse me sir, What Planet is this?

O ile w pewnych sytuacjach testy mogą "sterować" developmentem to nie zapominajmy o technikach, które rzeczywiście prowadzą do clean code.

Co nam po nawet 100% pokryciu kodu testami jeżeli kod będzie jedną wielką kupą...
Działającą nawet perfekcyjnie, ale wciąż jedną wielką kupą... [wstawić_swoją_ulubioną_metaforę]

Refaktoryzacja to podstawa? Co nam po wsparciu dla refaktoryzacji dzięki pokryciu kodu testami, które zwalniają nas z napięcia towarzyszącemu zmianom skoro... nie mamy pojęcia jak refaktoryzować ani w ogóle, w którą stronę refaktoryzować?

Do tego właśnie zmierzam... TDD nie daje żadnych wskazówek jakich technik developerskich użyć aby osiągnąć upragniony Clean Code. To oczywiste, że nie daje wskazówek, bo nie leży to w "domenie" TDD. Ale trzeba to sobie uświadomić i nie dajmy sobie wcisnąć kitu, że testy jednostkowe rozwiązują problem - są po prostu niewystarczające. Pomagają rozwiązać - ale rozwiązanie właściwe leży chyba gdzie indziej.

Czysty kod, aby powstał musi być wypracowany poprzez pewien wysiłek umysłowy. Nie wystarczy dorwać się do klawiatury i radośnie stukać (o tym na koniec). Na ten proces musi składać się rzetelna analiza problemu, projekt i dopiero implementacja. Nie mam tu oczywiście na myśli metodyki Waterfall, ale po prostu pewnej pracy, którą trzeba wykonać - nawet w umyśle jednej osoby.

Dla każdego z tych etapów mamy zestaw sprawdzonych technik oraz narzędzi, które oczywiście nie gwarantują sukcesu, ale zwiększają prawdopodobieństwo powstania upragnionego Clean Code.

Na poziomie analizy możemy posłużyć się Archetypami Modeli Biznesowych. Są to skatalogowane gotowe rozwiązania standardowych problemów biznesowych. Modele te zostały wypracowane przez wytrawnych analityków dzięki czemu można je dostosowywać do swych potrzeb. Są również zaprojektowane z myślą o rozbudowie modelu. Można by dużo o nich pisać, ale nic nie zastąpi przykładu. A przykład w jednym z najbliższych postów. Póki co zastanówmy się co nam po doskonale obłożonym testami modelu, który jest kupą...

Gdzieś na styku analizy i projektu mamy na przykład Domain Driven Design.

Zanim przejdziemy do poziomu projektu zatrzymajmy się przez chwilę na architekturze, będącej stylem, na podstawie którego będziemy projektować. Zastanówmy się co nam po doskonale obłożonym testami kod jeżeli architektura jest kupą...

Na poziomie projektu mamy zestaw technik np Object Oriented. GRASP i SOLID - pomagające w poprawnym interpretowaniu OO i uniknięciu proceduralnego kodu dziwacznie upchanego w klasy.
Nie zapominajmy o Wzorcach Projektowych. Kiedyś było o nich głośno, ale aktualnie młodzież woli sobie wyklikać/wygenerować trochę kodu. Ci bardziej Agile obłożą go paroma testami i jest git;)
Wzorce mogą pomagać u utrzymaniu czystej struktury kodu, ale przy ich "pomocy" można również bardzo skutecznie niepotrzebnie zagmatwać powinny pozostać prostsze - cała sztuka w wyważeniu. Ale tego oczywiście nie dowiemy się z marketingu TDD;)

Co do wzorców to planuję kiedyś stworzyć serię postów z realistycznymi (nieksiążkowymi) przykładami - ale jak zwykle czasu brak. Co do GRASP i SOLID to można znaleźć trochę dobrych przykładów - np ten.

Kiedy już wiemy co i jak to na poziomie implementacji możemy podeprzeć się techniką testów jednostkowych. Ale to czy kod będzie Clean zależy od pracy umysłowej włożonej również (albo przede wszystkim) wcześniej.

Konkludując: TDD ew. jako dodatek na drodze do Clen Code wujaszku;P


Aby uzupełnić całość warto wspomnieć o podejściu Specify First - dobry opis w poście TDD is not test-first. TDD is specify-first and test-last. Niektórzy twierdzą, że dopiero przy takim podejściu można mówić o prawdziwym TDD.
Chodzi generalnie o to, że pisząc najpierw test - nawet nie interfejs implementacji, a test - stawiamy się w roli kodu "klineta" danej klasy. Na pewno wpływa to pozytywnie na nasz design - bardzo pozytywnie.

Ale to znowu półśrodek. Technika owszem dobra, ale dla tych z nas, którzy są wciąż jeszcze na poziomie myślenia kodem.

Myślenie kodem to zupełnie normalna faza w rozwoju programisty i każdy przez nią przechodzi. Jeżeli jesteś początkujący to zapewne zauważyłeś, że gdy mówisz do kogoś ifami i pętlami to on krzywi się i zaczyna rysować jakieś prostokąty połączone strzałkami aby cokolwiek zrozumieć. Jeżeli bawisz się w to już kilka lat to zapewne irytują cię juniorzy, którzy mówią do Ciebie kodem. Normalna sprawa, abstrahowanie przychodzi z czasem ...

Zatem "Specify first" jest dobre na początek gdy świerzbią nas paluszki i chcemy jak najszybciej dorwać się do klawiatury i nastukać coś co można będzie za chwilę radośnie debugować. Analizowanie, modelowanie, projektowanie... to dobre dla zgredów.

Przy podejściu Top-Down rzeczywiście klasy "niższe" mogą być pokraczne bez wyobrażenie sobie jak będą używane. Ale top-down powinien być style analizowania problemu, natomiast projektowanie klas można przeprowadzić bottom-up dzięki czemu powstaną "komponenty" - niczym niezależne części "ruchomej machiny". Z czasem przychodzi umiejętność wyobrażania sobie i manipulowania obiektami "w pamięci" (własnej) bez potrzeby pisania czegokolwiek. Pomocy może być ew. ołówek, kartka i UML. Ktoś pamięta w ogóle co to jest UML? Nie to nie jest kolejna biblioteka do mockowania;P

Taaak.... kiedyś pewnie powstanie Unified Mocking Library - w sumie już nic mnie nie zdziwi w naszej branży...

//===============

Od razu uprzedzę ew. komentarze aby uniknąć niepotrzebnego niezrozumienia.
W tekście nie zajmuję stanowiska ZA albo PRZECIW testom jednostkowym. Moim celem było zwrócenie uwagi na fakt, że same testy niewiele wnoszą do koncepcji Clean Code. TDD jest techniką implementacyjną oraz od niedawna jest sprzedawany jako technika specyfikowania dla niecierpliwych/początkujących.

Moja teza jest następująca: aby osiągnąć Clean Code potrzeba dodatkowo kilku innych technik developerskich. Dopiero synteza całości może dać zadowalający rezultat.

I niestety trzeba się wysilić znacznie bardziej niż tylko dopisać więcej kodu - testującego. A podstawowe zagadnienia z zakresu inżynierii oprogramowania (Low Coupling, High Cohesion, IoC,...) przydają się również choćby do pisania kodu podatnego na testy - o wysokim poziomie testability.

22 komentarze:

Anonimowy pisze...

Brawo za artykuł! Nareszcie ktoś się odważył skrytykować tę durną koncepcję, która niczego nie wnosi, a wręcz przeszkadza w efektywnym tworzeniu oprogramowania.

Unknown pisze...

Cześć Sławku. Polecałbym Ci wziąć przekaz Wujka w szerszym kontekście (on współtworzył stan obecnej wiedzy nt OOP - zobacz listę publikacji na ObjectMentor), bo jego przekonanie do TDD wcale nie bierze się z niedocenienia zasad SOLID i GRASP (znajdziesz je zresztą szeroko omówione na stronach jego firmy). Przeciwnie, praktyka TDD w wersji polecanej przez Wujka bardzo sprzyja zachowaniu tych zasad. Niezbędny wysiłek umysłowy, o którym mówisz, przy TDD zachodzi niejako przy okazji - TDD stwarza dla niego bardzo wygodną ramę. Dodatkowo otrzymujesz cenny efekt uboczny, o który trudno nieraz w zaawansowanych metodach analityczno-projektowych: prostotę.

Sławek Sobótka pisze...

@oodventurer - masz rację, pod warunkiem, że jest to prezentowane całościowo - jako spójna "profesja".

Odnoszę się do Wujka w kontekście jego słynnej prezentacji gdzie mamy lansowanie samych testów - tak zresztą wygląda zdecydowana większość marketingu.

@Anonim - dziękuję za uznanie, ale nie miałem intencji krytykowania TDD. Chodziło mi o krytykę naiwnej interpretacji skupionej wokół SAMYCH TYLKO testów. Lansuje się to co proste/prostackie bo zawsze łatwiej sprzedać taki produkt:/

Unknown pisze...

@Sławek

W jednej prezentacji trudno by raczej było przedstawić całą profesję, na dodatek w atrakcyjny sposób. Wujek skupił się na TDD moim zdaniem słusznie, bo niewiele osób poza nim dostrzega podstawowe znaczenie tej techniki dla zachowania wielu pożądanych cech projektu.

Sławek Sobótka pisze...

@oodventurer - możesz polecić jakieś opracowanie rzetelnie prezentujące całość profesji? Nie musi być koniecznie produkcji Wujka.

Chętnie się zapoznam i streszczę na np blogu.

Unknown pisze...

@Sławek - niestety wątpię, aby istniało pojedyncze opracowanie zawierającego syntezę profesji głoszonej przez Wuja na wzór książki o DDD Mr Evansa. Moje informacje czerpię z jego bloga, publikacji na stronie objectmentor.com, oraz blogów i twórczości jego współpracowników z OM i przyjaciół - współtwórców Agile Manifesto (m.in. Martin Fowler, Kent Beck)

Anonimowy pisze...

Unit Testy można pisać, gdy wiadmomo, jaka ma być wyjście funkcji na dany zestaw wejść. Tego po pierwsze często nie wiadomo w przypadkach gdzy funkcja robi co kolwiek bardziej skomplikowanego, np. oblicza nastawy parametrów silnika w danych warunkach, a my często nie wiemy, jakie powinny byc dla danych wejść i właśnie chcemy, bo to funkcja obliczyła. Poza tym wiele błędów bierze się z tego, że program wykonał sekwencję operacji nieprzewidzianą przez programistę i tego też nie jesteśmy testami jednostkowymi w stanie opanować. Ogólnie testy jednostkowe nadają się do testowania jedynie najprostszych przypadków, które ze względu na swoją prostotę na ogół w ogóle (logicznie rzecz biorąc) nie wymagaja testowania.

pozdr.
Janusz

Łukasz Lenart pisze...

A mi podoba się idea, którą wyczytałem w pewnej książce, że testy jednostkowe pozwalają nam uczyć się .... <- wstawić cokolwiek: domeny biznesowej, działania algorytmu, zachowania klasy, etc.

Dzięki testom łatwo można poznać aplikację, jeśli nawet jej jeszcze nie ma ;-)

Unknown pisze...

@Janusz - w przekazie Wuja chodzi o TDD, a nie o same, wąsko rozumiane unit testy. Choć te ostatnie mogą być i owszem pożyteczne. Napisałem ostatnio unit test do właśnie tworzonej klasy, który trudno mi było doprowadzić do działania, dopóki nie stało się dla mnie jasne, że z tworzonej klasy powinienem wyodrębnić dodatkową klasę pomocniczą. Mój test przestał nagle być unit testem, bo przecież zaczął testować też tę drugą klasę. Zaczął być specyfikacją mini-komponentu. Klucz w tym, że bez posiadania testu nie wpadłem na pomysł wyodrębnienia dodatkowej klasy i gdybym testu nie miał, przypuszczalnie odnalazłbym błąd znacznie później.
Kwestia znanego wejścia i wyjścia jest bardzo istotna - ona formułuję specyfikację. Jeśli Twój system zależy od ściśle uporządkowanej sekwencji wywołań metod, to znaczy, że programujesz w stylu imperatywnym. Wszyscy, którzy docenili zalety stylu funkcyjnego będą Ci współczuć. Testy w naturalny sposób skłaniają do używania stylu funkcyjnego.

@Sławek - na wspomnianym przykładzie widać korzyści płynące ze stylu projektowego "continuous care" w porównianiu z "big design up-front". Pytanie, czy siadając i zastanawiając się nad projektem mojej klasy na początku uzyskałbym optymalny projekt szybciej? Na pewno musiałbym wtedy (starając się skupić) walczyć z licznymi rozproszeniami bardziej, niż w sytuacji, gdy zawalający test podpowiada mi wciąż, czego jeszcze brakuje w moim designie.

Sławek Sobótka pisze...

Co do dyskusji na temat stosować/nie stosować to wszystko zależy od cech osobniczych - w szczególności od metaprogramów kognitywnych.

http://www.racjonalista.pl/kk.php/s,4588/q,Preferowane.style.myslenia..metaprogramy

Zwróćcie uwagę na programy unikanie/możliwości oraz ew. ogólny/szczegółowy. Problem w tym, że team jest różnorodny...

Z uwagi na zależność od osobowości lepiej unikać tego typu dyskusji ponieważ nikogo nigdzie nie doprowadzi. Dlatego też podjąłem problem Craftsmanship jako takiego - czyli z czego powinna składać się nasza profesja oraz dlaczego nikt nie podejmuje trudnych problemów?

Rafał Jamróz pisze...

"Unit Testy można pisać, gdy wiadmomo, jaka ma być wyjście funkcji na dany zestaw wejść."

Zycze powodzenia programistom ktorzy proboja cokolwiek implementowac nie wiedzac jak ich funkcja ma sie zachowac NAWET dla przykladowego zestawu danych...

Krzysztof Jelski pisze...

"Konkludując: TDD ew. jako dodatek na drodze do Clen Code wujaszku;P"

No właśnie, wydaje się, że Uncle Bob mysli o tym zupełnie inaczej. Np. ostatnio napisał: "That makes me think that TDD was the true catalyst of change, and the bridge that conveyed our industry from then to now."

Dlaczego tak pisze? Udało się w mojej pracy zawodowej tworzyć kilka dużych systemów stosując TDD. Moje doświadczenie jest takie, że nie da się stosować TDD bez tego wszystkiego, co wymieniasz jako niezbędne dla tworzenia czystego kodu (SOLID, wzorce, IoC...). Tzn. że jeśli zespół zaczyna pracować w ten sposób, to albo musi szybko nadrobić zaległości w OOD, albo dość szybko przestanie pisać testy i nabierze przekonania o tym, że TDD nie działa. Stworzenie dużego systemu, który będzie miał bliskie 100% pokrycie testami, a jednocześnie będzie wielką kupą jest bliskie niemożliwości - utrzymanie testów będzie zbyt bolało.

Zatem TDD promuje, albo niemal wymusza pisanie czystego kodu. Absolutnie nie zgadzam się ze spojrzeniem na TDD jako na dodatek.

Zachęcam do tego, abyś spróbował zastosować TDD w dużym projekcie. Przekonasz się, jak bardzo wpływa to na sposób pracy, myślenia o architekturze, designie i o kodzie. Jeśli znasz i stosujesz to wszystko, o czym piszesz w kontekście czystego kodu, powinno Ci to przyjść dość łatwo. Choć i tak jestem przekonany, że TDD sprawi, że pójdziesz jeszcze dalej i nauczysz się pisać jeszcze lepszy i przejrzystszy kod.

Paweł Lipiński pisze...

Hej
Z tego co piszesz, widać, że nigdy nie próbowałeś TDD. Nawet samej idei dobrze nie zrozumiałeś. Co więcej, nie wiele zrozumiałeś też z przekazu Uncle Boba.

Podstawowe twoje założenie w tym poście - przeciwstawienie pisania testów myśleniu ("Czysty kod, aby powstał musi być wypracowany poprzez pewien wysiłek umysłowy. Nie wystarczy dorwać się do klawiatury i radośnie stukać.") - jest z gruntu błędne i wszystko co dalej piszesz po prostu jest bez sensu.

Czysty kod to kod czytelny. To jak powstanie nie ma znaczenia - ważna jest czytelność. A przypadkiem akurat TDD bardzo pomaga przy tworzeniu czytelnego kodu - myślenie przykładami jego wykorzystania; testowanie małych fragmentów "wymusza" małe metody realizujące choćby SRP.

Ale nie wykluczam, że są osoby o wysokiej intuicji i "inteligencji programistycznej" będące w stanie pisać taki kod po chwili zastanowienia. Może tak być.
Jednak odrzucając TDD pozbawiasz się tego co Uncle Bob nazywa "safety net". Kod z testami to kod pewny. Nie boisz się go modyfikować, bo wiesz, że nawet jeśli coś Ci nie pójdzie, to testy natychmiast to wykryją.

Posiadanie dobrego pokrycia kodu testami jest więc kluczowe z punktu widzenia tworzenia utrzymywalnych/modyfikowalnych aplikacji.

Co do SOLID, projektowania i TDD to już Krzysiek napisał - nie da się po prostu zrobić "kupy" używając TDD, bo nie byłbyś w stanie pisać do niej kolejnych testów. Po tygodniu przestałbyś pisać nowe.

Podsumowując - w mojej ocenie napisałeś względnie śmiesznym językiem parę głupot. Zanim więc skrytykujesz jakąś ideę, spróbuj ją postosować przez jakiś czas.
Jestem przekonany, że trzy miesiące pisania z użyciem TDD uczyni z Ciebie dużo lepszego programistę i pozwoli Ci zdobyć nowe doświadczenia.

PS. angielski czasownik "specyfikować" ma na końcu "y" a nie "i"

Sławek Sobótka pisze...

@Krzystof - dzięki za link do artykułu. To co piszesz odnośnie technik bez których nie da się robić TDD jest tym co chciałem wyrazić w tym poście.

Widzę, że się zgadzamy, jednak na co ja chciałem zwrócić uwagę: nie da się osiągnąć dobrej jakości kodu bez solidnego warsztatu. I jak sam podkreślasz nie są wówczas sensownie zaaplikować TDD.

Być może źle się wyraziłem pisząc o TDD jako o "dodatku" i stąd nieporozumienie. Chodzi mi po prostu o to, że NAJPIERW uczmy porządnego OO, później na tej bazie można mówić o kodzie podatnym na testy. Czyli może nie dodatek a etap drugi. Bo jak pisać kod o wysokim testability jeżeli jedyne techniki jakie zna team to np proceduralne podejście?

@Paweł
Dziękuję za uznanie dla mego miernego poczucia humoru. Dziękują również za korektę mojego kiepskiego angielskiego - z tym mam problemy od dziecka.

Odnośnie wpływu różnych ograniczeń (np TDD, ale nie tylko) na to czy programiści stworzą kod będący kupą... błota to jednak nie doceniasz kreatywności.

W poście chodziło mi o to abyś zadał sobie pytanie o PIERWOTNĄ przyczynę tego, że Twój kod jest wysokiej jakości:
a) Twój skil projektowy i praktyczna znajomość technik dev oraz to, że je sensownie zastosowałeś
b) Obecność testów jednostkowych

Piszesz o "safety net". Zgadzam się: lokalne błędne modyfikacje będą wyłapane przez testy jednostkowe, ale "rozpierducha" o szerszym zakresie to już raczej integracyjne.

Sławek Chmiel pisze...
Ten komentarz został usunięty przez autora.
Łukasz Lenart pisze...

Tak na podsumowanie tego wpisu, to co Sławek próbował przekazać, opisał sam wujek Bob tutaj -> http://blog.objectmentor.com/articles/2009/10/08/tdd-triage

Sławek Sobótka pisze...

@Łukasz - dzięki za tego linka. Potwierdzam, że mniej więcej to chciałem wyrazić.

Mam nadzieję, że ten tekst wyda się certyfikowanym ekspertom bardziej "sensowny" niż moje skromne wynurzenia;P

Rafał Jamróz pisze...

Mocno polecam wywiad Boba Martina z Jimem Coplienem (czyli nie byle kto). Panowie przedstawiaja 2 poprawne spojrzenia na TDD. ("TDD to glupota, a wiem to z tad ze wszystkie moje testy sa integracyjne" nie uwazam za sensowna) Mysle ze Slawku masz blizej do Jima :) .

Mimo odmiennego zdania obu Panow, dochodza do bardzo ciekawego wniosku mianowicie: "Jezeli programista chce sie uwazac za profesjonaliste musi przetestowac swoj kod (najlepiej jednostkowo)". Jest to oczywiscie warunek wymagany lecz nie wystarczajacy. Natomiast drugim wnioskiem jest: "dobrym sposobow na osiagniecie tego jest wlasnie TDD".

Rafał Jamróz pisze...

link jest tutaj... http://www.infoq.com/interviews/coplien-martin-tdd

Sławek Sobótka pisze...

Dziękuję wszystkim, którzy w ciągu tygodnia wyrazili swoje opinie i podzieli się doświadczeniem.

Cieszę się, że udało mi się sprowokować kilkanaście osób do dyskusji na na prawdę ważne tematy (zamiast wklejać hello world w kolejnym frameworku webowym).

Cieszę się, że udało się nam zachować *racjonalne* podejście i uniknąć dziecinnej przepychanki w stylu czyj system operacyjny jest fajniejszy. Dyskutujemy przecież dla własnego dobra i we własnym interesie.

@Rafał: Jutro sprawdzę czy aby na pewno bliżej mi do niejakiego Jima;P

Rafał pisze...

Jest całkiem kompetentne źródło przekazjujące informację na temat tego co Robert C. Martin uważa za czysty kod i jak go uzyskać. Szybka, dobra i znacznie lepsza lektóra niż blog czy artykuł. Książka pt. "Clean Code", dostępna również w tłumaczeniu na Polski. Plecam lektórę zanim zacznie się wypowiadać w kontekście "co autor miał na myśli".

Sławek Sobótka pisze...

Dziękuję za radę.

Post ma ponad 4 lata, w tym czasie niezależnie wpadłem na to aby zapoznać się z polecaną książką:P

Nie chcę wracać do tematu, to już zeszłoroczny śnieg dla mnie jaki i na pewno dla Wujka - bo sam sprostował już kilka rzeczy, więc dla mnie to przeszłość, która rozpierzchła się w chaos wszechświata:)