14-11-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.

Komentarze (22):

Blogger Michał Pasternak pisze...

wUjem, k%@^$, wUUUUUjem!!!

14 listopada 2009 14:05  
Blogger Paweł Lipiński pisze...

No już, już. O 2 w nocy to już nawet słownik w firefoxie śpi :P

14 listopada 2009 14:48  
Blogger Sławek Sobótka pisze...

Pawle - piękny post. Widzę, że wyznajemy dokładnie takie wartości. Podobnie Twoi koledzy - miło poznać ludzi, których wciąż jeszcze nie opuściła pasja i którym zależy.

Odnośnie czytelnego kodu zapraszam do kolejnego sarkastycznego posta:
http://art-of-software.blogspot.com/2009/05/understandability.html

Spawy, o których piszesz są niestety zwykle ignorowane. Cieszę się, że mój sarkastyczny post sprowokował parę osób do wymiany poglądów oraz Ciebie do podzielenia się doświadczeniem. Przyznam, że taki był mój cel:)

Niestety niewiele osób porusza na prawdę istotne tematy - inne niż hello world w kolejnym frameworku webowym;)

Odnośnie twoich przykładów, to ze wszystkim się zgadzam, pytanie tylko czy dobry kod nie wynika "po prostu" z dobrego stylu i smaku - wyrobionego prze lata zmagań? Owszem na początku zauważamy pewnie konstrukcje dzięki zaczynaniu od kodu "klienckiego", ale z czasem być może wejdą one po prostu do naszych "szablonów mentalnych".

Właśnie wydaje mi się, że nasza dyskusja rozbija się w sumie o jeden nieistotny szczegół: co było pierwsze jajko czy kura. Czyli OOD czy TTD?
Dzisiaj podczas porannego biegu kolega podsunął mi ciekawą myśl na podstawie swoich obserwacji: programista "niezbyt biegły" w OOD jeżeli zostanie zmuszony do TDD to poradzi sobie bardzo łatwo nie znając podstawowych technik zwiększających testability obiektowego kodu - po prostu stworzy kod proceduralny:)

O nic więcej nie chodziło mi w moim poście: powinno lansować się TDD w dwupaku z OOD. Trzeba zdać sobie sprawę z tego, że jest oczywiste dla nas - nie musi takie być dla innych.

14 listopada 2009 17:24  
Blogger Tomasz Nazar pisze...

Czuję się wywolany do tablicy. Juz wspomnialem na ostatnim spotkaniu, ze jedno nie zalezy od drugiego. Mozna miec "clean code", bez TDD. Niestety - albo "stety".

Mozna powiedziec, ze TDD pomaga w pisaniu, bo wymaga jednego: klasy, metody modelu musza byc latwe w wywolaniu z metod testujacych.
Rowniez test musi byc czytelny i latwy w przyszlosci do modyfikacji - wiec naturalne, ze i model musi.

Nie chce teoretyzowac: w wiekszosci projektow Pawel pewnie znow masz racje.
TDD pomaga. (a w-jakiejs-formie-automatyczne-testy sa konieczne, aby wiedziec, ze sie nic nie napsuło)

Natomiast spokojnie dopuszczalna jest sytuacja, gdy model jest pisany z "perspektywy test-first", ale bez samego testu.

Mój model jest naturalnie "czytelny" dla czlowieka. Ale nie dzieki posiadaniu/braku testow z pewnoscia :)

Ale bez znajomosci TDD kiedys, nie wiem jak by to wygladalo.

T.

14 listopada 2009 17:55  
Blogger Tomasz Nazar pisze...

Sorki, nie moglem sie powstrzymac - ale na temat czytelnosci kodu jak najbardziej:
Na blogu kolegi Sobotki taki kod znalazlem (nie wiem, czy jego, czy wklejony, czy to juz tak w Javie 'jest'):

[code]
public void submit(){
if (status != Status.NEW)
throw new InvalidStateException();
status = Status.IN_PROGRESS;
}
[code]

Oczywiscie powyzsze, jest wielkie Bee. Przeciez, czy jest "new", czy nie moze/bedzie zalezalo od innych czynnikow... moze byc uzyte przez kogos innego.. no i to jest takie niskopoziomowe ;]

