Wyszukiwanie błędów w skryptach

javascript js code 1486361 1920 Wyszukiwanie błędów w skryptach
Rate this post

Autor: Paweł Rajewski

Ile razy twój nowy skrypt nie chciał funkcjonować? U mnie jest to zjawisko nagminnie. Drobne błędy, literówki, czasem zła koncepcja lub nie do końca przemyślany algorytm – wszystko to kończy się komunikatem „Błąd na stronie”. Jak sobie z tym poradzić?

Po pierwsze, włączyć odpowiednie ustawienia przeglądarki. W Internet Explorerze 5.5 na karcie: Narzędzia\Opcje internetowe\Zaawansowane zaznacz opcję „Wyświetl powiadomienie o każdym błędzie skryptu”. Od teraz Explorer będzie informował gdzie i jakiego rodzaju wystąpił błąd. Przykładowy komunikat:

Wiersz: 67 
Znak: 3 
Błąd: Brak definicji 'vTemo' 
Kod: 0 
Adres URL: file://C:\serwis\pajtemp.htm

Jak widać, w 67. wierszu kodu nastąpiło odwołanie do zmiennej vTemo, która nie była wcześniej zdefiniowana (no jasne, literówka, powinno być vTemp…). Komunikaty Explorera nie są może obszerne, ale wystarczają do zlokalizowania problemu.

Działanie inne niż zamierzone

Przypadek, gdy skrypt zatrzymuje się z komunikatem błędu jest prosty – wiemy co i gdzie się wydarzyło. Gorzej, gdy skrypt działa, ale wyniki jego pracy są inne od oczekiwanych. Gdy błąd jest ewidentny (np. wynik wynosi 2px zamiast 200px) zwykle łatwo odszukać przyczynę. Prawdziwy kłopot robi się wtedy, gdy błąd jest drobny, ale istotny np. obliczana współrzędna jest zawsze powiększana o 1px. Albo problem pojawia się wyłącznie w określonych sytuacjach. Jak znaleźć taki błąd?

Odpowiedź można uzyskać śledząc wartości zmiennych w czasie pracy skryptu. Najprostsza metoda to wstawienie w wybranym punkcie funkcji instrukcji:

window.alert(zmienna); 

…gdzie zmienna to nazwa zmiennej, której wartość chcielibyśmy poznać. W ten sposób można odczytać jaką wartość przybiera konkretna zmienna w konkretnym punkcie funkcji, co prawie zawsze pomaga w zlokalizowaniu błędu.

Czasami warto znać całą „historię” przetwarzanej w kilku krokach zmiennej bez przerywania skryptu okienkami alert(). W wybranych punktach funkcji można wstawić „pułapki”:

vTemp='|'; 
...instrukcje... 
vTemp+=zmienna+'|'; 
...instrukcje... 
vTemp+=zmienna+'|'; 
...instrukcje... 
vTemp+=zmienna+'|'; 
window.alert(vTemp); 

W efekcie otrzymamy kolejne wartości zmiennej w kolejnych punktach funkcji.

W pewnych sytuacjach samo przerwanie skryptu może być źródłem błędu (choć zdarza się to rzadko). Internet Explorer nie odświeża ekranu podczas wykonywania skryptu. Operacje na elementach strony przeprowadzane są „w pamięci”, a efekty wyprowadzane na ekran dopiero po zakończeniu wszystkich instrukcji. Jeśli przerwiemy funkcję (np. okienkiem alert), ekran zostanie uaktualniony do stanu bieżącego, co może mieć wpływ na działanie kolejnych instrukcji gdy praca skryptu zostanie wznowiona. Jeśli przerwanie funkcji jest niewskazane, można wyświetlić wyniki „pomiarów” na pasku statusu (choć, niestety, mieści się tam niewiele informacji):

window.status=zmienna; 

Wyłączanie instrukcji

Bardzo prostą i skuteczną techniką poszukiwania błędów jest wyłączanie grup instrukcji przy pomocy komentarzy:

to zostanie wykonane; 
/* 
a ta podejrzana instrukcja nie; 
i ta też nie; 
*/ 
a to znowu będzie wykonane; 

W ten sposób można ograniczyć pole poszukiwań lub czasowo zablokować fragmenty skryptu, które w poszukiwaniach przeszkadzają. Jeśli skrypt wykonuje kilka kolejnych kroków przetwarzających dane, znacznie szybciej znajdziemy przyczynę kłopotu badając te kroki osobno niż wszystkie jednocześnie.

Niewłaściwe zmienne

