Ciemna Ścieżka

Przez ostatnich kilka miesięcy babrałem się w dwóch nowych językach. Swift i Kotlin. Te języki mają wiele podobieństw. Te podobieństwa są tak wyraźne, że zastanawiam się, czy to nie nowy trend w swoistym językowym biciu piany. Jeżeli tak, to jest to ciemna ścieżka.

Oba języki zawierają w sobie pewne właściwości funkcyjne. Na przykład, oba mają lambdy. Ogólnie, to dobrze. Im więcej wiemy o językach funkcyjnych, tym lepiej. Chociaż te języki są bardzo daleko od nazwania ich prawdziwie funkcyjnymi językami, to każdy krok w tę stronę to dobry krok.

Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.


Mam problem z tym, że oba te języki mocno zaakcentowały statyczne typowanie. W obu przypadkach widać intencje zamknięcia każdej jednej luki w temacie typów ich macierzystych języków. W przypadku Swifta, macierzystym językiem jest dziwaczna beztypowa hybryda C i Smalltalka zwana Objective-C; a więc nacisk na typowanie może być jakkolwiek zrozumiały. W przypadku Kotlina rodzicem jest już raczej mocno typowana Java.

A teraz nie chcę, żebyś pomyślał, że mam coś przeciwko statycznie typowanym językom. Nie mam. Są oczywiście wady i zalety obu dynamicznie i statycznie typowanych języków; ja osobiście z powodzeniem używam obu ich rodzajów. Osobiście preferuję odrobinę bardziej dynamiczne typowanie; i dlatego piszę dużo w Clojure. Z drugiej strony, prawdopodobnie pisze więcej kodu w Javie niż w Clojure. Możesz więc nazwać mnie dwu-typowym. Chodzę po dwóch stronach ulicy - że tak powiem.

To nie tak, że niepokoi mnie to, że Swift czy Kotlin są statycznie typowane jako takie. Raczej coś jest w głębi tego typowania statycznego.

Nie nazwałbym Javy językiem silnie zafiksowanym jeżeli chodzi o statycznie typowanie. W Javie możesz stworzyć struktury, które grzecznie podążają za zasadami typów; ale możesz też naruszyć te zasady kiedy tylko chcesz lub potrzebujesz. Język będzie trochę narzekał, jeśli to zrobisz; i rzuci Ci pod nogi trochę kłód, ale nie na tyle, żeby to była przeszkoda nie do przejścia.

Z drugiej strony Swift i Kotlin, są zupełnie nieelastyczne jeżeli chodzi o ich zasady typowania. Na przykład Swift, gdy zadeklarujesz funkcję, jako mogącą rzucić wyjątek, wtedy, na Boga, każde wywołanie tej funkcji, całą drogę na górę stosu, musi być ozdobione blokiem do-try lub try!, lub try?. Nie ma w tym języku innej możliwości, aby po cichu wyrzucić wyjątek aż na górę do najwyższego poziomu bez układania kostki po kostce całego utwardzonego traktu przez całe drzewo wywołań (Możesz obejrzeć mnie i Justina zmagającego się z tym w naszych filmach video z Warsztatu Tworzenia Aplikacji Mobilnej).

Teraz, może sobie pomyślisz, że to coś dobrego. Może myślisz, że będzie dużo bugów w systemach spowodowanych przez nieokiełznane wyjątki. Może pomyślisz, że wyjątki, które nie są eskortowane, krok po kroku, na górę stosu wywołań są ryzykowne i błędogenne. I, oczywiście, masz rację. Niezadeklarowane i niezarządzane wyjątki są bardzo ryzykowne.

Pytanie jest takie: Kto ma za zadanie zarządzać tym ryzykiem? Czy to zadanie języka? Czy to zadanie programisty?

W Kotlin nie podziedziczysz z klasy, nie przeciążysz funkcji, jeżeli nie ozdobisz tej klasy czy funkcji w open. Także nie przeciążysz funkcji, no chyba że ustroisz przeciążającą funkcję w override. Jeżeli zaniedbasz upiększania klasy w open, język nie pozwoli ci podziedziczyć po niej.

Teraz, może sobie pomyślisz, że to coś dobrego. Być może uważasz, że dziedziczenie i przeciążanie różnych hierarchii, którym pozwala się rosnąć bez ograniczeń to źródło błędów i ryzyka. Być może myślisz, że możemy wyeliminować całe klasy błędów zmuszając programistów aby jawnie deklarowali swoje klasy jako open. I możesz mieć rację. Przeciążanie i dziedziczenie ryzykowne. Wiele rzeczy może pójść źle w przeciążonej metodzie podziedziczonej klasy.

Pytanie jest takie: Kto ma za zadanie zarządzać tym ryzykiem? Czy to zadanie języka? Czy to zadanie programisty?

Oba Swift i Kotlin zawierają w sobie pomysł typów nulowalnych. To, że zmienna może przyjmować null stało się typem tej zmiennej. Zmienna typu String nie może przyjmować null; może zawierać jedynie urzeczowiony String. Z drugiej strony, zmienna typu String? ma typ nulowalny i może przyjmować null.

Zasady języka wymagają tego, że w momencie użycia zmiennej nulowalnej musisz najpierw sprawdzić czy ta zmienna jest nullem. A więc jeżeli s jest Stringiem? wtedy var l = s.length() się nie skompiluje. Zamiast tego musisz napisać:
var l = s.length() ?: 0
lub
var l = if (s!=null) s.length() else 0