Tak ma byc jesli juz:

[code]
public void submit(){
if (am_not_new())
throw new InvalidStateException();
status = Status.IN_PROGRESS;
}
[/code]

A pewnie lepiej inna wariacja:

[code]
public void submit(){
this.validate_state_when_submiting();
status = Status.IN_PROGRESS;
}
[/code]
...

14 listopada 2009 18:07  
Blogger Paweł Lipiński pisze...

@Sławek i trochę Tomek
Moje doświadczenie jest takie, że bardzo dobrzy programiści (w sensie intuicji, wiedzy itp.) bez TDD piszą w miarę czytelny kod. Przeciętni programiści z TDD piszą podobny. Bardzo dobrzy z TDD piszą kod świetny. Mam w firmie takiego jednego kolegę (pozdrowienia Michał :)), który jeszcze pół roku temu (a więc naprawdę niedawno) pisał dobry zwykły kod. Z wykorzystaniem wzorców i dobrych praktyk. Ale z punktu widzenia czystości (a więc utrzymywalności) na pewno wiele można było mu zarzucić.
Przez ostatnie pół roku pracy w parach i TDD jego kod stał się lepszy o 100%. Teraz trudno przyczepić się do czegokolwiek. Wczoraj w czasie pisania tego postu robiłem review jego kodu z jakiegoś opensourceowego projekciku i naprawdę musiałem się bardzo czepiać żeby mu coś znaleźć. Po prostu kod - perełka (oczywiście test-drive'owany). To właśnie TDD robi z ludźmi i ich kodem ;)

Do pisania czystego kodu bez TDD trzeba dużo samozaparcia i doświadczenia. Myślę, że to trudniejsze niż z TDD.

Tak jak pisałem, nie wierzę, że ktoś pisze bardziej złożone rzeczy od razu ładnie obiektowo. Po prostu nasz mózg myśli szeregowo i proceduralnie i trudniejsze rzeczy po prostu się pisze linia po linii tak jak o nich myślimy, tworząc parodziesięcio linijkowe pokraki. A potem jak to już działa, jest czas na refaktoryzację. Środowiska javowe mają masę automatycznych refaktoryzacji, więc nie wykluczam, że da się to zrobić bez testów. W dynamicznych językach, gdzie automatyczna refaktoryzacja jest szczątkowa, było by to bardzo ryzykowne. Z resztą możesz Tomek coś na ten temat z pewnością powiedzieć.

A co do tworzenia kodu proceduralnego w TDD, to prawie niemożliwe. To bardzo nieintuicyjne by było.

14 listopada 2009 18:38  
Blogger Paweł Lipiński pisze...

@Tomek
Co do TDD, to nie wierzę w pisanie kodu z perspektywą test-first, ale bez testów. Albo piszesz pod przykład, albo nie. Z resztą nie da się bez testów robić tak małych kroków, a więc o emergent design możesz zapomnieć, bo musisz wszystko przewidzieć.
Z resztą w tym poście pokazałem dość prosty przykład z emailem, który powiedzmy jeszcze można wymyśleć bez zapisania go gdzieś (w teście). W trudniejszych przypadkach - nie wierzę, że dasz radę osiągnąć ładny design bez napisania sobie najpierw. A jak już napisałeś, to dlaczego nie zrobić z tego testu?

A co do kodu, to od razu po twoich poprawkach widać, żeś się zpajtonowił :P To twoje nazywanie z podkreśleniami...

Ale oczywiście masz rację - jest wyraźne pomieszanie poziomów abstrakcji i zgwałcona SRP :)

14 listopada 2009 18:46  
Blogger Łukasz Lenart pisze...

Mam tylko dwie uwagi do Twojego wpisu, zresztą jedną sam już przytoczyłeś - można pisać Czysty Kod bez TDD. Nie jest to łatwe i testy w tym pomagają, ale TDD nie jest tu warunkiem koniecznym. Specjalnie rozróżniam TDD i testy.

