🐕 O psie, który jeździł koleją. 🚂 Jak zapłacić za koszyk w serwisie PKP? Odrobaczanie systemu.

3 lat temu

Powyżej zostało użyte zdjęcie psa ze strony Pies.pl

O psie, który jeździł koleją, albo bardziej o naszym polskim PKPowym isPies czy “kłopotliwych zniżkach” słyszał w branży IT już prawie każdy. Chociaż w międzyczasie doszła “szybka szpachla ifem” w postaci fragmentu if(ZakupBiletuUtil().isCOVID()), to dzisiaj także nie o tym. Dzisiaj historia z życia na kodach wzięta. Co stanie się, kiedy trzech programistów podejmie się karkołomnego wyzwania zapłacenia za jedyny bilet w koszyku? Czy zwyciężą “Cannot read properties of null”? W jaki sposób wyruszą w swoją podróż? Czy przeprowadzą odrobaczanie? A może się poddadzą? Przeczytaj, jak 3 programistów bilet na PKP kupowało i pokonali siejącego czerwone logi buga, który mieszkał w przypadku brzegowym.

  • Ten wpis pomoże Ci rozwiązać problem w serwisie PKP, jeżeli masz w koszyku bilet/y na jedno połączenie kolejowe i nie możesz dokonać płatności.
  • Jako programista przeczytasz trochę o Debuggerze w Google Chrome i Ubiquitous Language z Domain-Driven Design. Dowiesz się, kiedy Polskie wyrazy w kodzie mają uzasadnienie!
  • Zabłądziłeś/aś w odmęty internetu? W najgorszym przypadku jedynie umilę Ci trochę czas, jeżeli nie kupujesz żadnego biletu i znasz się na frontendzie oraz DDD.

Enjoy!

Z życia na kodach

Bywają tacy podróżni, którzy mogą “nie dbać o bagaż, nie dbać o bilet” tak jak bohater książki “O psie, który jeździł koleją”. Jednak w naszym przypadku taka zasada nie miała zastosowania. Zaopatrzenie się w bilet powinno być jedną z najprostszych czynności związanych z podróżą, ale nie tym razem!

I może nic by się nie udało, gdyby nie Ci wścibscy programiści. A adekwatnie ja i dwóch kolegów frontendowców — pozdrawiam Marcina i Konrada :) Historia niestety jest niezmyślona i dużo w niej prawdy.

Weszliśmy na stronę PKP i wykonaliśmy rutynowe kroki zakupu biletów.

  1. Wprowadziliśmy kryteria wyszukiwania — stację początkową i docelową oraz datę wyjazdu.
  2. Wybraliśmy jedno z proponowanych połączeń połączenie.
  3. Określiliśmy ile biletów, jakiego typu chcemy kupić.
  4. Nie zapłaciliśmy za wybrane bilety, ale wybraliśmy opcję “Dodaj do koszyka”.

Zakup biletów w serwisie Intercity PKP. Kroki dodania do koszyka.
(Kliknij, aby powiększyć)

Plan był taki, aby wyszukać też bilety na kolejne połączenia. Jednak po chwili, zdaliśmy sobie sprawę, iż nie wiemy jeszcze, kiedy chcemy wracać. Dlatego porzuciliśmy na razie ten pomysł i postanowiliśmy od razu zapłacić za bilety w koszyku.

Bug zamieszkały w przypadku brzegowym

Bilety w koszyku figurowały jako jedna pozycja, ponieważ były wybrane dla tego samego połączenia kolejowego. Jak się okazało, było to bardzo zgubną decyzją i tu leży isPies pogrzebany. Niestety, zaatakował nas znienacka bug siejący czerwonymi logami w konsoli. Jakie było nasze zdziwienie (no dobra, tak naprawdę to nic nas już na tej stronie nie zaskoczy), kiedy kilkukrotne klikanie w button “Kupuję i płacę” nie powodowało żadnej reakcji systemu (stan aktualny na 24.11.2021). Słyszałem kiedyś historię, o tym, iż przez kilka dni od startu nowej firmy, nikt nie kupował produktu przez internet. Cały sztab ludzi analizował sytuację, marketingowcy zastanawiali się, co jest nie tak z reklamą itp. A okazało się, iż przyczyna leży zupełnie gdzie indziej. Po prostu przycisk “kup teraz” nie działał…

W naszym przypadku konsola w przeglądarce zabarwiła się na czerwono, a wszystko jakby krzyczało: “zawróćcie z tej drogi”, niczym potwory w Scooby-Doo. Ale my postanowiliśmy się nie poddawać i przeprowadzić odrobaczanie (luźne tłumaczenie dla ang. debugging) tego kodu z błędów. Zbadaliśmy pacjenta dzięki trybu Inspect w Google Chrome oraz debuggera kodu JavaScript. Błąd w konsoli wyglądał następująco:

Uncaught TypeError: Cannot read properties of null (reading 'value') at platnosc (platnosc.js?ver=883646852:63) at HTMLInputElement.<anonymous> (main.js?ver=883646852:1449) at HTMLDocument.dispatch (jquery-3.2.1.min.js:3) at HTMLDocument.q.handle (jquery-3.2.1.min.js:3)

Jako programiści postanowiliśmy pobawić się w Devtektywów i osiągnąć nasz cel. Nadszedł czas na tripple programming.

Szybkie otworzenie narzędzi deweloperskich i… znaleźliśmy kłopotliwą linijkę (nie zniżkę) w pliku platnosci.js. Problem nie leżał w isPies, tylko zupełnie gdzie indziej…

document.getElementById('faktura_nazwa_firmy').value = convertString(document.getElementById('faktura_nazwa_firmy').value);

Przeprowadzając łączenie kropek między błędem a linijką, widać, iż null został zwrócony przez document.getElementById('faktura_nazwa_firmy'), bo to z tego obiektu chcemy odczytać adekwatność value. Oznacza to, iż na stronie jest brak pola faktura_nazwa_firmy, którego wartość jest konieczna do wykonania kodu po wciśnięciu przycisku. Dlaczego kod wymaga czegoś takiego, skoro ja wcale nie kupuję biletu na firmę? To już pozostanie tajemnicą. Poradziliśmy sobie z tym bardzo łatwo. Najprostszym rozwiązaniem było dodanie jednej linijki kodu w kodzie HTML strony. <input id="faktura_nazwa_firmy" value="ZycieNaKodachPL" /> Dzięki temu pojawiło się dodatkowe pole, kod wykonał się bez rzucenia błędem i mogliśmy przejść do płatności. Dalej już wszystko działało. Po zapłaceniu bilety pojawiły się w systemie i podczas kontroli przez konduktora, też nie było problemu :) Bug został przechytrzony!

Przycisk nie reaguje. Po otwarciu konsoli widoczny błąd.
(Kliknij, aby powiększyć)

Co to za hackowanie!?

Jeśli dopiero zaczynasz przygodę z programowaniem, to z pewnością zastanawiasz się: “Można tak sobie zmieniać kod strony”? Czy to nie jest jakieś hackowanie? Otóż nie… Pamiętam, jak w czasach gimbazy roiło się w internecie od poradników, w stylu “Jak zarobić 99999 surowców w grze Plemiona”. Autorzy tych filmików po prostu zmieniali wartość w kodzie strony. Oczywiście taka zmiana w praktyce nie dawała zupełnie nic. Dlaczego? Tak samo, jak w przypadku dodania pola przy zakupie biletu. Twoja przeglądarka działa w taki sposób, iż pobiera kod strony i wykonuje go u Ciebie na komputerze. Dlatego też, skoro kod, znajduje się u Ciebie, to możesz też wprowadzać dowolne zmiany. Jednak są one widoczne tylko dla Ciebie i nie mają wpływu na innych graczy, czy klientów PKP. Przy ponownym otworzeniu strony choćby na Twoim komputerze, pliki serwisu zostaną ponownie pobrane z serwera, a wcześniej wprowadzone zmiany znikną.

Zreprodukowanie buga

Znacie ten dowcip, jak wchodzi tester do baru i zamawia 1 piwo, potem 999999 piw, oczywiście też 0 i -1 piwo? Sytuacja podobna jak na poniższym filmiku HRejterów.

Tak samo tutaj, prawdopodobnie trafiliśmy na pewien przypadek brzegowy (ang. edge case). Postanowiłem zreprodukować ten błąd i występuje on jedynie, kiedy w koszyku mamy jedną pozycję (bilety na jedno połączenie kolejowe). Dlatego pewnie w większości przypadków funkcjonalność działa poprawnie (kto używa koszyka dla jednego biletu!? ja :D). Ty też możesz go powtórzyć, wykonując kroki, które opisałem. To bardzo istotne, jeżeli pracujesz jako tester, żeby dokładnie opisywać jakie Twoje działania spowodowały błąd. Niezastąpiony może być też filmik obrazujący problem!

Dlaczego dla większej ilości biletów koszyk działa? Nie mam pojęcia. Jeśli chcesz, podejmij wyzwanie, przeanalizuj błąd i daj znać w komentarzu! A może pracujesz w PKP i zaraz zabierasz się za to zgłoszenie? Pamiętajcie na początek o odpowiednich testach, aby pokryć ten przypadek!

Ucz się na błędach

Błędy są po to, żeby się na nich uczyć! Najlepiej wiedzą o tym osoby odpowiadające za nasze bezpieczeństwo podczas podróży samolotem. Z katastrof lotniczych zawsze wyciąga się wnioski i wprowadza nowe procedury, aby coś podobnego się nie powtórzyło. Szczęście, iż to tylko sprzedaż biletów i nie spowoduje to żadnego zderzenia pociągów (co innego, kiedy od softu zależy ludzkie życie) :)