Teraz, może sobie pomyślisz, że to coś dobrego. Może już naoglądałeś się wystarczająco dużo NullPointerExcepszynsów w życiu. Może wiesz, bez cienia wątpliwości, że niesprawdzane nulle są sprawcami strat rzędu miliardów dolarów z powodu błędów w oprogramowaniu. (Rzeczywiście, dokumentacja do Kotlina nazywa NPEszyny “Bugami za Miliard Dolców”). I oczywiście masz rację. To bardzo ryzykowne mieć nulle szalejące po całym systemie bez żadnej kontroli.

Pytanie jest takie: Kto ma za zadanie zarządzać tymi nullami? Język? Czy programista?

Te języki przypominają mi przypadek małego Duńczyka wsadzającego palce do tamy. Każdorazowo, gdy pojawia się nowy rodzaj błędu, dodajemy funkcję języka zapobiegającą przed tego rodzaju błędami. I te języki angażują coraz więcej i więcej palców w dziurach w tamie. Problem jest taki, że w końcu skończą się palce u rąk i u nóg.



Ale zanim skończą się palce u rąk i u nóg, stworzymy sobie języki zawierające tuziny słów kluczowych, setki ograniczeń, pokręconą składnię i dokumentację, którą czyta się jak kodeks karny. W rzeczy samej, aby stać się ekspertem w tych językach musisz stać się językowym prawnikiem (termin został wymyślony podczas ery C++).

To jest niewłaściwa ścieżka!


Zapytaj sam siebie dlaczego próbujemy zatamować defekty poprzez funkcje w języku. Odpowiedź wydaje się oczywista. Próbujemy zatamować te defekty, bo te defekty zdarzają się zbyt często.

A teraz zapytaj sam siebie dlaczego te defekty zdarzają się zbyt często. Jeżeli Twoją odpowiedzią jest, że nasze języki nie zapobiegają temu, to ja mocno zachęcam do rzucenia Twojej programistycznej posady i nie zaprzątania sobie głowy programowaniem już nigdy więcej; ponieważ defekty nigdy nie są winą naszych języków. Defekty są winą programistów. To programiści  są tymi, którzy tworzą defekty - nie języki.

No i co więc powinni robić programiści aby zapobiec defektom? Zgaduj. Podpowiem ci. To czasownik. Zaczyna się na literkę “T”. Tak. Zgadłeś. TESTOWAĆ!

Testujesz czy Twój system nie produkuje niespodziewanych nulli. Testujesz czy Twój system obsługuje nulle na swoich punktach wejścia. Testujesz czy każdy wyjątek, który może wystrzelić jest gdzieś łapany.

Dlaczego te wszystkie języki zawierają te wszystkie funkcje? Ponieważ programiści nie testują swojego kodu. I ponieważ programiści nie testują swojego kodu, mamy teraz języki, które zmuszają nas do wpisywania słowa open na początku każdej klasy, z której chcemy podziedziczyć. Mamy teraz języki, które zmuszają nas żeby upiększać każdą funkcję, aż na górę drzewa wywołań w try!. Mamy teraz języki, które są tak ograniczające i tak nadmiernie sprecyzowane, że musisz zaprojektować cały system z góry zanim zaczniesz pisać jakikolwiek kod.

Rozważ: Jak mogę stwierdzić czy klasa jest open czy nie? Ja mogę stwierdzić, że gdzieś w dole drzewa wywołań ktoś może rzucać wyjątek? Jak dużo kodu muszę zmienić aż w końcu odkryję, że ktoś na górze drzewa wywołań koniecznie musi zwrócić nulla?

Wszystkie te ograniczenia, wymuszane przez te języki, przypuszczają, że programista ma idealną wiedzę o systemie; zanim ten system będzie napisany. Przypuszczają, że Ty wiesz, które klasy będą potrzebowały być open, a które nie. Przypuszczają, że Ty wiesz, które ścieżki wykonania rzucą wyjątkami, a które nie. Przypuszczają, że Ty wiesz, które funkcję zwrócą nulla, a które nie.

I przez te wszystkie przypuszczenia, języki te każą Cię jeśli się mylisz. Zmuszają Cię abyś z powrotem zmienił przeogromne połacie kodu, dodając try! lub ?: albo open całą drogę w górę stosu.

No więc jak uniknąć kary? Istnieją dwie drogi. Jedna, która działa i jedna, która nie działa. Ta, która nie działa to zaprojektować wszystko z góry przed kodowaniem. Ta, która działa i pozwala uniknąć kary to obejść wszystkie zabezpieczenia.

I deklarujesz wszystkie swoje klasy i wszystkie swoje funkcje jako open. Nigdy nie używasz wyjątków. I przyzwyczajasz się do używania wielu, wielu znaków ! żeby obejść sprawdzanie nulli i pozwolić NullPointerExcepszynsom szaleć po Twoim systemie.



Dlaczego reaktor jądrowy w Czernobylu zapalił się, roztopił, zniszczył małe miasto i pozostawił ogromny teren niezamieszkałym? Oni obeszli zabezpieczenia. Więc nie polegaj na zabezpieczeniach, aby zapobiec katastrofom. Zamiast tego, lepiej przyzwyczaj się do pisania wielu, wielu testów, nie ważne jakiego języka używasz!


Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.




