15 marca 2017

Łamanie symetrii

Wyobraź sobie, że jesteś księgowym. Twoją odpowiedzialnością jest manipulowanie tajemniczymi symbolami, konceptami i procedurami, aby stworzyć bardzo skomplikowane i wymagające precyzji modele dla Twojej firmy. Stawka jest ogromna. Dokładność jest kluczowa. Miliony złotych są do zyskania lub stracenia, a wszystko opiera się na Twoich unikalnych zdolnościach.


W jaki sposób upewnisz się o swojej wysokiej wydajności? Na jakiej dyscyplinie się oprzesz? Jak zapewnisz, że modele, które stworzysz i proponowane przez Ciebie zalecenia będą zgodne z Twoją profesją i przyniosą dochody dla Twojej firmy?


Przez ostatnie 500 lat księgowi używali dyscypliny o nazwie “Reguła podwójnego zapisu”. Pomysł jest prosty, ale zastosowanie jej może być uciążliwe. Każda transakcja jest rejestrowana, współbieżnie na różnych kontach; raz jako uznanie, raz jako obciążenie. Te uznania i obciążenia podążają oddzielnie matematycznymi ścieżkami przez system rachunków różnych kategorii, aż złączą się na zestawieniu bilansowym w odejmowanie, które powinno zwracać zero. Cokolwiek innego niż zero wskazuje na błąd, który został zrobiony w jednej ze ścieżek.


My, programiści mamy podobny problem. Naszą odpowiedzialnością jest manipulowanie tajemniczymi symbolami, konceptami i procedurami, aby stworzyć bardzo skomplikowane i wymagające precyzji modele dla Naszych firm. Stawka jest ogromna. Dokładność jest kluczowa. Miliony złotych są do zyskania lub stracenia, a wszystko opiera się na Naszych unikalnych zdolnościach.


W jaki sposób upewniamy się o naszej wysokiej wydajności? Na jakiej dyscyplinie się opieramy? Jak zapewniamy, że modele, które tworzymy i proponowane przez nas zalecenia są zgodne z naszą profesją i przynoszą dochody dla naszych firm?


Już od dawna stwierdzono, że Test-Driven Development (TDD) jest odpowiednikiem Reguły podwójnego zapisu. Są niezaprzeczalne podobieństwa. Pod dyscypliną TDD każde pożądane zachowanie jest pisane dwa razy; raz w kodzie testowym, które weryfikuje to zachowanie; raz w kodzie produkcyjnym które to zachowanie dowodzi. Te dwa strumienie kodu pisane są współbieżnie i podążają oddzielnie różnymi ścieżkami wykonania, aż złączą się w wyniku liczby defektów w systemie, w wyniku, który powinien wynosić zero.


Innym podobieństwem jest szczegółowość tych dwóch dyscyplin. Reguła podwójnego zapisu opiera się na ekstremalnym rozdrobnieniu pojedynczych transakcji. TDD opiera się na równie ekstremalnym rozdrobnieniu indywidualnych zachowań i asercji. W obu przypadkach podział na elementarne cząstki jest naturalny i oczywisty. Nie ma innych podstawowych części dla księgowości; i trudno sobie wyobrazić bardziej odpowiednie części dla oprogramowania.


Kolejnym podobieństwem jest natychmiastowy zwrot informacji w obu podejściach. Błędy są wykrywane na poziomie każdej cząstki. Księgowi są uczeni sprawdzania wyników dla każdej transakcji. W związku z tym, należycie je wykonując, żaden błąd nie może wedrzeć się, a tym samym zepsuć ogromnych połaci modeli. Szybkie sprzężenie zwrotne, w obu przypadkach, zapobiega długim godzinom szukania błędów i powtarzania tej samej pracy.


Chociaż te dwie dyscypliny wydają się podobne na pierwszy rzut oka, są między nimi głębokie różnice. Niektóre są oczywiste; tak jak fakt, że jedna zajmuje się liczbami i kontami, natomiast druga zajmuje się funkcjami i asercjami. Inne różnice są mniej oczywiste i dużo głębsze.


Niesymetryczność