Jednak, aby nasze systemy nie miały takich defektów, konieczne jest ich odpowiednie przetestowanie. Chcesz wiedzieć jak pisać aplikacje, aby były testowalne? Zastanawiasz się, w jaki sposób pisać utrzymywalne testy? Zdradzę Ci teraz, od czego zacząć naukę. Zrób w tym kierunku pierwszy krok i już dzisiaj zaopatrz się w książkę Unit Testing Principles, Practices, and Patterns: Effective Testing Styles, Patterns, and Reliable Automation for Unit Testing, Mocking, and Integration Testing with Examples in C#. Tytuł niebywale długi jak nazwy klas w Javie, ale temat jest jednak obszerny :D

Wstań z kanapy! A raczej siadaj do komputera… Masz trochę czasu, zanim kurier zapuka do Twoich drzwi. Już teraz warto zajrzeć na blog autora książki Enterprise Craftsmanship - Vladimir Khorikov. Znajdziesz tam wiele o testach, ale też o Domain-Driven Design.

Nie wiem, czy wybrano psa, ale wiem, iż Ponglish czasem choćby na propsie

Lampo, czyli prawdziwy "Pies, który jeździł koleją" żył we Włoszech w latach 50. XX wieku.

Skoro jesteśmy już w temacie PKP, to warto przypomnieć, iż swego czasu kod na stronie przewoźnika był pośmiewiskiem internetu i spadła na niego fala hejtu. Ważne jest, aby krytykować sam kod, a nie programistów, którzy go napisali. Na pewno każdemu zdarzyło się napisać w karierze podobnego potworka :) Ale co z tymi Polskimi nazwami w kodzie? Czy ktoś w końcu wie czyWybranoPsa i dlaczego? Czy mieszanie Polskiego z Angielskim, tzw. “Ponglish” jest tutaj uzasadniony? Tego nie wiem, ale odejdźmy na chwile na bezpieczną odległość od torów i zadajmy sobie pytanie kiedy warto rozpatrzyć taki sposób zapisu.

Jak wiemy w IT zawsze “to zależy”. Super przykład podaje Łukasz Szydło w rozmowie z Mariuszem Gilem w podcaście Better Software Design O różnych odmianach Ubiquitous Language. Szczególne branżowe słownictwo może być ciężko przetłumaczalne, a jedną z najważniejszych zasad Domain-Driven Design jest utrzymanie języka biznesowego w kodzie. To podstawa do tego, aby nasz kod mógł ewoluować wraz z biznesem. Jeśli jesteśmy w stanie przetłumaczyć słownictwo domenowego 1 do 1, a biznes funkcjonuje w innym języku niż angielski, to warto utrzymywać słowniczek, żebyśmy nie mieli kilku wyrazów na to samo. Byłem już w projekcie, gdzie np. na paliwo raz było słowo Gas, a raz Fuel. Polski język ma sens, kiedy nie potrafimy przetłumaczyć dokładnie, albo w naszym języku mamy więcej określeń na to samo niż angielski. Łukasz daje przykład Glebogryzarki w branży rolniczej, a także instytucji finansowych. Po więcej odsyłam Cię do podcastu i na mój mailing. Jaki mailing? Wszystko masz poniżej :)

✉️ isCovid - jak to zrobić lepiej?

Kod PKP, chociaż jest już legacy, to nie można o nim zapomnieć. Na przepisanie prawdopodobnie nie ma czasu, a dochodzą nowe wymagania. Ostatnio jak mógł zauważyć uważny obserwator, doszła “szybka szpachla ifem” w postaci fragmentu if(ZakupBiletuUtil().isCOVID()). Taką implementację możnaby nazwać anty-wzorcem. Dostrzegam tutaj pewną przypadłość, którą można nazwać “schizofrenię kontekstów”. Jest to problem, o którym piszę więcej na mailingu Domain-Driven Design. Kod nie wie, czy funkcjonuje w kontekście epidemii, czy nie i musi co chwile pytać samego siebie czy isCovid. To rodzi potrzebę ciągłej ifologi i zagnieżdżania, tam, gdzie kod tak naprawdę powinen zostać rozdzielony na konteksty i uproszczony w myśl zasady “dziel i zwyciężaj”. Takie rozwiązanie może być dobrym sposobem na “Deadline-Driven Development”, ale z pewnością nie jest to inwestycja w przyszłość.

Chcesz uczynić swoje programowanie bardziej pragmatyczne? Marzysz, żeby modelować procesy biznesowe zamiast tabelek w bazie danych? Już pierwszą dawkę wiedzy otrzymasz po zapisaniu się na mailing Domain-Driven Design. Będziesz miał ze mną bezpośredni kontakt i dostaniesz praktyczne zadania prosto na swoją skrzynkę! ZAPISZ SIĘ TUTAJ

Inni też tym żyją

Idź do oryginalnego materiału