I druga rzecz, to Twoja uwaga dotycząca przyszłości, że "za miesiąc trzeba będzie coś przerobić" - pisanie w stylu zgadywania? bo coś będzie tak lub inaczej za miesiąc albo będziemy potrzebować nowych funkcji? Wolę w tym przypadku powiedzieć, że testy uczą nas jak używać klasy, kolejności wywołania metod, etc. Zresztą sam pokazałeś poprawną kolejność wywołań. Jedno, że uczą a po drugie potwierdzają, że takie wywołanie działa.

A nie, że "za miesiąc będzie coś potrzebne".

14 listopada 2009 22:27  
Blogger Paweł Lipiński pisze...

Łukasz
1) Clean code bez TDD:
Tak piszecie jak to można ładnie bez TDD, to ja poproszę przykłady. Nie mówię, że się nie da, tylko chciałbym to zobaczyć w praktyce. Konkretny czysty kod powyżej 10k linii.
2) Zmiana czegoś za miesiąc.
To zależy już od projektu, choć myślę, że rzadko w dużych projektach po napisaniu kod jest zamknięty na zmiany. U nas faktycznie są fragmenty, które nie podlegają modyfikacjom od miesięcy, ale to jest mniejszość. Wiele wymagań które realizujemy dziś dotyka funkcjonalności napisanych miesiąc-dwa temu. I zdarza się, że je rozszerza albo ma potrzebę wykorzystania tak, że tamten kod trzeba jakoś dostosować, żeby zachować czystość tego nowego. Więc to nie jest przykład z kosmosu, tylko rzeczywistość wielu projektów.
Dlatego czas poświęcony na czystość kodu zwraca się zwykle jeszcze w tym samym projekcie - nasz kod nie blokuje ani zwalnia nas po paru miesiącach developmentu, bo cały czas jest w dobrej formie.

15 listopada 2009 10:29  
Blogger Łukasz Lenart pisze...

@Paweł
Sarkazm to nie argument, ja tylko, napisałem, że TDD to nie jest warunek konieczny dla pisania Czystego Kodu - bardzo to ułatwia ale nie jest niezbędy. Samo podejście TDD nic tu nie da, bo to ludzie piszą kod a nie TDD. Chodzi mi o trzeźwe spojrzenie.

I nie piszę tego celem powiedzenia "nie używajcie TDD bo jest beee", sam jestem zwolennikiem TDD i w swojej pracy staram się pisać testów jak najwięcej.

Dziwnie przekręcasz moje słowa, ja tylko uważam, że pisanie czegoś "na przyszłość", "bo się przyda" jest złym podejściem. Nie pisałem o zamknięciu kodu lub braku potrzeby modyfikacji istniejącego. Tylko o gdybaniu.

15 listopada 2009 13:45  
Blogger Tomasz Nazar pisze...

A jak sie w tym bloggerze wkleja kod, aby ladnie formatowalo?

15 listopada 2009 17:43  
Blogger Paweł Lipiński pisze...

@Łukasz
Ale ja nie napisałem nic sarkastycznego - naprawdę chciałbym zobaczyć taki kod. Nie mam pomysłu jak można by zapewnić czystość kodu w zespole np. 7 osób bez stosowania przez cały zespół TDD.

Oczywiście nie jest tak, że dowolny test-drive'owany kod jest od razu dobry. Ale szanse na osiągnięcie takowego są dużo wyższe, a przy okazji uprawiając TDD zdobywa się świadomość tego czym czysty kod jest, ponieważ inaczej ciężko pisać testy.

A co do "gdybania" to albo ja nie zrozumiałem o co Tobie chodziło, albo Ty o co mi. Nie ważne.
Nasz projekt jest teraz taki, że przerabiamy faktycznie dużo, bo klientowi samemu świadomość potrzeb się zmienia w trakcie rozwijania aplikacji. Więc my już w ogóle nie gdybamy, tylko pytamy.
Ale nawet jakby ktoś miał projekt w którym od razu wiadomo wszystko, to i tak warto dbać o czystość kodu i testy, bo do dobrego napisania nowych rzeczy trzeba często adaptować te stare. I automatyczna refaktoryzacja często wtedy nie wystarcza.

@Tomek
Chyba tylko można boldować italikować i linkować. A co, 10k linii ładnego kodu chciałeś wkleić? :P

15 listopada 2009 19:16  
Blogger Tomasz Nazar pisze...

