Czy nasze sprawdziany mówią prawdę
Jakiś czas temu opisałem polowanie na opcje, które istnieją, ale nic nie robią. Skończyłem je z listą trzydziestu kilku poprawek i całkiem dobrym samopoczuciem. A potem przyszła myśl, od której zrobiło mi się nieprzyjemnie:
skąd wiem, że rzeczy, którymi to wszystko sprawdzam, nie kłamią tak samo?
Program ma zestaw automatycznych sprawdzianów. Uruchamiam je, widzę „OK", idę dalej. To „OK" jest dokładnie takim samym komunikatem jak suwak, który wygląda, że działa. Postanowiłem potraktować własne narzędzia tak, jak przed chwilą traktowałem program. Poniżej to, co znalazłem — bo okazało się gorzej, niż się spodziewałem.
Sprawdzian, którego nie dało się nawet uruchomić
Zacząłem od czegoś banalnego: uruchomiłem wszystkie po kolei. Nigdy wcześniej tego nie robiłem — zwykle odpala się ten, którego akurat dotyczy zmiana.
Jeden nie dawał się zbudować. Nie „nie przechodził" — nie dawał się skompilować, od dłuższego czasu. Ktoś kiedyś wydzielił kawałek kodu do nowego pliku i nie dopisał go do listy tego sprawdzianu. Nikt tego nie zauważył, bo te sprawdziany są celowo wyłączone ze zwykłego budowania programu — uruchamia się je ręcznie.
To nie była bramka, która przepuszcza błędy. To była bramka, której nie było.
„OK" wypisane przez wczorajszy program
Chwilę później zobaczyłem coś jeszcze przyjemniejszego. Poprawiłem sprawdzian, uruchomiłem, zobaczyłem „OK" — i dopiero po chwili zauważyłem, że kompilacja zakończyła się błędem. Uruchomił się poprzedni, nieaktualny plik wykonywalny, który leżał sobie z poprzedniego razu.
Zielony wynik pochodził od programu sprzed moich zmian. Od tej pory kasuję plik wykonywalny przed każdym uruchomieniem — jeśli go nie ma, nie da się przeczytać wyniku, którego nie było.
Sprawdzian, który przeżył katastrofę, przed którą miał chronić
Ten jest najciekawszy, bo zdarzył mi się dwa razy tego samego dnia i za drugim razem dalej go nie przewidziałem.
Jest tylko jeden sposób, żeby dowiedzieć się, czy sprawdzian naprawdę czegoś pilnuje: celowo zepsuć to, czego pilnuje, i zobaczyć, czy zaświeci się na czerwono. Robię to teraz przy każdym nowym sprawdzianie.
Pierwszy przypadek: pilnowałem, żeby pewna operacja nie zniszczyła przezroczystości obrazu. Napisałem sprawdzenie „przy pełnej sile efektu wynik zachowuje przezroczystość". Wyglądało sensownie. Zepsułem kod — i sprawdzian przeszedł. Przy pełnej sile wynik pochodzi w całości z efektu, więc tło, o które chodziło, w ogóle nie bierze udziału w rachunku. Sprawdzałem to jedyne miejsce, w którym błędu nie da się zobaczyć.
Drugi przypadek, kilka godzin później: sprawdzenie „trzy składowe koloru są sobie równe, czyli wynik jest szary". Zepsułem zabezpieczenie przed dzieleniem przez zero — i znowu przeszło. Bo katastrofa zniszczyła wszystkie trzy składowe po równo, a jednakowy śmieć nadal jest sobie równy. Sprawdzałem, czy wynik jest szary; czerń też jest szara.
Wniosek, który zapisałem sobie grubo: sprawdzenie mówiące o zależności między wartościami jest słabsze niż sprawdzenie samej wartości. Zależność przeżywa awarię, która niszczy wszystkie strony jednakowo. Teraz sprawdzam konkretną liczbę.
Zabezpieczenie, które ogłosiłem, a nie włączyłem
Jedna z poprawek polegała na tym, żeby kompilator sam pilnował, czy nie zapomniano obsłużyć któregoś przypadku — bo właśnie takie zapomnienie kosztowało wcześniej znikające rysunki. Napisałem kod, dopisałem komentarz „od teraz pilnuje tego kompilator", zbudowałem — zielono.
I dopiero wtedy sprawdziłem, czy to w ogóle działa. Nie działało. To konkretne ostrzeżenie jest domyślnie wyłączone i trzeba je włączyć osobno. Bez tego mój komentarz był po prostu nieprawdą zapisaną w kodzie — czyli tym samym gatunkiem kłamstwa, na które polowałem cały dzień, tylko lepiej ubranym.
Włączyłem, i to jako błąd, nie ostrzeżenie: przy setkach linii budowania ostrzeżenie ginie, a to ma kogoś zatrzymać, nie umilić mu dzień. Potem usunąłem jeden przypadek, żeby zobaczyć, że faktycznie zatrzymuje.
Instrukcja, która odradzała działającą funkcję
Na koniec zajrzałem do dokumentacji. Znalazłem dwa zdania opisujące ograniczenia, których już nie ma — funkcje zostały dorobione, a zdania nikt nie cofnął.
To szkodzi inaczej niż martwa opcja i chyba gorzej. Martwa opcja mówi „kliknij, nic się nie stanie" — prędzej czy później ktoś to zgłosi. Nieaktualne ograniczenie mówi „nawet nie próbuj" i brzmi wiarygodnie. Użytkownik omija działającą funkcję i nigdy tego nie zgłasza, bo z jego punktu widzenia wszystko zgadza się z instrukcją. Taki błąd sam się maskuje.
Przy okazji: publiczna lista zmian na stronie była pięć dni w tyle. Poprawiłem plik w repozytorium, zameldowałem sobie „zrobione" — i dopiero potem sprawdziłem samą stronę. Nadal pokazywała stare wpisy, bo stronę zasila baza, nie plik. Plik w repozytorium a to, co widzi użytkownik, to dwie różne rzeczy.
Nic z tego nie jest nową funkcją. Ale zauważcie, ile z tych historii ma ten sam kształt: coś wyglądało na zrobione, wyglądało na sprawdzone, wyglądało na zabezpieczone — i nie było. Za każdym razem wystarczyło zadać jedno pytanie: skąd wiem?
Dla mnie płynie z tego dnia jeden konkretny nawyk. Zielony wynik nie znaczy nic, dopóki nie zobaczyłem, że ten sam sprawdzian potrafi wyglądać inaczej. To kosztuje dodatkową minutę na każdy napisany sprawdzian — i dzisiaj wyłapało cztery, które ładnie wyglądały i niczego nie pilnowały.