Читать книгу Programowanie w TypeScript - Boris Cherny - Страница 12
JavaScript versus TypeScript
ОглавлениеTeraz przyjrzyjmy się systemowi typów w TypeScripcie i porównajmy go z systemem typów w JavaScripcie. Tabela 2.1 przedstawia zestawienie. Zrozumienie różnic daje wyobrażenie, w jaki sposób działa TypeScript.
Tabela 2.1. Porównanie systemów typów w JavaScripcie i TypeScripcie
Cecha systemu typów | JavaScript | TypeScript |
Jak wiązane są typy? | dynamicznie | statycznie |
Czy typy są automatycznie przekształcane? | tak | nie (z reguły) |
Kiedy sprawdzane są typy? | w czasie wykonania | w czasie kompilacji |
Kiedy ujawniają się błędy? | w czasie wykonania (z reguły) | w czasie kompilacji (z reguły) |
Jak wiązane są typy?
Dynamiczne wiązanie typów oznacza, że JavaScript musi tak naprawdę uruchomić nasz program, aby poznać zastosowane w nim typy. JavaScript nie zna typów przed uruchomieniem programu.
TypeScript jest językiem stopniowo typowanym. To oznacza, że TypeScript działa najlepiej, gdy w czasie kompilacji zna typy wszystkich obiektów w programie, ale może skompilować program nawet wtedy, gdy nie zna wszystkich typów. W programie, w którym typy nie zostały zdefiniowane, TypeScript potrafi automatycznie wywnioskować pewne typy i wychwycić niektóre błędy, jednak gdy nie zna wszystkich typów, może przeoczyć wiele błędów, które trafiają do użytkowników.
Stopniowe typowanie bardzo pomaga w migracji istniejącej bazy kodu JavaScriptu bez typów do TypeScriptu z typami (dodatkowe informacje można znaleźć w części „Stopniowa migracja z JavaScriptu do TypeScriptu” na stronie 230), ale w innych sytuacjach należy dążyć do uzyskania 100-procentowego pokrycia dla typów. Takie podejście zostało zastosowane również w tej książce, o ile nie zaznaczyliśmy inaczej.
Czy typy są automatycznie przekształcane?
JavaScript jest językiem o słabym typowaniu. To oznacza, że jeśli wykonamy nieprawidłowe działanie, np. dodamy liczbę do tablicy (jak w rozdziale 1), JavaScript zastosuje wiele reguł w celu odgadnięcia naszych intencji i spróbuje zrobić co może w danej sytuacji. Przeanalizujmy pewien przykład ilustrujący, w jaki sposób JavaScript wyznacza wynik działania 3 + [1]:
1 JavaScript zauważa, że 3 jest liczbą, a [1] jest tablicą.
2 Ponieważ używamy operatora +, zakłada, że chcemy połączyć je ze sobą.
3 W niejawny sposób przekształca 3 w łańcuch, co daje “3”.
4 W niejawny sposób przekształca [1] w łańcuch, co daje “1”.
5 Łączy wyniki, co daje “31”.
Moglibyśmy osiągnąć ten sam efekt również w jawny sposób (eliminując kroki 1, 3 i 4):
3 + [1]; // daje w wyniku “31”
(3).toString() + [1].toString() // daje w wyniku “31”
Podczas gdy JavaScript próbuje pomóc, automatycznie wykonując sprytne przekształcenia typów, TypeScript skarży się, gdy podejmujemy nieprawidłowe działanie. Gdy uruchomimy ten sam kod JavaScriptu za pomocą TSC, otrzymamy błąd informujący, że nie można stosować operatora ‘+’ na tych typach:
3 + [1]; // Błąd TS2365: Nie można zastosować operatora
// ‘+’ do typów ‘3’ i ‘number[]’.
(3).toString() + [1].toString() // daje w wyniku “31”
Jeśli podejmiemy działanie, które wydaje się nieprawidłowe, TypeScript zgłasza zażalenie i wycofuje je dopiero wtedy, gdy w jawny sposób wyrazimy nasze intencje. Takie zachowanie ma sens: komu przyszłoby do głowy dodawanie liczby do tablicy w celu otrzymania łańcucha (oczywiście poza Bavmordą, czarownicą JavaScriptu, która siedzi w piwnicy pewnego start-upa i programuje przy blasku świec)?
Tego typu niejawne przekształcania, jakie przeprowadza JavaScript, mogą być źródłem trudnych do zlokalizowania błędów i stanowią zmorę wielu programistów JavaScriptu. To utrudnia pojedynczym inżynierom wykonywanie zadań i dodatkowo komplikuje skalowanie kodu w dużych zespołach, ponieważ każdy członek zespołu musi zrozumieć niejawne założenia przyjęte w kodzie.
W skrócie, gdy przekształcanie typów jest konieczne, należy przeprowadzać je w sposób jawny.
Kiedy sprawdzane są typy?
W większości sytuacji JavaScript nie zwraca uwagi na przekazywane typy i próbuje jak najlepiej dostosować to, co otrzymał, do tego, czego oczekiwał.
Natomiast TypeScript sprawdza kod pod kątem typów w czasie kompilacji (drugi krok na przedstawionej na początku rozdziału liście), dlatego nie trzeba uruchamiać kodu, aby zobaczyć przedstawiony w poprzednim przykładzie błąd. TypeScript statycznie przeanalizuje nasz kod pod kątem tego rodzaju błędów i pokaże je przed uruchomieniem. Jeśli kod nie kompiluje się, często oznacza to, że popełniliśmy błąd i musimy naprawić go, zanim spróbujemy uruchomić kod.
Rysunek 2.2 ilustruje, co się stało po wpisaniu kodu ostatniego przykładu w VSCode (naszym ulubionym edytorze).
Rysunek 2.2. Błąd TypeError zgłoszony przez VSCode
Gdy wybrany edytor kodu ma dobre rozszerzenie TypeScriptu, błąd jest wskazywany już w czasie wpisywania za pomocą czerwonej falistej linii podkreślającej kod. To znacznie skraca czas między napisaniem kodu a dowiadywaniem się o błędzie i poprawianiem kodu w celu wyeliminowania tego błędu.
Kiedy ujawniają się błędy?
JavaScript zgłasza wyjątki lub przeprowadza niejawne przekształcenia typów dopiero w czasie wykonania2. To oznacza, że musimy uruchomić program, aby otrzymać cenną informację o tym, że wykonaliśmy nieprawidłowe działanie. W najlepszym przypadku zachodzi to w czasie testów jednostkowych, w najgorszym – przejawia się w postaci nieprzyjemnego e-maila od użytkownika.
TypeScript zgłasza błędy związane ze składnią i z typami w czasie kompilacji. W praktyce oznacza to, że tego rodzaju błędy pojawiają się w edytorze w czasie pisania kodu. Jest to niesamowite przeżycie dla osób, które nie miały wcześniej okazji programować w językach z typowaniem statycznym i kompilacją przyrostową3.
Niemniej istnieje wiele błędów, których TypeScript nie może wykryć w czasie kompilacji, takich jak: przepełnienie stosu, przerwane połączenia sieciowe czy niepoprawne dane wejściowe, które skutkują zgłoszeniem wyjątku w czasie uruchomienia. Zadaniem TypeScriptu jest wychwycenie w czasie kompilacji wielu błędów, które w przypadku użycia wyłącznie kodu JavaScriptu ujawniłyby się dopiero po uruchomieniu.