
Debug it! W tym artykule zajmiemy się testowaniem kodu i obsługą błędów w JavaScript.
Obsługa błędów JavaScript
Na początek trzeba by powiedzieć kilka słów o potrzebie testowania.
Testować trzeba wszystko! Najlepiej na każdym etapie tworzenia; im wcześniej wyłapiemy błędy, tym lepiej dla oprogramowania, które w pocie czoła tworzymy.
A jako twórcy musimy dbać o jakość kodu! Dobry kod to nie tylko kod przetestowany, ale również taki, który reaguje na trudne do przewidzenia sytuacje. Mówimy wtedy o obsłudze wyjątków, co JavaScript również wspiera.
Podstawowa obsługa błędów w JavaScript
Na początek prosty przykład: użycie onerror. Załóżmy, że przeglądarka spróbuje załadować obrazek z pliku, który nie istnieje.
Przykład – zdefiniowanie reakcji na określony błąd:
<img src="not_existing_foobar.png" onerror="alert('Wystąpił błąd')" />
Podczas nieudanej próby załadowania obrazka otrzymamy obsługę błędu (tutaj alert z informacją).
Idąc dalej. Przykład poniżej jest dość przydatny – informuje nas o tym, co poszło nie tak w naszym kodzie, gdy wystąpi jakiś błąd.
Przykład – przechwycenie i obsługa dowolnego błędu, np. nieistniejącej funkcji:
window.onerror = function(sMessage, sUrl, sLine) { alert("Błąd:\n" + sMessage + "\nURL: " + sUrl + "\n Numer wiersza: " + sLine); return true; } // wywołaj nieistniejącą funkcję: nonExistentFunction();
Obiekt typu Error
Obsługa błędów w JavaScript daje nam różne narzędzia. Jednym z nich jest tworzenie i rzucanie (throw) obiektów stworzonych do tego celu (Error). Obiekt taki przechowuje bardziej szczegółowe informacje dotyczące błędów.
Przykład – tworzenie obiektu typu Error w celu obsługi wyjątku:
function divide(iNum1, iNum2) { if (arguments.length != 2) { throw new Error("divide() wymaga dwóch argumentów."); } else if (typeof iNum1 != "number" || typeof iNum2 != "number") { throw new Error("divide() wymaga dwóch argumentów liczbowych"); } /* else if (iNum2.valueOf() == 0) { throw new Error("Nie dziel przez zero"); } */ return iNum1.valueOf() / iNum2.valueOf(); } alert(divide("a")); // alert(divide(2, 0)); // alert(divide("a", 0));
Bardziej szczegółowy opis obiektu Error na stronach Developer Mozilla.
Konstrukcja try… catch… finally
Konstrukcja tego typu występuje w wielu językach obiektowych, a służy do obsługi wyjątków.
W bloku try umieszczamy kod, którego działanie może zakończyć się w nieprzewidywalny sposób.
W bloku catch umieścimy kod obsługi wyjątku, który tu przechwycimy.
Natomiast w opcjonalnym bloku finally – kod, który wykona się niezależnie – zawsze po przetworzeniu bloku try i ewentualnie catch.
Przykład – podstawowe użycie try / catch w celu obsługi wyjątku:
try { window.nonExistentFunction(); alert("Metoda wykonana."); } catch (oException) { alert("Wystąpił wyjątek: " + oException.message); } finally { alert("Koniec instrukcji try… catch"); }
Możliwe jest nawet tworzenie zagnieżdżeń.
Przykład – zagnieżdżanie try / catch:
try { eval("a ++ b"); // instrukcja powoduje błąd } catch (oException) { alert("Wystąpił wyjątek."); try { // instrukcja powoduje błąd var aErrors = new Array(10000000000000000000000); push(exception); } catch (oException2) { alert("Wystąpił kolejny wyjątek."); } } finally { alert("Koniec rozbudowanego przykładu"); }
Poniżej przykład jeszcze mocniej związany z poruszanym wcześniej OOP. Pod lupą jest teraz użycie wspomnianego już obiektu typu Error.
W tym kodzie określamy typ obiektu błędu (rzuconego w wyjątku), tzn. jego instancji. Robimy to w celu podjęcia działania odpowiedniego dla danego typu.
Przykład – badanie typu błędu Error:
try { foo.bar(); } catch (e) { if (e instanceof EvalError) { alert(e.name + ": " + e.message); else if (e instanceof RangeError) { e.name + ": " + e.message); } // ... itd }
Jak na JavaScript to całkiem elegancki sposób.
Przyjrzyjmy się teraz instrukcji throw.
Instrukcja throw wywołuje wyjątek. Jako argument określamy wyrażenie zawierające wartość, która ma zostać wywołana.
Przykłady użycia instrukcji throw:
throw "Error2"; throw 47; throw true; throw { toString: function() { return "Jestem obiektem!"; } };
Asserts – asercje w JavaScript
Asercje to w skrócie konstrukcje umieszczane w kodzie, poprzez które programista zakłada prawdziwość wyrażenia, co przydatne jest w sprawdzaniu oprogramowania pod kątem luk i odporności na błędy. W JavaScript możemy za symulować użycie asercji.
Ten przykład używa funkcji assert() do wyrzucania własnych błędów JavaScript:
function assert(bCondition, sErrorMessage) { if (!bCondition) { throw new Error(sErrorMessage); } } function divide(iNum1, iNum2) { assert(arguments.length == 2, "divide() wymaga podania dwóch argumentów"); assert(typeof iNum1 == "number" && typeof iNum2 == "number", "divide() wymaga dwóch argumentów będących liczbami"); // ... obsługa innych przypadków ... return iNum1.valueOf() / iNum2.valueOf(); } alert(divide("a"));
Użycie konsoli (oraz debuggery i profilery)
No koniec słowo o użyciu konsoli w celu testowania kodu (w przeglądarkach wspierających takie rozwiązanie).
Przykład:
var ss = "Test"; console.log(ss);
Zwykle (chyba że są ku temu określone powody) należy pamiętać o wyłączaniu takich instrukcji z kodu produkcyjnego.
Większość z nas zna, i zapewne często używa genialnego narzędzia, jakim jest Firebug. Aż z niedowierzaniem wspominam czasy, gdy pisało się kody bez użycia tego narzędzia.
Firebug jest związany z Firefox’em, ale inne przeglądarki również dostarczając programiście podobnych rozwiązań. Są one nieocenione przy poszukiwaniu przyczyn problemów w kodzie JavaScript, HTML czy CSS.
Narzędzia developerskie w Google Chrome:
Czasem, gdy wykonuje się testy cross-browser i coś nie gra, nie sposób nie odpalić tych narzędzi.
Podsumowanie
Mam nadzieję że ta mała garść porad pomoże komuś w pisaniu jeszcze lepszego kodu, a przede wszystkim pozwoli spojrzeć szerzej na zagadnienie, jakim jest obsługa błędów JavaScript.
Za jakiś czas opiszemy więcej ciekawych narzędzi, jakimi może wspomóc się Web Developer, m.in: EcmaUnit, Selenium, JSLint, i inne.