Zdarza się, że błąd powodowany jest niewłaściwym rodzajem zmiennej. Zmienna ala może zawierać liczbę, łańcuch, tablicę, wartość boolowską, skrót (obiekt), albo… nic (i to „nic” na parę różnych sposobów!). Co więcej, rodzaj zmiennej może zmieniać się w czasie zależnie od przeprowadzanych na niej operacji. A czymś innym jest dodawanie: x=5+5, a czym innym x=5+’5′ (pierwsze da w wyniku liczbę 10, drugie łańcuch ’55’). Może zdarzyć się i tak, że typ zmiennej będzie prawidłowy, tylko wynik, jaki ona zawiera – nie. Oto dość złośliwy przykład:

x=5; 
x=x+1-1; 
y='5'; 
y=y+1-1; 

Po zakończeniu obliczeń obydwie zmienne będą liczbami, z tym, że x=5, a y=50. Co gorsze, testując zawartość zmiennych x i y na początku funkcji, zobaczymy cyfrę 5, bo przecież zarówno liczba, jak i łańcuch na oko wyglądają identycznie…

Aby sprawdzić z czym naprawdę mamy do czynienia, można wstawić w wybranym punkcie funkcji:

window.alert(zmienna+' '+typeof(zmienna)); 

W ten sposób otrzymamy informację nie tylko o wartości, ale i o typie zmiennej. Może to pomóc w zlokalizowaniu „niewykrywalnego” inaczej błędu.

W tym miejscu warto wspomnieć, że operator porównania (==) może nie wykryć różnicy w typach zmiennych i „wpuścić” niewłaściwą wartość tam, gdzie nie powinna mieć wstępu. Porównanie: 5==’5′ da wynik true choć obydwie wartości są w rzeczywistości czymś innym. Podobnie wynikiem true zakończą się porównania: false==0; 0==”; 0==’0000′. Takie zachowanie bywa wygodne, ale gdy trzeba dokładnie porównać zmienne, biorąc pod uwagę zarówno wartość jak i typ, lepiej stosować operator identyczności: === (trzy znaki równości).

Nadmierny zasięg zmiennych

Wiadomo, że zmienne dzielą się na lokalne i globalne. Lokalne to te, które zdefiniowane są wewnątrz funkcji, globalne – poza funkcją. Teoretycznie zmienne lokalne znikają po zakończeniu funkcji, a jednak zdarza się, że pozostają, a nawet (w pechowych sytuacjach) nadpisują zmienne globalne! Dlaczego? Prawdopodobnie dlatego, że definiując zmienną wewnątrz funkcji zapomnieliśmy podać instrukcję var. Zmienna zadeklarowana jako: var x=5; będzie miała zasięg lokalny, ale zmienna zadeklarowana w sposób skrótowy: x=5; zawsze będzie globalna, nawet jeśli została zadeklarowana wewnątrz funkcji! Jeśli więc funkcja zawiera pętlę:

for(i=0;i<10;i++){}; 

…to po zakończeniu funkcji pozostanie zmienna globalna i zawierająca liczbę 10. Aby tego uniknąć, należy deklarować wszystkie zmienne występujące w funkcji w sposób pełny. Wtedy nie pozostaną po funkcji „resztki” zabierające pamięć i mogące sprawiać problemy w dalszych częściach skryptu:

var i; 
for(i=0;i<100;i++){}; 
  ...lub... 
for(var i=0;i<10;i++){}; 

Powolne działanie

Czasami skrypt działa poprawnie, ale zbyt wolno. Widoczne opóźnienia rzadko mają charakter ogólny – zwykle można zidentyfikować instrukcje lub funkcje działające najwolniej i spróbować je zoptymalizować. Pojawia się więc pytanie: jak znaleźć najbardziej czasochłonne fragmenty skryptu? Oto prosta metoda:

vTim=(new Date()).getTime(); 
...instrukcje... 
window.status=(new Date()).getTime()-vTim+' ms'; 

Ta konstrukcja to miernik czasu, jaki upłynął podczas wykonywania instrukcji. Najpierw pobierany jest z zegara aktualny czas w ms, następnie wykonywane są instrukcje, po czym ponownie pobierany jest czas, obliczana różnica, a wynik wyświetlany na pasku statusu. W ten sposób, obejmując „miernikiem” kolejne grupy instrukcji można zidentyfikować fragment wprowadzający największe opóźnienia.

W praktyce nie jest to metoda pomiarowa w sensie laboratoryjnym (choć dokładność – 1/1000 sek. mogłaby to sugerować). Szybko zorientujemy się, że wyniki pomiarów są różne przy kolejnych uruchomieniach skryptu. Niemniej przy szukaniu głównych „hamulcowych” ten prosty sposób sprawdza się znakomicie.