A co! Czytajcie. Ale jak sie brzydko polamie, to Pawel zmieniaj dostawce.. porazka to formatowanie kodu z  

//Model do treningow

class Training(PortalBaseModel):
    FRENCH_ONLY_TRAININGS = [18, 19]

    def has_questions(self):
        return self.questions and len(self.questions) > 0

    def completed_because_only_in_french(self, text_lang):
        return self.id in self.FRENCH_ONLY_TRAININGS \
and text_lang and text_lang.id != 'fr'


class TrainingAnswer(PortalBaseModel):
    def __init__(self, training, question, answer):
        self.training = training
        self.question = question
        self.answer = int(answer)

    def is_answer_the_right_one(self):
        return self.answer == self.question.right_answer

    def is_later_than(self, answer):
        return self.when_created > answer.when_created


class User(PortalBaseModel, CanBeDeleted):
    def answer_quiz(self, training, question, answer):
        self.training_answers.append(TrainingAnswer(training, question, answer))
        Event.training_completed(user=self, training=training)

    def profile_completed_quiz(self):
        for training in Training.find_all():
            if not self.is_training_complete(training):
                return False
        return True
...


//Kontroler z jedna metoda
class TrainingController(WiBaseController):
    def submit_quiz(self, training_id):
        training = Training.get_by_id(training_id)
        logged = self._logged_user()
    question_answers = {} #....
        for question_id in question_answers:
            question = TrainingQuestion.get_by_id(question_id)
            logged.answer_quiz(training, question, question_answers[question_id])

            if logged.is_training_complete(training) and logged.profile_completed_quiz():
                Event.training_completed_all(user=logged)

T.

15 listopada 2009 22:19  
Anonymous Anonimowy pisze...

Model domenowy zależny od DTO. Zakładam, że BaseModel jest jakimś DTO.
Lepiej produkować z fabryki.

API modelu zorientowane na język francuski?

User zorientowany na Quiz. Co jeżeli aplikacja będzie robić coś jeszcze? User stanie się boską klasą?

Model ma statyczne metody DAO. Rozumiem, że to nie Twój wymysł. Raczej jakiś słaby framework.

Nazewnictwo zmiennych - miejscami mało adekwatne nazwy.

Szczekałeś, szczekałeś i w końcu pokazałeś:)

Sam stosuję TDD. Akurat nie przysporzyłeś mu chluby tym razem.

16 listopada 2009 20:26  
Blogger Tomasz Nazar pisze...

@Anonimowy
>Model domenowy zależny od DTO.

Nie. DTO sluzy do transferu obiektow.
W tym "base" jest akurat get_by_id/find_all/etc.. Tak, aby bylo krocej. Wszystkie User.find_by* sa w klasie user, jesli jej dotycza.

W tym modelu jest to co ma byc: obiekt ma swoje atrybuty (mapowane w bazie) i metody, ktore w wiekszej wiekszosci nie pchaja sie do bazy danych.

Z kontrolera zniknela jedna linijka na koncu db.save().. ale zalozmy, ze jest po kazdym "requescie" wykonywana.


>API modelu zorientowane na język francuski?

Hmm. Wymog biznesowy. Dla tego jezyka quiz ma byc spelniony dla kazdego usera.

>User zorientowany na Quiz. Co jeżeli aplikacja będzie robić coś jeszcze? User stanie się boską klasą?

User ma jeszcze ze 20 innych metod. Podobnie jak w zyciu..


>Model ma statyczne metody DAO. Rozumiem, że to nie Twój wymysł. Raczej jakiś słaby framework.

To akurat moj. Powyzej powody.
Akurat DAO wogole istnieje, bo istnieja problemy z obiektowym dostepem do bazy danych, vide: Object relational impedance mismatch

Moze, moznaby metody *.find_*/get* wywalic do DAO, ale po co.. tylko kolejna sztuczna warstwa.


>Nazewnictwo zmiennych - miejscami mało adekwatne nazwy.

O tak. Tu zawiniam ja :/


>Szczekałeś, szczekałeś i w końcu pokazałeś:)

To za prosba Pawla, prosimy o wlasny kod kolego Anonimowy.

>Sam stosuję TDD. Akurat nie przysporzyłeś mu chluby tym razem.