Na sali są panie


Oh! Myślę, że w końcu zrozumiałem!
Być może wywołam falę emocji pisząc to, co mam do napisania; ale myślę, że w końcu zrozumiałem coś, co mnie niepokoiło.
Trzy razy w ciągu ostatnich kilku lat spotkałem się z kobietami, które poczuły się obrażone tym, co powiedziałem w czasie rozmowy, czy też wykładu.

Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.

Po raz pierwszy było to kilka lat temu na konferencji Rails. Wygłosiłem wówczas wykład zatytułowany: "To, co zabiło Smalltalka, może też zabić Ruby". Nazwałem wówczas język C++ językiem testosteronu, a język Java językiem estrogenu. Nie miałem nic złego na myśli. Zażartowałem jedynie na temat tego, jak poważnie programiści C++ traktują siebie samych w porównaniu z programistami Java. Jednak niektóre kobiety na widowni poczuły się obrażone i napisały o tym na Tweeterze. Szybko i publicznie przeprosiłem je, ponieważ nie chcę, aby kobiety poczuły się niemile widziane w naszej branży. Myślę, że bardzo Je potrzebujemy. Ale o tym później...
Chociaż przeprosiłem, to jednak byłem trochę zdumiony. Moje drugie ja – Tim Taylor – zapytało: „eeeuuuhhh?”



Co było obraźliwego w tym, że użyłem określenia testosteron/estrogen? Nie wiedziałem wtedy tego, ale odpowiedź była bardzo blisko. Po prostu wtedy jeszcze nie byłem jej świadom.
Kilka lat później wygłaszałem prelekcję na konferencji ACCU w Oxfordzie. Tematem było "Requiem dla C". Zacząłem mówić o COBOLu. I to spowodowało, że przyszło mi do głowy powszechnie znane zdjęcie pani admirał Grace Hopper, wynalazczyni COBOLa.



Na zdjęciu stała dumnie w mundurze oficera marynarki i z czapką na głowie. Użyłem wtedy słów "z tą słodką małą czapeczką". Chciałem je cofnąć, ale ...
Po wykładzie podeszła do mnie Emily Bache i delikatnie zasugerowała, że moja uwaga nie była zbyt pomocna dla kobiet na widowni. Przypomniała mi, że kobietom i tak jest wystarczająco trudno być programistkami w męskim towarzystwie nawet bez moich żartów o innych kobietach. Całkowicie się z nią zgodziłem i opublikowałem kolejne przeprosiny. Opublikowałem je, bo chcę, aby jak najwięcej kobiet było w naszej branży. Myślę, że bardzo Je potrzebujemy. Ale o tym później...
W tym tygodniu wykładałem na temat Clean Code w Londynie. Miałem 30 studentów, z których cztery to były kobiety. Byłem bardzo zadowolony, ponieważ był to jeden z najwyższych wskaźników udziału kobiet, jakie miałem na zajęciach. Wykład się udał, a po trzech dniach, kiedy odbyłem je wszystkie, przeczytałem ich ocenę.
Jedna z kobiet napisała, że ​​podobały jej się zajęcia i wiele się z nich nauczyła, ale potem dodała; "były bardziej ukierunkowane na mężczyzn, co sprawiło, że czułam się nieswojo". Tim Taylor: “Eeeuuuhhh?”



Co ja takiego powiedziałem? Nawet teraz nie mam pojęcia, co takiego mogło mi się wymknąć. Nie wiem, co tamta kobieta mogła mieć na myśli. Ale w tym samym tygodniu wydarzyło się coś jeszcze, co wiele wyjaśniło.
Zdarzeniem, o którym chcę opowiedzieć, było fiasko na PyCon, w którym uczestniczyli Adria Richards, Alex Reid, Playhaven i SendGrid. Jeśli nie masz pojęcia o co chodzi, to w blogu Amandy Blum znajdziesz odpowiednie wytłumaczenie. Nie będę się rozwodził na temat tego, kto w tym wszystkim miał rację, wystarczy powiedzieć, że prawdziwymi winowajcami okazali się śmierdziele dokonujący ataki DDOS, a także tweetujący osobiste obelgi i groźby karalne.
Więc co z tego wynika? Cóż, opowiem najpierw kilka historii:
Kiedy byłem w ósmej klasie, przyszedł do nas nowy uczeń. Został wyrzucony z dawnej szkoły w dzielnicy o bardzo złej reputacji, więc natychmiast stał się bohaterem. Prawdziwy "zakapior". Najpierw pozdrawiał wszystkich uśmiechając się olśniewająco, a potem kusząco wskazywał kciukiem na rozporek. Gdy nieuchronnie spojrzałeś w dół, aby podążać za jego wskazującym gestem, mówił: "Ale sztuka!". Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic bardziej nie może rozświetlić dnia, jak humor nastoletniego chłopca.

Dwadzieścia lat temu pracowałem, jako główny architekt oprogramowania w startupie zajmującym się zarządzaniem siecią. Pewnego dnia poszedłem do toalety, gdzie dwaj faceci w garniturach załatwiali swoje potrzeby przy pisuarach. Kiedy skończyli, jeden z nich rzekł do drugiego: "Uważaj, jak strzepujesz tę rosę ze swojego kwiatka!" Ten drugi odpowiedział: "Jeszcze tylko dwa potrząśnięcia i jest gotowy do zabawy!" Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic bardziej nie rozświetla dnia, jak pisuarowy humor trzydziestoparolatka.