Szybkie uruchamianie funkcji i odczytywanie zmiennych

Bywa, że warto uruchomić funkcję zawartą na stronie, ale z innym niż zwykle parametrem (np. podsuwając jej jakąś wartość testową). Można to zrobić zmieniając kod strony. Można też prościej – wpisując na pasku adresowym:
javascript:void funkcja(parametry)

…gdzie funkcja to nazwa funkcji, a parametry to parametry, jakie do niej przekazujemy. Przykład:

<HTML> 
<BODY> 
<SCRIPT TYPE="text/Jscript" LANGUAGE="JScript"> 
function kolor(sBC) 
{window.document.body.style.backgroundColor=sBC;}; 
</SCRIPT> 
Test 
</BODY> 
</HTML> 

Uruchom stronę, a następnie wpisz na pasku adresowym: javascript:void kolor(’#FF00FF’) Dużą zaletą tej metody jest nie przeładowywanie strony, co umożliwia dostęp do aktualnego stanu zmiennych.

W ten sposób można też odczytać bieżący stan zmiennych czy właściwości obiektów. Np:

javascript:alert(document.fileSize) 

Da w wyniku rozmiar pliku;

javascript:alert(document.fileCreatedDate)

Datę utworzenia pliku;

javascript:alert(document.images.length)

Ilość obrazków osadzonych na stronie;

javascript: alert(document.body.currentStyle.backgroundColor) 

Aktualny kolor tła strony, itd.

Podając jako parametr metody alert() ścieżkę do dowolnej właściwości lub zmiennej globalnej, można wyświetlić jej aktualną wartość.

Niekończące się pętle

Jednym z najgorszych błędów, jakie mogą przytrafić się w czasie testowania skryptów, są niekończące się pętle. Ot, tworzymy pętlę while, podajemy warunek, który zawsze zostanie spełniony, uruchamiamy skrypt i… katastrofa. Przeglądarka nie reaguje. Nie wpadajmy jednak w panikę. Po pewnym czasie Internet Explorer wykryje, że dzieje się coś niewłaściwego i zaproponuje przerwanie skryptu (zwykle po 1-2 mln „obrotów”). Zajmuje to kilka sekund do kilkudziesięciu minut – zależnie od konstrukcji zapętlonej funkcji (przy Fc=300 MHz). W bardziej skomplikowanych przypadkach trzeba czekać tak długo, że sensowniejszym wyjściem jest tradycyjny Ctrl+Alt+Del… Jeśli wypadek zdarzy się w podglądzie wewnętrznym Pajączka2000, może zakończyć się dłuuugim czasem bezczynności albo utratą niezapisanego projektu. Dlatego lepiej testować strony w podglądzie zewnętrznym. Ewentualne błędy w skryptach nie zablokują nam edytora.

Położenie skryptu

Na zakończenie błąd przytrafiający się czasem osobom początkującym. To odwołanie do „nieistniejącego” obiektu. A dokładniej do obiektu nie istniejącego w chwili uruchamiania całkowicie poprawnego skryptu.

Kod strony przetwarzany jest w takiej kolejności, w jakiej jest zapisany. A to znaczy, że skrypt umieszczony w sekcji HEAD czytany jest w chwili, gdy nie istnieje jeszcze sekcja BODY, ani w ogóle żadna „widzialna” zawartość serwisu! Jeżeli skrypt zawiera instrukcje uruchamiane natychmiast (położone poza funkcjami) i odwołujące się do czegokolwiek umieszczonego w sekcji BODY, nastąpi błąd.

Można przyjąć zasadę, że skrypt uruchamiany od razu „widzi” jedynie ten fragment strony, który wpisany jest w kodzie przed nim. Dotyczy to również kolekcji takich jak all, links czy images, które uzupełniane są na bieżąco w trakcie ładowania strony, i w chwili uruchamiania skryptu mogą zawierać mniej pozycji niż po zakończeniu ładowania. Umieszczając skrypt na stronie trzeba więc upewnić się, że wymagane obiekty zostaną załadowane przed jego uruchomieniem.

Powodzenia w walce z opornymi skryptami.

Paweł Rajewski

Skomentujesz?

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Administratorem Twoich danych osobowych będzie Rafał Płatek, prowadzący działalność gospodarczą pod firmą CREAM.SOFTWARE RAFAŁ PŁATEK, wpisaną do rejestru ewidencji gospodarczej CEiDG pod numerem NIP 681-112-89-55. Szczegóły związane z przetwarzaniem danych osobowych znajdziesz w polityce prywatności.