Akurat ten kod jest bez TDD. Z TDD wygladalby +-95% tak samo.

16 listopada 2009 20:42  
Blogger Paweł Lipiński pisze...

@Anonim
Jak coś krytykujesz, to się podpisuj. Inaczej brzmi to niepoważnie. Ale przynajmniej częściowo masz rację. Tym bardziej szkoda, żeś nie podpisał.

@Tomek
Własnie udowodniłeś, że nie miałeś racji ;)
Gdybyś robił tu tdd pewnie szybko zorientowałbyś się, że potrzebna jest klasa quiz i nie zanieczyszczałbyś tego biednego użytkownika.
A uciekanie od warstw w imię zwięzłości powoduje pogorszenie testowalności kodu.

Tak więc twierdzę, że z tdd ten kod byłby lepszy i wymagający mniej refaktoryzacji (pewnie niedostępnej w pythonie).


Każdy kod zawiera takie rzeczy. Nie oszukujmy się. Każdy czasem idzie na skróty. Ja tylko twierdzę, że z tdd dzieje się to po prostu rzadziej, więc i kod statystycznie jest lepszy.

16 listopada 2009 21:18  
Blogger Tomasz Nazar pisze...

Ha! Ciekawe uwagi Pawel, ale wina silnika blogu i nazewnictwa, ze mniej trafne :>
Training == Quiz. OK?
(nazewnictwo sie usprawni)

>Własnie udowodniłeś, że nie miałeś racji ;)

te 9% usprawnien przeznaczylbym na kontroler. Tam jest pole do ulepszen.
1% na model jak cos znajdziesz.


>..i nie zanieczyszczałbyś tego biednego użytkownika.

Ale przeciez logged.is_training_complete(training) to jest jego odpowiedzialnosc (albo kogos nad nim, ale od biedy i jego). To jego sie pytamy o jego treningi (quizy).

Przeciez to on odpowiada na pytania:
logged.answer_quiz(training, question, answer)

:)


>A uciekanie od warstw w imię zwięzłości powoduje pogorszenie testowalności kodu.

To na dluzsza dyskusje. Ale raczej nie. Ja nie potrzebuje DAO, bo po co. Bazy nie wymienie. A jak wymienie to sie cos wymysli _wtedy_. Znalezienie/zmiana/ekstrakt wszystkich model.*.find_* jest trywialne.
Akurat w Javie to wiadomo jak wyglada DAO i ile interfejsow trzeba, a stamtad to juz niedaleko, do "managerow", a stad juz daleko od OO Domain. (raczej przesadzam)

>Tak więc twierdzę, że z tdd ten kod byłby lepszy

Pewnie tak. Ale w modelu jest dobrze, w kontrolerach "mogło by byc lepiej".
Czekam na ladne kawalki innych osob :)

> (pewnie niedostępnej w pythonie)

Spoko spoko. Sam potrafie ;) A przeciez IDEA ma search&replace

16 listopada 2009 21:44  
Blogger Tomasz Nazar pisze...

Wracajac do tematu posta i Literate programming.. prosze przebic te czytelnosc kodu testującego w Javie (i niestety Pythonie): http://andrzejonsoftware.blogspot.com/2008/01/rspec-user-story-example.html

Bdd, Rails, Ruby:

1. Story: Creating an order

3. Scenario: admin user creates an order
4. Given an admin user
5. And some orders
6. And some customers

8. When he creates an order

10. Then a new order is created
11. And a new customer is created

16 listopada 2009 22:24  
Blogger Paweł Lipiński pisze...

Ja Tomek nie podejmuję się nic tu pisać na temat tego kodu z którego widać 30 linijek na krzyż.
Ale i tak sądzę, że coś z tym quizem jest nie ok, skoro user ma tyle metod dotyczących go. Nie lepiej by było np. quiz.answer() zamiast user.quiz_answer?

Mi chodziło poza tym o projekty tworzone zespołowo. W pojedynkę jest dużo łatwiej utrzymać jakość.


A skoro już przyznałeś, że z TDD jednak było by lepiej, to dlaczego tego nie robić? Skoro jest lepiej i jeszcze do tego bezpieczniej zmieniać potem. A jak bezpieczniej, to się to chętniej i częściej robi. Więc długoterminowo jest po prostu łatwiej utrzymać jakość.