Reguła podwójnego zapisu jest symetryczna. Uznania i obciążenia nie mają wzajemnych pierwszeństw. Każde jedno wywodzi się z drugiego. Jeżeli znasz rachunki obciążeń i transakcje, możesz z nich wyprowadzić sensowny zbiór rachunków uznań i odwrotnie. Stąd nie ma powodu żeby księgowi potrzebowali wpisywać najpierw uznanie lub najpierw obciążenie. Wybór jest dowolny. Wynik dzielenia będzie taki sam w każdym przypadku.


To nie jest prawdziwe w przypadku TDD. Jest oczywiste wskazanie pomiędzy testami a kodem produkcyjnym. Testy zależą od produkcyjnego kodu, produkcyjny kod nie zależy od testów. To jest prawdziwe zarówno w czasie kompilacji, jak i w czasie działania. Strzałka wskazuje tylko w jedną stronę.


Ta asymetria prowadzi do nieuniknionej konkluzji, że równorzędność z RPZ zajdzie tylko w przypadku, gdy testy pisane będą jako pierwsze. Nie ma sposobu stworzenia odpowiadającej dyscypliny, gdy kod produkcyjny jest pisany przed testami.


Może być trudno to zauważyć na początku. Użyjmy więc starej matematycznej sztuczki sprowadzenia do niedorzeczności. Ale zanim to zrobimy ustalmy określmy TDD od strony formalnej, tak abyśmy ten formalizm mogli odwrócić; formalizm trzech praw. Te prawa to:


  1. Nie możesz napisać jakiekolwiek kodu produkcyjnego, bez napisania najpierw testu, który nie przechodzi, ponieważ kod produkcyjny nie istnieje.
  2. Nie możesz napisać więcej testów niż to wystarcza do warunku nieprzechodzenia testu; włączając w to błędy kompilacji
  3. Nie możesz napisać więcej kodu produkcyjnego, niż tyle, ile wystarcza do przejścia testu, który aktualnie nie przechodzi.


Wypełniając te trzy prawa w formie bardzo uporządkowanej i subtelnej procedury:


  • Musisz zadecydować o funkcji kodu produkcyjnego jaki zamierzasz stworzyć.
  • Musisz napisać test, który nie przechodzi, bo kod produkcyjny nie istnieje.
  • Musisz przestać pisać ten test do momentu, gdy zacznie nie przechodzić z jakiegokolwiek powodu, wliczając błędy kompilacji.
  • Musisz napisać tylko tyle kodu produkcyjnego, aby wystarczyło do zaliczenia tego testu.
  • Powtarzasz w nieskończoność.


Zauważ, jak dyscyplina zmusza Cię do rozdrobnienia pojedynczych zachowań i asercji; włączając asercje czasu kompilacji. Zauważ, że jest tu bardzo mało niejasności w temacie jak wiele kodu musi być napisane w tym momencie; i czy ma to być kod testowy czy produkcyjny. Te trzy prawa przywiązują Cię do określonego, ciasno zdefiniowanego zachowania.


To powinno być jasne, że proces wynikający z tych trzech praw jest logicznym odpowiednikiem Reguły Podwójnego Zapisu.




A teraz załóżmy, że podobna dyscyplina może być określona przy zamianie porządku, czyli kod testowy jest pisany po kodzie produkcyjnym. Jak napisalibyśmy taką dyscyplinę?


Moglibyśmy zacząć zamieniając po prostu trzy prawa. Ale jak tylko zaczniemy, wpadniemy w kłopoty:


  • 1)  Nie możesz napisać jakiekolwiek kodu testowego, bez napisania najpierw kodu produkcyjnego, którego …


Jak spełnić tę zasadę? W nieodwróconej wersji zdanie jest spełnione przez żądanie, że test musi nie przejść z powodu braku kodu produkcyjnego. Ale jaki jest warunek dla naszej nowej odwróconej zasady? Moglibyśmy wybrać coś dowolnego w rodzaju “... którego funkcja jest spełniona”. Jednak nie jest to właściwe odwrócenie pierwszego prawa TDD.


W rzeczywistości, nie istnieje właściwe odwrócenie. Pierwsze prawo nie może być odwrócone. Powód tego jest taki, że pierwsze prawo zakłada, że wiesz jaką produkcyjną logikę zamierzasz stworzyć - a więc to założenie obowiązuje do każdego pierwszego prawa włączając w to odwrócone pierwsze prawo.