Kilka lat temu pewnego wieczoru oglądałem Ostry Dyżur, gdzie piękna dziewczyna radziła swojej równie pięknej koleżance, żeby rzuciła swojego chłopaka, ponieważ ten nie sprostał oralnemu wyzwaniu i nie chciał posunąć się na południe od równoleżnika 32. Nie chciał też pożywić się z Y. Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic bardziej nie rozświetla dnia, jak twórcy niegdyś wielkiego telewizyjnego show, którzy ulegają pokusie, aby użyć niesmacznego pisuarowego humoru nastolatków.

Moja żona została fanem serialu Chirurdzy. Widziałem kilka odcinków tej produkcji, ale nie byłem zachwycony. Było tam wyłącznie o zdradzie, pieprzeniu w każdym nadającym się do tego miejscu, przypinaniu majtek do tablic ogłoszeń, i o tym, jak nauczyć byłego chłopaka, aby zadowolił nową, lesbijską kochankę. Ha, ha, ha. Hi, hi, hi. Ho, ho, ho. Nic tak nie rozświetla dnia, jak fekalny humor ośmiolatka.

Wszystkie te rzeczy mają jedną wspólną cechę. Niesmaczny humor. Uważałam, że wszystko to jest obraźliwe dla mojej inteligencji, obraźliwe dla moich wartości i po prostu obraźliwe – ze względu na kontekst. Takie rzeczy nie powinny mieć miejsca w szkole, w toalecie, ani w telewizji w moim salonie. Powinno się zostawić je w szatni, która jest najlepszym dla nich miejscem.
Czy stworzyliśmy środowisko szatni w branży oprogramowania? Czy do tego stopnia zdominowane ono zostało przez mężczyzn, że przekształciło się w miejsce, gdzie mężczyźni rozluźniają się i opowiadają żarty o puszczaniu bąków i fiutach, aby łaskotać swoje niedojrzałe ego? Czy kiedy programiści rodzaju męskiego przebywają razem, czują się tak swobodnie, że mogą porzucić wszelkie zasady dobrych obyczajów?
Co stałoby się, gdyby role zostały odwrócone? Jak by to było, gdyby kobiety zdominowały softwarową branżę, a my bylibyśmy tymi, którzy starają się do niej wedrzeć? Panowie, czy możecie sobie wyobrazić, jak byłoby nam nieswojo, gdyby wszystkie kobiety ciągle i otwarcie mówiły o tamponach, skurczach, drożdżowych infekcjach, zdradach, byciu zdradzanym, zapaleniach pochwy, itd.? Nie wiem jak wy, ale ja czułbym się wykluczony. I nie można byłoby się ukryć, bo kobiety byłyby wszędzie. Pragnąłbym wtedy, żeby tego rodzaju rozmowy odbywały się tylko w damskiej szatni. Chciałbym wówczas, żeby pamiętały o tym, że na sali są panowie.
Panowie, być może powinniśmy pamiętać, że na sali są panie.
To ostatnie zdanie może wpędzić mnie w kłopoty, ponieważ zbyt wiele nieuczciwych opinii było wypowiedzianych pod pretekstem tego, że są na sali są panie. Dowiedziałem się o tym z filmu o nazwie Niebezpieczna Piękność. Opowieść była o pięknej i młodej kurtyzanie w XVI-wiecznej Wenecji. Ważni ludzie z miasta zabierali ją na wszystkie spotkania polityczne i biznesowe, pozwalając jej poznawać ich skryte plany. Owi mężczyźni trzymali swoje żony z daleka, ponieważ nie chcieli rozmawiać o takich rzeczach gdy na sali są panie. Więc, aby dowiedzieć się, co się naprawdę dzieje z ich mężami i miastem, żony konsultowały się z kurtyzaną; i one używały określenia, które bardzo mi się spodobało. Mówiły, że zostały skazane na ciągłe lekceważenie.
Ciągłe lekceważenie. To brzmi jak więzienie. To brzmi jak piekło. Jeżeli to oznacza “być traktowanym jak dama”, to do diabła z tym.
Myślę, że to wyjaśnia inne wydarzenie, jakie było moim udziałem przy okazji uczestnictwa w konferencji Midwestern Ruby kilka lat temu. Zanim wygłosiłem końcową mowę przewodnią na temat Architektury, siedziałem na widowni, słuchając młodego człowieka, który wygłaszał bardzo interesującą prezentację na temat GitHuba. Niestety byłem udręczony tym, że w każdym jej zdaniu padało grube słowo na “ka”. I było “ku” tu i “ku” tam i “ku” wszędzie, mówię Wam. Na koniec opowiadał o kodzie, który napisał, gdy był ostro nawalony.
Wtedy ktoś wstał, wygłosił kolejną mowę i wspomniał, że jest szczęśliwy, ponieważ na widowni jest tak wiele kobiet, których potrzebujemy w naszej branży. (Miał rację ... ale o tym później ...).
Wtedy przyszła moja kolej. Zacząłem : "Jeśli chcemy mieć więcej kobiet w naszej branży, to musimy zważać na słowa na "ka" i "cha". Kilku mężczyzn z sali zgodziło się ze mną i rozległy się brawa. Ale pewna młoda kobieta wstała i krzyknęła: "Całkowicie się nie zgadzam, kurwa!"
Tim Taylor: "O co chodzi?"