16 listopada 2009 22:24  
Blogger Sławek Sobótka pisze...

@Paweł - chciałbym zapytać o tą właśnie sytuację, bo mamy akurat próbkę materiału do dyskusji...

Piszesz:
"Ja tylko twierdzę, że z tdd dzieje się to po prostu rzadziej, więc i kod statystycznie jest lepszy."

Więc zapytam o to o co chodziło mi od samego początku: statystyka czy estetyka?

Ja twierdzę, że zrobiłbyś to inaczej (ograniczenie odpowiedzialności Usera, wprowadzenie DAO/Repo) ze względu na swe wyczucie estetyki. Twierdzę też, że ktoś inny mógłby obłożyć testami Usera i zostawić go jako "boską klasę" jak nawał to Anonim.

Oczywiście warstwa DAO albo artefakt DDD: Repozytorium pojawiłby się ponieważ dostęp do bazy musiałby być siłą rzeczy stubowany/mockowany.

Ale ich (DAO/Repo) istnienie ma inny, głębszy sens - jak sam zauważyłeś - a nie tylko taki aby dało się napisać testy.

17 listopada 2009 00:12  
Blogger Tomasz Nazar pisze...

>Mi chodziło poza tym o projekty tworzone zespołowo. W pojedynkę jest dużo łatwiej utrzymać jakość.

Nasz zespół ma: 5 dev + ~5 biznesowych ludzi. Ale fakt: 5 nie pracuje nad modelem.


>A skoro już przyznałeś, że z TDD jednak było by lepiej, to dlaczego tego nie robić? Skoro jest lepiej i jeszcze do tego bezpieczniej zmieniać potem. A jak bezpieczniej, to się to chętniej i częściej robi. Więc długoterminowo jest po prostu łatwiej utrzymać jakość.

To jest trudne pytanie. Może sobie sam odpowiem jak już będę miał długą brodę... na razie nie wiem. Próbuję inaczej.

Póki co się udaje. Bez testów funkcjonalnych krytycznych miejsc z użyciem Selenium i kodem jegoż w Pythonie - nie dałbym rady.
Bez jednostkowych do arytmetycznych funkcji również nie dałbym rady..
Bez testów jednostkowych do funkcji biznesowych - "żyję" ..... ale co to za życie ;-)))

EOT

17 listopada 2009 00:52  
Blogger Paweł Lipiński pisze...

@Sławek
Estetyka nie ma znaczenia sama w sobie. Służy czytelności. Nie ma czegoś takiego jak estetyka kodu - nikt nie siedzi godzinami oglądając 100 linijek i zachwycając się nimi. Nie ma też wycieczek do muzeum programowania. Estetyka służy więc czytelności, a ta utrzymywalności.

Nie możesz przeciwstawiać jednego drugiemu, bo obie rzeczy mówią o tym samym. Tylko jedno z punktu widzenia programisty piszącego (estetyka), a drugie czytającego (statystyka jakości).


Nie wierzę, że wyłącznie ze względu na poczucie estetyki tworzysz czysty kod. Jakieś to znaczenie pewnie ma, ale nie przeceniałbym go. Bo co jak ktoś nie ma poczucia estetyki kodu? Do tego trzeba doświadczenia a nie gustu.
A co jak w zespole jest wielu programistów: który ma lepsze wyczucie estetyki? Nie możemy się opierać na tak miękkich miarach i terminach. Jeżeli założymy, że testowalność choć w przybliżeniu ~= estetyka (z dokładnością do nazewnictwa) to mamy od razu dużo twardszą miarę.
Z resztą widzisz, że Twoje poczucie estetyki jest inne niż Tomka, który też ma za sobą dobre parę lat doświadczenia jako programista.

@Tomek
Sądzę, że gdybyś miał narzędzia do wygodnego robienia TDD to zobaczyłbyś jak bez sensu jest nie korzystanie z tego. Jak jedyne co masz do refaktoryzacji to search&replace, to trudno się dziwić...

17 listopada 2009 08:23  

Prześlij komentarz

<< Strona główna