W Test Driven Development (TDD) zaczynasz od rozwiązania nieoptymalnego, a następnie iteracyjnie produkujesz lepsze, dodając przypadki testowe i refaktoryzując. Kroki mają być małe, co oznacza, że każde nowe rozwiązanie będzie w jakiś sposób znajdować się w sąsiedztwie poprzedniego.
Przypomina to matematyczne lokalne metody optymalizacji, takie jak opadanie gradientu lub wyszukiwanie lokalne. Dobrze znanym ograniczeniem takich metod jest to, że nie gwarantują one znalezienia globalnego optimum, ani nawet akceptowalnego lokalnego optimum. Jeśli punkt początkowy jest oddzielony od wszystkich akceptowalnych rozwiązań dużym regionem złych rozwiązań, nie można się tam dostać, a metoda zawiedzie.
Mówiąc dokładniej: myślę o scenariuszu, w którym zaimplementowałeś wiele przypadków testowych, a następnie stwierdziłem, że następny przypadek testowy wymagałby zupełnie innego podejścia. Będziesz musiał odrzucić swoją poprzednią pracę i zacząć od nowa.
Tę myśl można faktycznie zastosować do wszystkich zwinnych metod, które przebiegają małymi krokami, nie tylko do TDD. Czy ta proponowana analogia między TDD a lokalną optymalizacją ma jakieś poważne wady?
źródło
Odpowiedzi:
Aby twoje porównanie było bardziej adekwatne: w przypadku niektórych problemów iteracyjne algorytmy optymalizacji mogą generować dobre lokalne optymima, w niektórych innych przypadkach mogą zawieść.
Mogę sobie wyobrazić sytuację, w której może się to zdarzyć w rzeczywistości: kiedy wybierzesz niewłaściwą architekturę w taki sposób, że będziesz musiał odtworzyć wszystkie istniejące testy od nowa. Załóżmy, że zaczynasz wdrażać pierwsze 20 przypadków testowych w języku programowania X w systemie operacyjnym A. Niestety, wymaganie 21 obejmuje, że cały program musi działać w systemie operacyjnym B, gdzie X nie jest dostępny. Dlatego musisz wyrzucić większość swojej pracy i ponownie wdrożyć w języku Y. (Oczywiście nie wyrzuciłbyś kodu całkowicie, ale przeniósł go do nowego języka i systemu).
To uczy nas, nawet podczas korzystania z TDD, dobrym pomysłem jest wcześniejsze przeprowadzenie ogólnej analizy i projektu. Dotyczy to jednak każdego innego podejścia, więc nie uważam tego za nieodłączny problem TDD. W przypadku większości rzeczywistych zadań programistycznych możesz po prostu wybrać standardową architekturę (np. Język programowania X, system operacyjny Y, system bazy danych Z na sprzęcie XYZ) i możesz być stosunkowo pewny, że iteracyjna lub zwinna metodologia, taka jak TDD nie doprowadzi cię w ślepy zaułek.
Powołując się na Roberta Harveya: „Nie można wyhodować architektury na podstawie testów jednostkowych”. Lub pdr: „TDD nie tylko pomaga mi dojść do najlepszego ostatecznego projektu, ale pomaga mi dotrzeć tam przy mniejszej liczbie prób”.
Więc właściwie to co napisałeś
może stać się prawdą - jeśli wybierzesz niewłaściwą architekturę, prawdopodobnie nie osiągniesz stamtąd wymaganego rozwiązania.
Z drugiej strony, kiedy wcześniej planujesz jakieś ogólne planowanie i wybierasz odpowiednią architekturę, korzystanie z TDD powinno być jak rozpoczęcie iteracyjnego algorytmu wyszukiwania w obszarze, w którym możesz oczekiwać osiągnięcia „globalnego maksimum” (lub przynajmniej wystarczająco dobrego maksimum ) w kilku cyklach.
źródło
Nie sądzę, że TDD ma problem lokalnych maksimów. Kod, który piszesz, może, jak zauważyłeś, ale właśnie dlatego refaktoryzacja (przepisywanie kodu bez zmiany funkcjonalności) jest na miejscu. Zasadniczo wraz ze wzrostem liczby testów można przepisać znaczną część modelu obiektowego, jeśli zachodzi taka potrzeba, zachowując niezmienione zachowanie dzięki testom. Testy stwierdzają niezmienne prawdy o twoim systemie, które dlatego muszą być ważne zarówno w lokalnych, jak i absolutnych maksimach.
Jeśli interesują Cię problemy związane z TDD, mogę wymienić trzy różne, o których często myślę:
Kompletność Problem: ile testy są konieczne, aby całkowicie opisać system? Czy „kodowanie przykładowych przypadków” to kompletny sposób na opisanie systemu?
Hartowania problemem: cokolwiek testuje interfejs, musi mieć niezmienną interfejs. Pamiętajcie, że testy reprezentują niezmienne prawdy . Niestety, te prawdy w ogóle nie są znane w przypadku większości kodu, który piszemy, w najlepszym wypadku tylko w przypadku zewnętrznych obiektów.
Uszkodzenie testu problemem: w celu dokonania twierdzeń sprawdzalne, możemy trzeba pisać kodu nieoptymalne (mniej wydajnych, na przykład). Jak piszemy testy, aby kod był tak dobry, jak to tylko możliwe?
Edytowane w celu zaadresowania komentarza: oto przykład przekroczenia lokalnego maksimum dla funkcji „podwójnej” poprzez refaktoryzację
Test 1: gdy wartość wejściowa wynosi 0, zwróć zero
Realizacja:
Refaktoryzacja: nie jest wymagana
Test 2: gdy wejście to 1, zwróć 2
Realizacja:
Refaktoryzacja: nie jest wymagana
Test 3: gdy wejście to 2, zwróć 4
Realizacja:
Refaktoryzacja:
źródło
To, co opisujesz matematycznie, to to, co nazywamy malowaniem się w kącie. Zjawisko to nie wyklucza wyłącznie TDD. W wodospadzie możesz zbierać i przesypywać wymagania przez miesiące, mając nadzieję, że zobaczysz globalne maksimum tylko po to, aby się tam dostać i zdać sobie sprawę, że jest lepszy pomysł tylko na następną górkę.
Różnica polega na zwinnym środowisku, w którym nigdy nie spodziewałeś się, że będziesz idealny w tym momencie, więc jesteś więcej niż gotowy, by rzucić stary pomysł i przejść do nowego.
Bardziej specyficzne dla TDD jest technika, aby temu zapobiec, gdy dodajesz funkcje w TDD. To Przesłanka Priorytetowa Transformacji . Tam, gdzie TDD ma formalny sposób na refaktoryzację, jest to formalny sposób dodawania funkcji.
źródło
W swojej odpowiedzi @Sklivvz przekonująco argumentował, że problem nie istnieje.
Chcę argumentować, że to nie ma znaczenia: podstawową przesłanką (i raison d'être) iteracyjnych metodologii ogólnie i Agile, a zwłaszcza TDD w szczególności, jest to, że nie tylko globalne optimum, ale także lokalne optymalizacje nie są znany Innymi słowy: nawet jeśli był to problem, nie ma mowy o robieniu tego iteracyjnie. Zakładając, że akceptujesz podstawowe założenie.
źródło
Czy praktyki TDD i Agile mogą zapewnić optymalne rozwiązanie? (A może nawet „dobre” rozwiązanie?)
Nie dokładnie. Ale to nie jest ich cel.
Metody te zapewniają po prostu „bezpieczne przejście” z jednego stanu do drugiego, potwierdzając, że zmiany są czasochłonne, trudne i ryzykowne. Obie praktyki mają na celu zapewnienie, że aplikacja i kod są wykonalne i udowodniono, że spełniają wymagania szybciej i bardziej regularnie.
TDD koncentruje się na zapewnieniu, że każda „część” kodu spełnia wymagania. W szczególności pomaga upewnić się, że kod spełnia wcześniej istniejące wymagania, a nie pozwala, aby wymagania były sterowane przez słabe kodowanie. Ale nie obiecuje, że wdrożenie jest w jakikolwiek sposób „optymalne”.
Jeśli chodzi o procesy zwinne:
Zwinność nie szuka optymalnego rozwiązania ; tylko działające rozwiązanie - z myślą o optymalizacji ROI . Obiecuje działające rozwiązanie wcześniej niż później ; nie „optymalny”.
Ale to OK, ponieważ pytanie jest złe.
Optymalne w tworzeniu oprogramowania są rozmyte, ruchome cele. Wymagania są zwykle zmienne i pełne tajemnic, które pojawiają się tylko, ku twojemu zawstydzeniu, w sali konferencyjnej pełnej szefów twojego szefa. „Wewnętrzna dobroć” architektury i kodowania rozwiązania jest oceniana na podstawie podzielonych i subiektywnych opinii twoich rówieśników i opinii twojego kierowniczego zwierzchnika - z których żaden tak naprawdę nie mógł wiedzieć o dobrym oprogramowaniu.
W każdym razie, TDD i Agile praktyki uznają trudności i próby w celu optymalizacji dla dwóch rzeczy, które są obiektywne i mierzalne: . Robocza V nie pracujący i prędzej v później..
I nawet jeśli mamy „pracować” i „wcześniej” jako obiektywne wskaźniki, twoja zdolność do ich optymalizacji zależy przede wszystkim od umiejętności i doświadczenia zespołu.
Do rzeczy, które można interpretować, gdy wysiłki dają optymalne rozwiązania, należą:
itp..
To, czy każda z tych rzeczy faktycznie zapewnia optymalne rozwiązania, byłoby kolejnym wielkim pytaniem!
źródło
Jedną z rzeczy, których do tej pory nikt nie dodał, jest to, że opisywany „Rozwój TDD” jest bardzo abstrakcyjny i nierealny. Może tak być w przypadku aplikacji matematycznej, w której optymalizujesz algorytm, ale nie dzieje się tak często w aplikacjach biznesowych, nad którymi pracuje większość programistów.
W prawdziwym świecie twoje testy w zasadzie sprawdzają i weryfikują reguły biznesowe:
Na przykład - jeśli klientem jest 30-letni niepalący z żoną i dwójką dzieci, kategorią premium jest „x” itp.
Nie będziesz iteracyjnie zmieniać silnika obliczeń premium, dopóki nie będzie on poprawny przez bardzo długi czas - i prawie na pewno nie, dopóki aplikacja będzie uruchomiona;).
To, co faktycznie stworzyłeś, to siatka bezpieczeństwa, dzięki której po dodaniu nowej metody obliczeń dla określonej kategorii klientów wszystkie stare reguły nie łamią się nagle i udzielają złej odpowiedzi. Siatka bezpieczeństwa jest jeszcze bardziej przydatna, jeśli pierwszym krokiem debugowania jest utworzenie testu (lub serii testów), który odtwarza błąd przed napisaniem kodu w celu naprawienia błędu. Następnie, rok później, jeśli ktoś przypadkowo odtworzy pierwotny błąd, test jednostkowy psuje się, zanim kod zostanie nawet wpisany. Tak, jedną rzeczą, na którą pozwala TDD, jest to, że możesz teraz robić duże refaktoryzacje i porządkować rzeczy z pewnością ale to nie powinna być ogromna część twojej pracy.
źródło
Nie sądzę, żeby to przeszkadzało. Większość zespołów nie ma nikogo, kto byłby w stanie wymyślić optymalne rozwiązanie, nawet jeśli zapisałeś to na ich tablicy. TDD / Agile nie będzie im przeszkadzać.
Wiele projektów nie wymaga optymalnych rozwiązań, a te, które wymagają, będą poświęcać niezbędny czas, energię i skupić się na tym obszarze. Jak wszystko, co zwykle budujemy, po pierwsze, aby działało. Więc zrób to szybko. Możesz to zrobić za pomocą jakiegoś prototypu, jeśli wydajność jest tak ważna, a następnie odbudować całość dzięki mądrości zdobytej podczas wielu iteracji.
Może się to zdarzyć, ale bardziej prawdopodobne jest obawa przed zmianą skomplikowanych części aplikacji. Brak testów może zwiększyć poczucie strachu w tym obszarze. Jedną z zalet TDD i posiadania zestawu testów jest to, że zbudowałeś ten system z myślą, że trzeba będzie go zmienić. Gdy wymyślisz to monolityczne zoptymalizowane rozwiązanie od samego początku, jego zmiana może być bardzo trudna.
Umieść to również w kontekście obaw związanych z niedostateczną optymalizacją, a nie możesz nie poświęcić czasu na optymalizację rzeczy, których nie powinieneś mieć, i tworzenie nieelastycznych rozwiązań, ponieważ byłeś tak bardzo skoncentrowany na ich wydajności.
źródło
Wprowadzanie koncepcji matematycznych takich jak „lokalne maksimum” do projektowania oprogramowania może być mylące. Korzystanie z takich terminów sprawia, że tworzenie oprogramowania wydaje się o wiele bardziej kwantyfikowalne i naukowe niż w rzeczywistości. Nawet jeśli istnieje „optymalny” kod, nie mamy możliwości jego zmierzenia, a zatem nie wiemy, czy go osiągnęliśmy.
Zwinny ruch był naprawdę reakcją na przekonanie, że rozwój oprogramowania można planować i przewidywać metodami matematycznymi. Na lepsze lub gorsze, tworzenie oprogramowania bardziej przypomina rzemiosło niż naukę.
źródło
fibonacci
, że widziałem to jako przykład / samouczek TDD, to mniej więcej kłamstwo. Jestem skłonny założyć się, że nikt nigdy nie „odkrył” Fibonacciego lub podobnej serii przez TDD. Wszyscy zaczynają od znajomości fibonacciego, który oszukuje. Jeśli spróbujesz to odkryć przez TDD'ing, prawdopodobnie dojdziesz do ślepego zaułka, o który prosi OP: nigdy nie będziesz w stanie uogólnić serii po prostu pisząc więcej testów i refaktoryzacji - musisz zastosować matematykę rozumowanie!