Teraz uważam, że w końcu to zrozumiałem. Powiedziała mniej więcej tak:
"Nie oczekuję, abyś mnie wyróżniał, kiedy mówisz o sprawach zawodowych, bo nie zgodzę się na wieczne lekceważenie . Jeśli używasz grubych słów na “ka” w profesjonalnym kontekście, to nie powstrzymuj się tylko dlatego, że jestem kobietą. Pamięta jednak, że jestem kobietą i nie chcę uczestniczyć w tym, co mówią i robią mężczyźni. Takie rzeczy możesz mówić w męskiej toalecie, ale nie w mojej obecności, bo mnie to doprowadza do szału.”
Teraz, aby spełnić to, co obiecałem.
Miałem przyjemność spędzić godzinkę z Desi McAdam (@desi) jednego wieczoru, podczas, gdy implementowała jedną funkcję. Powiedziałem jej, że dla mnie, sprawianie żeby program działał jest jak podbój - zabijanie bestii i przynoszenie mięsa do domu. Odpowiedziała, że dla niej było to raczej odżywcze doświadczenie wzrostu pomagające oprogramowaniu przyjść na świat. Tim Taylor: "Eeeuuhhhh?".



Miałem trochę czasu, aby o tym pomyśleć. Więc kiedy myślę o kodzie, mam na myśli zabijanie robactwa i ugniatanie kodu według moich zamierzeń. Jestem panem. To mój niewolnik. Mam moc, aby sprawiać żeby był taki, jaki chcę. Dla mnie to jest mega odjazd.
Zastanawiam się, czy to w części nie tłumaczy problemu z rzemiosłem i profesjonalizmem, który ma nasza branża. Dlaczego tak dużo naszego oprogramowania jest tak źle zrobiona? Czy to dlatego, że traktujemy je jako podbój? Czy to dlatego, że niezbyt wystarczająco je pielęgnujemy?
Nie zrozumcie mnie źle, dla mnie to zawsze będzie podbój. Zawsze będę panem mojego kodu. To jestem ja. Ale być może to nie wystarczy, aby przekształcić naszą pracę nad oprogramowaniem w prawdziwy zawód. Może wymaga ona również innej postawy.
Wszakże w życiu osobistym wiem, że moje najgorsze decyzje podjąłem bez udziału żony. Żona uzupełnia mnie na wiele różnych sposobów. Bez niej byłbym cieniem mężczyzny, który przemierza dwuwymiarową przestrzeń. Moje relacje z nią sprawiły, że z chłopca przeobraziłem się w mężczyznę. Pomogła mi dorosnąć.
Nasza branża też musi dorosnąć. Być może powodem, dla którego mamy tyle problemów z dojrzewaniem jest to, że nie mamy wokół siebie wystarczająco dużo kobiet, które pomogłyby nam przejść proces dorastania. Ich postawy, sposoby myślenia, różniące się od naszych, mogą być czymś, czego potrzebujemy, aby udoskonalić naszą branżę, zawód oraz rzemiosło. Wydaje mi się, że potrzebujemy kobiet, aby nasza profesja stała się profesją.
Musimy sprawić, aby kobiety poczuły się mile widziane w naszym towarzystwie. Powinniśmy również zdać sobie sprawę z tego, że cała branża oprogramowania komputerowego nie jest naszą prywatną szatnią. Musimy traktować kobiety jak profesjonalnych partnerów, zważając jednocześnie na to, że stale są między nami obecne.
Oczywiście, jestem tylko Timem Taylorem, który rozmawia przez ogrodzenie z Wilsonem.



Drogie panie, czy ująłem to dobrze?



Powyższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.

Testy - obywatele pierwszej kategorii

Mam wrażenie, że to może być jakieś fatum, że trafiam na blogi pisane przez ludzi, którzy poddali się negatywnemu działaniu nieszczęsnych dyscyplin, które z kolei doprowadziły ich do całkowitego zaprzestania pisania testów jednostkowych.
Ten blog jest przykładem jednego z takich.


Poniższy tekst jest luźnym tłumaczeniem wpisu z bloga Roberta Cecila Wujka Boba Martina stąd:
Proszę o komentarze, gdyby ta luźność była zbyt daleko posunięta.


Autor opowiada o tym, jak jego testy są wrażliwe, ponieważ mockuje wszystkie współpracujące odwołania zewnętrzne. (ech). Każdorazowo, gdy odwołania zewnętrzne się zmienią trzeba zmieniać mocki. I to oczywiście czyni testy jednostkowe wrażliwymi.

Autor opowiada nam, jak porzucił testowanie jednostkowe i zaczął robić coś, co nazywa “Testowaniem Systemowym”. W jego słowniku, “Testy Systemowe” były po prostu testami, które były bardziej end-to-end niż “testy jednostkowe”.