Na przykład, możemy odwrócić pierwsze prawo w taki sposób:


  • 1) Nie możesz napisać jakiekolwiek kodu testu, bez napisania najpierw kodu produkcyjnego, który powoduje nieprzechodzenie kodu testowego dla zachowania, które piszesz.


Myślę, że możesz zobaczyć już dlaczego to nie jest właściwie odwrócenie. Aby podążać za tą zasadą musiałbyś napisać kod testowy w twoim umyśle, najpierw, i potem napisać kod produkcyjny, który załamuje ten kod testowy. W istocie test został zidentyfikowany zanim kod produkcyjny został napisany. Test musi wciąż iść jako pierwszy.


Mógłbyś zaprotestować zauważając, że zawsze jest możliwe napisanie testów po kodzie produkcyjnym; i że programiści robili tak przez wiele lat. To prawda; ale naszym celem było napisanie zasady, która byłaby odwróceniem pierwszego prawa TDD. Zasady, która wymuszałaby ten sam poziom rozdrobnienia zachowań i asercji; ale to doprowadziło nas do wymyślenia testów na końcu. Taka reguła wydaje się nie istnieć.


Druga zasada ma podobne problemy.


  • 2) Nie możesz napisać więcej produkcyjnego kodu, który wystarcza do …


Jak dokończyć to zdanie? Nie istnieje oczywiste ograniczenie ilości produkcyjnego kodu jaki możesz napisać. Znowu, jeżeli wybierzemy założenie, to założenie będzie dowolnie wybrane przez nas. Dla przykładu: … spełnienie pojedynczej funkcji. Ale, oczywiście ta funkcja może być ogromna albo malutka albo dowolnej wielkości. Straciliśmy oczywistą i naturalną szczegółowość pojedynczych zachowań i asercji.


Więc znowu, zasada jest nieodwracalna.


Zauważ, że te porażki w odwracalności dotyczą głównie rozdrobnienia. Kiedy testy idą jako pierwsze rozdrobnienie jest naturalnie ograniczone. Kiedy produkcyjny kod idzie pierwszy, nie ma granicy.


To nieograniczone rozdrobnienie oznacza coś głębszego. Zauważ, że trzecie prawo TDD zmusza nas abyśmy sprawili, żeby przeszedł aktualny test, który nie przechodzi i tylko ten test. To oznacza, że produkcyjny kod, który za chwilę napiszemy wyniknie z testu, który nie przechodzi. Ale gdy odwrócisz trzecie prawo skończy się to bzdurą:


  • 3) Nie możesz napisać więcej kodu testowego niż potrzeba do przechodzenia aktualnego kodu produkcyjnego.


Co to oznacza? Mogę napisać test, który przechodzi aktualny kod produkcyjny za pomocą testu bez asercji - lub bez kodu w ogóle. Czy ta zasada prosi nas byśmy testowali każdą możliwą asercję? Jakie to są asercje? Nie zostały one zidentyfikowane.


Powiedzmy to dokładniej. To proste, że używając trzech praw TDD możemy stworzyć całość kodu produkcyjnego ze zbioru poszczególnych testów zawierających asercje; ale nie jest proste stworzyć całość zestawu testów z ukończonego kodu produkcyjnego.


Nie mówię, że nie możesz stworzyć testów z kodu produkcyjnego w ogóle. Oczywiście możesz. To, co nam to mówi to: (i każdy, kto kiedykolwiek próbował pisać testy do kodu zastanego wie o tym) jest to wybitnie trudne, jeżeli nie całkowicie niepraktyczne, pisać dobrze rozdrobnione, wyczerpujące testy z kodu produkcyjnego. Aby napisać takie testy z kodu produkcyjnego musisz najpierw zrozumieć całość tego produkcyjnego kodu; ponieważ każda część tego produkcyjnego kodu może mieć wpływ na test, który próbujesz napisać. Po drugie, produkcyjny kod musi być uniezależniony w sposób, który pozwoli zachować dobre rozdrobnienie.


Kiedy testy są pisane jako pierwsze, rozdrobnienie i uniezależnienie jest bardzo łatwo osiągnąć. Kiedy testy podążają za produkcyjnym kodem rozdrobnienie i uniezależnienie osiągnąć jest o wiele trudniej.


Nieodwracalność



