Przez ostatnie cztery dni robocze walczyliśmy z jedną zagwozdką. Spory kawałek kodu, w teorii działa nieźle, w praktyce troszkę gorzej. Objawy:

  • pierwsze odpalenie procedury testowej - pomyślne
  • drugie odpalenie procedury testowej - failed (w tym samym requeście!)
  • ciekawostka: wystarczy gdzieś po drodze w testowanej funkcji odpalić funkcję zrzucającą zawartość struktury (w tym wypadku ref. do hasza), żeby oba testy zaczęły działać poprawnie
  • ciekawostka2: wystarczy dodać tą samą funkcję w innym miejscu, żeby np. pierwsze odpalenie testu wypadało nieprawidłowe, a drugie już działało OK

W sumie, dla nas to przez jakiś czas wyglądało na magię :( kombinowaliśmy na różne sposoby, już zacząłem prosić o włożenie cgi_dump()‘a (naszej funkcji dumpującej zawartość zmiennej) i przekierowanie jej wyjścia do /dev/null ;) W końcu wczoraj doprowadziliśmy do sytuacji, kiedy funkcja zaczęła się zachowywać prawidłowo, ale jeszcze nie znaleźliśmy konkretnej odpowiedzi: CZEMU?

Dziś, po długich bojach, połapaliśmy się w (chyba) wszystkim. Pomijając burdel w kodzie, tworzenia zmiennych do niczego nie potrzebnych, lub też deklarowaniu tych samych zmiennych po kilka razy w kodzie, i nazwy zmiennych typu: $data, @data, %data, sprawcą okazywała się konstrukcja while (($key, $value) = each %hash). Ta niewinna z pozoru konstrukcja posiada jednego ‘fiuczera’, o którym nie doczytaliśmy w dokumentacji, mianowicie:

There is a single iterator for each hash, shared by all each, keys, and values function calls in the program; it can be reset by reading all the elements from the hash, or by evaluating keys HASH or values HASH .

Czyli, przekładając na nieco bardziej ludzki opis: jeśli iterowałeś po haszu za pomocą each()‘a, w międzyczasie tą iterację przerwałeś, to wewnętrzny wskaźnik hasza znajdował się w miejscu w którym została przerwana iteracja. Następne wywołanie each()‘a zacznie się nie od początku hasza, tylko od miejsca w którym zostało przerwane. Oczywiście w niektórych przyapadkach może to być dobre, w innych - w tym naszym - niespecjalnie. Położenie tego wewnętrznego wskaźnika można zresetować np. poprzez użycie funkcji keys() lub values() na haszu, ale czyste (samodzielne) wywołanie tej funkcji jest jednak mało czytelne (a “samowyjaśniający” się kod jest lepszy od dodatkowych komentarzy). Jest jednak czytelniejszy sposób aby osiągnąć nasz cel, a którego nie omieszkaliśmy wykorzystać: zastąpiliśmy wszystkie wywołania konstrukcji while-each, konstrukcją foreach-keys, która według przeprowadzonych przeze mnie jakiś czas temu tesów, jest nawet nieznacznie szybsza! Co prawda traci się tutaj odrobinę na czytelności kodu, jednak przeskoczenie pewnych problemów, których rozwiązanie zajęło nam 4 dni, jest zdecydowanie tego warte.