A więc najpierw trochę definicji. Wybacz mi moją nieposkromioną pychę, ale jest teraz tyle różnych definicji “testów jednostkowych”, “testów systemowych” czy “testów akceptacyjnych”, że ktoś powinien podać jedną autorytarną definicję. Nie wiem, czy te definicje się przyjmą; ale mam szczerą nadzieję, że jakimś definicjom uda się to przyszłości.

  • Test Jednostkowy: Test napisany przez programistę, aby upewnić się, że kod produkcyjny robi to, co powinien. (Na ten moment pomińmy fakt, że testy jednostkowe także ulepszają projekt aplikacji, itd.)
  • Test Akceptacyjny: Test napisany przez “biznes”, aby upewnić się, że produkcyjny kod robi to, co powinien. Autorami tych testów są ludzie od strony biznesowej, albo ludzie od strony technicznej, którzy reprezentują biznes, np. Analitycy Biznesowi i Testerzy.
  • Test Integracji: Test napisany przez architektów i/lub liderów technologicznych, aby upewnić się, że podzespół składników systemu działa prawidłowo. To są testy kanalizacyjne. Nie są zestawem testów reguł biznesowych. Ich celem jest potwierdzić, że podzespół jest zintegrowany i skonfigurowany właściwie.
  • Test Systemowy: Test integracyjny napisany , aby upewnić się, że bebechy całego zintegrowanego systemu działają tak jak wcześniej zaplanowano. Są to testy kanalizacyjne. Nie są zestawem testów reguł biznesowych. Ich celem jest potwierdzić, że system jest zintegrowany i skonfigurowany właściwie.
  • Mikro-test: Termin ukuty przez Mike’a Hill’a (@GeePawHill). Test Jednostkowy napisany w bardzo małym kontekście. Celem tego testu jest sprawdzenie jednej funkcji albo małej grupy funkcji.
  • Test Funkcjonalny: Test Jednostkowy napisany dla szerszego kontekstu z odpowiednim zestawem mocków dla powolnych komponentów.

Biorąc pod uwagę te definicje autor wcześniej wspomnianego bloga zrezygnował z pisania źle napisanych mikro testów, na korzyść źle napisanych testów funkcjonalnych. Dlaczego źle napisanych? Ponieważ w obu przypadkach autor opisuje te testy jako połączone do rzeczy, do których nie powinny być połączone.
W przypadku mikro-testów używał za dużo mocków i mocno powiązał swoje testy z kodem produkcyjnym. To oczywiście powoduje, że testy są kruche.

W przypadku testów funkcjonalnych, autor opisuje je jako całą drogę od Interfejsu Użytkownika do Bazy Danych i odnosi się do faktu, że były powolne. Cieszy się kiedy jego testy czasami działają tylko 15 minut. 15 minut to o wiele za dużo, jak na szybki feedback, który testy jednostkowe powinny nam dostarczać. Praktycy TDD nie mają w zwyczaju czekać na system ciągłej integracji, tylko po to, żeby stwierdzić, że testy przechodzą.
Doświadczeni praktycy TDD rozumieją, że ani mikro-testy, ani testy funkcjonalne (ani nawet testy akceptacyjne) nie powinny być mocno połączone z implementacją systemu. Powinny (tak jak autor bloga zachęca nas) być rozważone, jako część systemu i “... traktowane jako obywatele pierwszej kategorii: [One] powinny być traktowane w taki sam sposób, jak kod produkcyjny”.

To, czego wydaje się, autor bloga nie dostrzega, to to, że obywatele pierwszej kategorii systemu nie powinni być ściśle połączeni. Ktoś, kto traktuje swoje testy jako obywateli pierwszej kategorii podejmie wiele starań, aby zapewnić, że te testy nie są mocno powiązane z kodem produkcyjnym.

Rozluźnianie powiązań mikro-testów i testów funkcjonalnych z kodem produkcyjnym nie jest szczególnie trudne. Rzeczywiście wymaga trochę zdolności w projektowaniu oprogramowania i trochę wiedzy na temat technik rozłączania. Zasadniczo spora dawka Projektowania Zorientowanego Obiektowo i odwrócenia zależności, razem z rozsądnym użyciem Wzorców Projektowych (jak Fasada czy Strategia) wystarczą, aby rozluźnić nawet najbardziej szkodliwy zestaw testów.

Niestety, zbyt dużej liczbie programistów wydaje się, że zasady dla testów jednostkowych są inne - że testy mogą być “śmieciowym kodem” napisanym przy użyciu dowolnego sposobu, wybranego ad hoc, jaki tylko ci programiści uznają za stosowny. Także zbyt wielka liczba programistów czyta książki na temat mockowania i kupili te książki w przekonaniu, że narzędzia do mockowania są nieodłączną i potrzebną częścią testowania jednostkowego. Nic nie może być dalej od prawdy.

Ja, dla przykładu, rzadko używam narzędzia do mockowania. Kiedy potrzebuję mocka (albo, raczej Dublera Testowego) piszę go sam. Nie jest trudno pisać takie dublery testowe. Moje IDE bardzo mi w tym pomaga. Co więcej, pisanie Dublera Testowego zachęca mnie do pisania testów jednostkowych bez użycia Dublerów Testowych, no chyba że to jest bardzo potrzebne. Zamiast używać Dublerów Testowych, odchodzę na chwilę od pisania mikro-testów i piszę testy, które są ciut bliższe testom funkcjonalnym. To także pomaga mi rozdzielić testy od wnętrzności kodu produkcyjnego.