To oznacza, że testy i kod produkcyjny są nieodwracalne. Księgowi nie mają tego problemu. Rachunki uznań i obciążeń są wzajemnie odwracalne. Możesz stworzyć jedne z drugich. Ale testy i kod produkcyjny postępują w jednym kierunku.


Dlaczego tak jest?



Odpowiedź leży w kolejnej asymetrii pomiędzy testami a kodem produkcyjnym: ich strukturze. Struktura kodu produkcyjnego jest bardzo różna od struktury kodu testowego.


Kod produkcyjny kształtuje system z oddziałujących ze sobą części. System działa jako jedna całość. Jest podzielony na komponenty, rozdzielony przez warstwy abstrakcji i zorganizowany z użyciem szlaków komunikacyjnych, wspierających działanie, wydajność i łatwość konserwacji systemu.


Testy z drugiej strony nie kształtują systemu. Są za to zestawem niepowiązanych asercji. Każda asercja jest niezależna od pozostałych. Każdy test w zestawie testów stoi oddzielnie. Każdy test może być odpalony oddzielnie. Faktycznie, testy nie mają preferowanego porządku odpalania; i wiele frameworków testowych wymusza to przez odpalanie testów w porządku losowym.


Dyscyplina TDD mówi nam, żeby w danym momencie budować kod produkcyjny per jeden mały przypadek testowy. Ta dyscyplina daje nam także wskazówki co do porządku pisania tych testów. Wybieramy najprostszy test najpierw i podnosimy złożoność testów tylko wtedy, kiedy wszystkie łatwiejsze testy zostały napisane i przechodzą.


Ta kolejność jest ważna. Początkujący adepci TDD często mylą kolejność i odkrywają, że stworzyli test, który zmusza ich do napisania zbyt dużej ilości kodu produkcyjnego. Zasady TDD mówią nam, że jeżeli test nie może zacząć przechodzić po trywialnym dodaniu lub zmianie kodu produkcyjnego; należy wybrać prostszy test.


A zatem, testy i ich kolejność tworzą instrukcję montażu kodu produkcyjnego. Jeżeli testy zostaną stworzone w takim porządku, wtedy kod produkcyjny zostanie zmontowany poprzez serię łatwych kroków.


Ale, jak pamiętamy, instrukcje montażu nie są odwracalne. Jest trudno, patrząc, dla przykładu na samolot, wysnuć procedurę jego montażu. Z drugiej strony, gdy jest instrukcja montażu, samolot może być zbudowany w skończonym czasie.


Tak więc, zamiana zestawu testów w kod produkcyjny jest funkcją jednokierunkową z zapadką; podobnie do mnożenia dwóch dużych liczb pierwszych. Może być łatwo wykonywana w jednym kierunku; ale jest bardzo trudno; jeżeli nie kompletnie niepraktycznie wykonać ją w drugim kierunku. Testy mogą łatwo prowadzić kod produkcyjny; ale kod produkcyjny nie może praktycznie prowadzić odpowiadającego mu zestawu testów.


Podsumowanie



To co możemy podsumować, to to, że ta trójka jest dobrze zdefiniowaną dyscypliną podejścia “najpierw testy” i jest odpowiednikiem reguły podwójnego zapisu; ale nie istnieje taka dyscyplina dla podejścia “testy później”. Ta dyscyplina działa tylko w jedną stronę. Najpierw testy.


Jak powiedziałem na początku: Stawka jest ogromna. Miliony złotych są do zyskania lub do stracenia. Na szali leżą życia i fortuny. Nasze firmy, i w rzeczywistości całe nasze społeczeństwo liczą na nas. Jaką dyscyplinę wybierzemy, żeby upewnić się, że Ich nie zawiedziemy?


Czy jeżeli księgowi mogą to robić, to czy my też możemy?


Oczywiście, że możemy.

Poniższy tekst jest luźnym tłumaczeniem wpisu bloga Roberta Cecila "Wujka Boba" Martina ze strony :
http://blog.cleancoder.com/uncle-bob/2017/03/07/SymmetryBreaking.html
Proszę o komentarze, jeżeli ta luźność jest zbyt daleko posunięta.



1 komentarz:

Podstawy Programowania Funkcyjnego Epizod 3

Czy wszystkie Zasady Się Zmieniają? Kiedy tylko zaczynamy używać nowego paradygmatu , porównujemy z nim na...