Cztery bugi, które kosztowały mnie po dniu — i w każdym kłamały moje narzędzia
Crawler wymyślający błędy, czarne zrzuty ekranu, middleware przesłonięte pustym folderem i pipeline renderujący bez renderera. Cztery stracone dni i jeden nawyk, który by je uratował.
Bugi, które kosztowały mnie najwięcej czasu, nigdy nie były trudnymi bugami. Trudne bugi są uczciwe — zgłaszają się, dają stack trace, naprawiasz je. Drogie są te, w których narzędzie kłamie ci z pełnym przekonaniem, a ty spędzasz dzień na debugowaniu czegoś zupełnie innego.
Oto cztery z ostatnich miesięcy. Każdy zjadł mniej więcej dzień. Tematycznie nie mają ze sobą nic wspólnego, kształtem są identyczne.
1. Crawler, który wymyślał błędy
Do kontroli własnych projektów puszczam crawler strony: zepsute linki, łańcuchy przekierowań, brakujące nagłówki, takie rzeczy. Przy jednej witrynie wrócił ze ścianą czerwieni — brakujące nagłówki bezpieczeństwa, problemy strukturalne, wyniki ściągnięte w dół na całej linii.
Spędziłem godziny na sprawdzaniu nagłówków, które w sposób oczywisty były wysyłane. curl je pokazywał. Przeglądarka je pokazywała. Crawler upierał się, że ich nie ma.
Prawdziwa przyczyna: crawler prosił o strony z akceptowaną kompresją Brotli i nie potrafił poprawnie zdekodować odpowiedzi w Brotli. Wszystko, co „przeczytał” z tych stron, było śmieciem, więc nie przechodziło nic, co sprawdzał. Wyniki były zaniżone na każdej pojedynczej stronie.
Wymuszenie gzipa naprawiło to natychmiast.
Czego się nauczyłem: gdy narzędzie zgłasza problem, potwierdź innym narzędziem, że problem istnieje, zanim zaczniesz naprawiać. Jedno curl -I na starcie uratowałoby ten dzień, bo od razu pokazałoby sprzeczność.
2. Zrzuty ekranu, które były czarne
Robiłem grę w Unity i chciałem, żeby build sam się fotografował w ramach automatycznej kontroli — zrzut ekranu, spojrzenie na niego, potwierdzenie, że scena naprawdę się renderuje.
Nałożyły się dwie pułapki. Po pierwsze, renderowanie kamery do tekstury łapało scenę 3D, ale po cichu pomijało interfejs debugowy rysowany w trybie immediate — czyli dokładnie ta informacja, którą chciałem zweryfikować, nigdy nie trafiała do obrazu. Gdy potem przeszedłem na przechwytywanie ekranu na poziomie systemu, macOS zwrócił całkowicie poprawny, całkowicie czarny PNG. Żadnego błędu. Żadnego pytania o uprawnienia. Sama czerń.
Zielony ptaszek nad pipeline'em, który fotografował nic.
Czego się nauczyłem: niech testowana rzecz sama wytwarza dowód, od środka własnej pętli renderowania, i niech głośno się wywala, gdy nie może. Automatyczna kontrola, która nie odróżnia „wszystko gra” od „nic nie zobaczyłem”, jest gorsza niż jej brak, bo produkuje pewność siebie.
3. Middleware, które działało na produkcji, a lokalnie nie
Strona w Next.js miała middleware obsługujące przekierowania. Na produkcji działało. Lokalnie nie robiło absolutnie nic — żadnych logów, żadnego efektu, jakby plik nie istniał.
Sprawdziłem konfigurację matchera, lokalizację pliku, runtime, wersję Nexta. Wszystko w porządku.
Przyczyną był pusty katalog src/pages, pozostałość po starej migracji. Samo jego istnienie zmieniało sposób rozwiązywania struktury projektu, a middleware z katalogu głównego wypadało z builda. Usunięcie pustego folderu naprawiło sprawę.
Trop był w manifeście builda — middleware po prostu nie było na liście. Znalazłem to po godzinach rozmyślań nad własnym kodem, choć mogłem znaleźć w minutę, pytając, co framework sądzi, że zbudował.
Czego się nauczyłem: gdy zachowanie różni się między dwoma środowiskami z tym samym kodem, przestań czytać kod. Porównaj oba wyniki builda. Różnica jest w tym, co zostało zbudowane, nie w tym, co napisałeś.
4. Renderer, który nie miał renderera
Automatyzując Blendera bez GUI do produkcji wideo, miałem pipeline, który przechodził od początku do końca bez błędów i nie produkował żadnego pliku wideo. Dwie osobne przyczyny, odkryte po kolei: build, którym sterowałem, w ogóle nie miał dostępnego writera FFmpeg, a API, na którym opierałem krzywe animacji, zostało usunięte w wersji, na którą się przesiadłem. A gdy klatki wreszcie zaczęły wychodzić, domyślne zarządzanie kolorem zamieniło płaskie kreskówkowe barwy w wyprane szarości — co nie jest bugiem, tylko ustawieniem domyślnym robiącym dokładnie to, do czego je zaprojektowano, w kontekście, w którym jest złe.
Czego się nauczyłem: przy każdym narzędziu bez GUI sprawdź, czy dana możliwość w ogóle istnieje, zanim zbudujesz workflow, który na niej stoi. Jedna linijka pytająca z góry „umiesz zapisać ten format?” zamiast wnioskowania z braku pliku na końcu.
Wspólny kształt
Wszystkie cztery mają tę samą strukturę:
- Narzędzie coś zgłosiło — awarię, sukces, pusty wynik.
- Uwierzyłem zgłoszeniu.
- Zgłoszenie było artefaktem narzędzia, nie faktem o moim systemie.
Rozwiązaniem nie jest nieufność wobec wszystkiego. Jest tańsze: zanim zaczniesz działać na podstawie zgłoszenia, potwierdź ten sam fakt z jednego niezależnego kierunku. Drugie narzędzie, surowe zapytanie, wynik builda, bezpośrednie pytanie o możliwość. Kosztuje minutę i albo potwierdza twoją rzeczywistość, albo natychmiast demaskuje kłamstwo.
Drugi nawyk jest surowszy: traktuj cichy sukces jako porażkę, dopóki nie udowodnisz inaczej. Czarny zrzut ekranu przeszedł. Pusty pipeline wideo zakończył się czysto. Middleware zawiodło, nie robiąc nic. W każdym z tych przypadków system informował mnie, że coś jest nie tak, milczeniem — a dzień straciłem tylko dlatego, że odczytałem milczenie jako zgodę.
Nic z tego nie jest wyrafinowane. To po prostu różnica między debugowaniem własnego kodu a debugowaniem własnych przyrządów — i świadomością, które z dwojga akurat robisz.