Podsumowując, powodem tego, że ludzie poddali się z pisaniem testów jednostkowych jest zwykle to, że nie podążali za powyższą radą autora. Nie traktowali testów jako obywateli pierwszej kategorii. Nie traktowali testów w taki sposób, aby były one nieodłączną częścią systemu. Nie utrzymywali standardów dla tych testów jakich utrzymywali dla innych części systemu. Zamiast tego pozwolili testom się psuć, stać się mocno powiązanymi z kodem, stać się niezmienialnymi, kruchymi i powolnymi. I wtedy, w wielkiej frustracji, porzucili testy.

Morał: Zachowaj swoje testy czyste. Traktuj je jako obywateli pierwszej kategorii w Twoim systemie.


Koniec konkursu Daj Się Poznać 2017

Nadszedł 31 maja 2017, a więc koniec konkursu Daj Się Poznać 2017.
Jestem bardzo wdzięczny. Wdzięczny Społeczności konkursu i Maćkowi Aniserowiczowi. Społeczności za możliwość czytania fantastycznych wpisów. Za burzliwe dyskusje na Slacku o konkursie, programowaniu, czy polityce. Maćkowi za to, że zorganizował pierwszy i najlepszy konkurs dla bloggerów technologicznych w Polsce.

Wiele się nauczyłem przez ten czas od Was - uczestników DSP. Mój dev i blog skill poszybował dzięki Wam o kilka poziomów do góry. Życzę każdemu z Was, abyście prowadzili regularnie Wasze blogi przynajmniej do następnej edycji konkursu. Dzięki za wszystko. Do następnego.

Pozdrawiam
Michał Kuliński



Wzorzec Obserwator - otrzymywanie subskrybowanych informacji.

Po tym jak mogę dodawać obiekty do listy subskrybentów zabieram się za otrzymywanie wiadomości.

Poprzedni post tu.

Napisałem sobie krótki test w Ruby:
def test_receiving_information                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
    @client = Client.new                                                                                                                                                                                                                     
    @media.add_advertising("5 55 555")                                                                                                                                                                                                       
    @media.add_to_subscribers @client                                                                                                                                                                                                        
    #@media.broadcast                                                                                                                                                                                                                        
    #assert_not_empty @client.ads                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
end




Na razie wykomentowałem dalsze akcji i puszczam rake:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): RuntimeError: Argument should have publish method                                                                                                                                      
/home/coola/dsp2017/xp-simulator/src/Media.rb:17:in `add_to_subscribers'                                                                                                                                                                     
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:47:in `test_receiving_information'                                                                                                                                               
     44:                                                                                                                                                                                                                                     
     45:     @client = Client.new                                                                                                                                                                                                            
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
  => 47:     @media.add_to_subscribers @client                                                                                                                                                                                               
     48:     #@media.broadcast                                                                                                                                                                                                               
     49:     #assert_not_empty @client.ads                                                                                                                                                                                                   
     50:                                                                                                                                                                                                                                     
=============================================================================================================================================================================================================================================
                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
Finished in 0.002118131 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
85.7143% passed                                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3304.80 tests/s, 4249.03 assertions/s                                                                                                                                                                                                        
rake aborted!                                                                                                                                                                                                                                
Command failed with status (1): [/usr/share/ruby-2.3.1/bin/ruby ./test/all_...]                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/Rakefile:4:in `block in <top (required)>'                                                                                                                                                                   
/home/coola/.gems/2.3/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'                                                                                                                                                                     
Tasks: TOP => default => test                                                                                                                                                                                                                
(See full trace by running task with --trace)                                   


Okazuje się, że mój klient nie "implementuje" metody publish, potrzebnej dla obiektu Media do publikacji.

Dopisuję dwie linijki:

class Client                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                             
  def publish                                                                                                                                                                                                                                
  end  
                                                                                                                                                                                                                                      
end 

Dla rake to wystarczy:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
.......                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
Finished in 0.00171854 seconds.                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
100% passed                                                                                                                                                                                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4073.22 tests/s, 5237.00 assertions/s               


Odkomentowuję następną linijkę:

def test_receiving_information                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
    @client = Client.new                                                                                                                                                                                                                     
    @media.add_advertising("5 55 555")                                                                                                                                                                                                       
    @media.add_to_subscribers @client                                                                                                                                                                                                        
    @media.broadcast                                                                                                                                                                                                                         
    #assert_not_empty @client.ads                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
end 


Tutaj przede wszystkim nie ma metody broadcast i jej implementacji.

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): NoMethodError: undefined method `broadcast' for #<Media:0x007f3f412768c0>                                                                                                              
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:48:in `test_receiving_information'                                                                                                                                               
     45:     @client = Client.new                                                                                                                                                                                                            
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
     47:     @media.add_to_subscribers @client                                                                                                                                                                                               
  => 48:     @media.broadcast                                                                                                                                                                                                                
     49:     #assert_not_empty @client.ads                                                                                                                                                                                                   
     50:                                                                                                                                                                                                                                     
     51:   end                                                                                                                                                                                                                               
=============================================================================================================================================================================================================================================
                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
Finished in 0.002872458 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
85.7143% passed                                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2436.94 tests/s, 3133.21 assertions/s                                                                                                                                                                                                        
rake aborted!                                                                                                                                                                                                                                
Command failed with status (1): [/usr/share/ruby-2.3.1/bin/ruby ./test/all_...]                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/Rakefile:4:in `block in <top (required)>'                                                                                                                                                                   
/home/coola/.gems/2.3/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'                                                                                                                                                                     
Tasks: TOP => default => test                                                                                                                                                                                                                
(See full trace by running task with --trace)          


Dodaję zmiany w klasie Media:

class Media                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                             
  attr_accessor :ads, :subscribers                                                                                                                                                                                                           
                                                                                                                                                                                                                                             
  def initialize                                                                                                                                                                                                                             
    @ads = Array.new                                                                                                                                                                                                                         
    @subscribers = Array.new                                                                                                                                                                                                                 
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def add_advertising telephone_number                                                                                                                                                                                                       
    @ads.push telephone_number                                                                                                                                                                                                               
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def add_to_subscribers client                                                                                                                                                                                                              
    if !client.respond_to? :publish                                                                                                                                                                                                          
      raise 'Argument should have publish method'                                                                                                                                                                                            
    end                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
    @subscribers.push client                                                                                                                                                                                                                 
                                                                                                                                                                                                                                             
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def broadcast                                                                                                                                                                                                                              
    @subscribers.each { |subscriber| subscriber.publish @ads }                                                                                                                                                                               
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
end 


Używam metody publish z atrybutem i rzeczywiście rake mówi, że w klasie Client nie ma atrybutu metody publish:

                                                                                                                                                                                         
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): ArgumentError: wrong number of arguments (given 1, expected 0)                                                                                                                         
/home/coola/dsp2017/xp-simulator/src/Client.rb:3:in `publish'                                                                                                                                                                                
/home/coola/dsp2017/xp-simulator/src/Media.rb:25:in `block in broadcast'                                                                                                                                                                     
/home/coola/dsp2017/xp-simulator/src/Media.rb:25:in `each'                                                                                                                                                                                   
/home/coola/dsp2017/xp-simulator/src/Media.rb:25:in `broadcast'                                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:48:in `test_receiving_information'                                                                                                                                               
     45:     @client = Client.new                                                                                                                                                                                                            
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
     47:     @media.add_to_subscribers @client                                                                                                                                                                                               
  => 48:     @media.broadcast                                                                                                                                                                                                                
     49:     #assert_not_empty @client.ads                                                                                                                                                                                                   
     50:                                                                                                                                                                                                                                     
     51:   end                                                                                                                                                                                                                               
=============================================================================================================================================================================================================================================
                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
Finished in 0.002341671 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 9 assertions, 0 failures, 1 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
85.7143% passed                                                                                                                                                                                                                              
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2989.32 tests/s, 3843.41 assertions/s                                                                                                                                                                                                        
rake aborted!                                                                                                                                                                                                                                
Command failed with status (1): [/usr/share/ruby-2.3.1/bin/ruby ./test/all_...]                                                                                                                                                              
/home/coola/dsp2017/xp-simulator/Rakefile:4:in `block in <top (required)>'                                                                                                                                                                   
/home/coola/.gems/2.3/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'                                                                                                                                                                     
Tasks: TOP => default => test                                                                                                                                                                                                                
(See full trace by running task with --trace)           

Dopisuję argument ads do klasy Client i jest OK.

Teraz odkomentowuję ostatnią linijkę testu:

def test_receiving_information                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
    @client = Client.new                                                                                                                                                                                                                     
    @media.add_advertising("5 55 555")                                                                                                                                                                                                       
    @media.add_to_subscribers @client                                                                                                                                                                                                        
    @media.broadcast                                                                                                                                                                                                                         
    assert_not_empty @client.ads                                                                                                                                                                                                             
                                                                                                                                                                                                                                             
  end  

Rake krzyczy, że nie ma zmiennej klasowej ads i jest ona pusta:

Started                                                                                                                                                                                                                                      
......E                                                                                                                                                                                                                                      
=============================================================================================================================================================================================================================================
Error: test_receiving_information(SubscriptionTests): NoMethodError: undefined method `ads' for #<Client:0x007f1a96ef2420>                                                                                                                   
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:49:in `test_receiving_information'                                                                                                                                               
     46:     @media.add_advertising("5 55 555")                                                                                                                                                                                              
     47:     @media.add_to_subscribers @client                                                                                                                                                                                               
     48:     @media.broadcast                                                                                                                                                                                                                
  => 49:     assert_not_empty @client.ads                                                                                                                                                                                                    
     50:                                                                                                                                                                                                                                     
     51:   end                                                                                                                                                                                                                               
     52:                                                                                                                                                                                                                                     
=============================================================================================================================================================================================================================================
                         

Dodaję zmiany:

class Client                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                             
  def publish ads                                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
    @ads = ads                                                                                                                                                                                                                               
                                                                                                                                                                                                                                             
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
  def ads                                                                                                                                                                                                                                    
    @ads                                                                                                                                                                                                                                     
  end                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
end

I teraz projekt przechodzi wszystkie testy :)


coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb                                                                                                                                                                                           
Loaded suite ./test/all_tests                                                                                                                                                                                                                
Started                                                                                                                                                                                                                                      
.......                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                             
Finished in 0.001713314 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 tests, 10 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                       
100% passed                                                                                                                                                                                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4085.65 tests/s, 5836.64 assertions/s 

Commit do tego tu.

To jest ostatni projektowy wpis w konkursie Daj się Poznać 2017. Dzięki Maćku i Społeczności :)