Zarządzanie złożonością

Utrzymywanie prostej aplikacji internetowej może być zaskakująco skomplikowane. W tym module dowiesz się, jak internetowe interfejsy API współpracują z wątkami i jak można ich używać na potrzeby typowych wzorców PWA, takich jak zarządzanie stanem.

W swoim materiale Simple Made Easy Rich Hickey omawia właściwości prostych i skomplikowanych rzeczy. Opisuje proste rzeczy jako skupiające się na:

„1 rola, 1 zadanie, 1 koncepcja lub 1 wymiar”.

Podkreśla jednak, że prostota nie dotyczy:

„Posiadanie jednej instancji lub wykonywanie jednej operacji”.

To, czy coś jest proste, zależy od tego, jak bardzo są one ze sobą powiązane.

Złożoność wynika z wiązania, tkania elementów, czyli łączenia elementów w jedną całość. Złożoność możesz obliczyć, zliczając liczbę ról, zadań, koncepcji lub wymiarów, którymi zarządza.

Prostota ma kluczowe znaczenie przy tworzeniu oprogramowania, ponieważ prosty kod jest łatwiejszy do zrozumienia i utrzymania. Prostota jest również niezbędna w przypadku aplikacji internetowych, ponieważ może pomóc nam uczynić ją szybką i dostępną w każdym kontekście.

Zarządzanie złożonością PWA

Cały skrypt JavaScript, który tworzymy dla stron internetowych, w pewnym momencie dotyka głównego wątku. Wątek główny jest jednak już gotowy i nie ma nad nim kontroli.

Wątek główny to:

  • Odpowiada za rysowanie strony, która jest złożonym, wieloetapowym procesem obejmującym obliczanie stylów, aktualizowanie i komponowanie warstw oraz malowanie na ekran.
  • Odpowiada za wychwytywanie zdarzeń i reagowanie na zdarzenia, w tym zdarzenia takie jak przewijanie, i reagowanie na nie.
  • Odpowiada za wczytanie i usunięcie strony.
  • Zarządzanie multimediami, bezpieczeństwem i tożsamością To wszystko, zanim zostanie uruchomiony w tym wątku dowolny napisany przez Ciebie kod, na przykład:
  • Manipulowanie DOM.
  • Dostęp do poufnych interfejsów API, na przykład do funkcji urządzenia lub multimediów, zabezpieczeń lub tożsamości.

W swoim rozmowie z Chrome Dev Summit w 2019 roku Surma stwierdził, że główny wątek jest przeciążony i niedopłacony.

Mimo to większość kodu aplikacji znajduje się też w wątku głównym.

Cały ten kod zwiększa złożoność wątku głównego. Wątek główny jest jedynym, za pomocą którego przeglądarka może rozmieszczać i renderować treści na ekranie. Jeśli więc Twój kod wymaga coraz większej mocy obliczeniowej, musimy go uruchamiać szybko. Każda sekunda potrzebna do wykonania logiki aplikacji to sekunda, której przeglądarka nie może odpowiedzieć na dane wejściowe użytkownika ani ponownie wyświetlić strony.

Gdy interakcje nie łączą się z danymi wprowadzanymi przez dane, spada liczba klatek lub zbyt długo korzysta z witryny, użytkownicy są sfrustrowani, mają wrażenie, że aplikacja działa, a ich zaufanie maleje.

Złe wieści? Skomplikowanie wątku głównego to niemal niezawodny sposób, który utrudnia osiągnięcie tych celów. Mamy jednak dobrą wiadomość. Ponieważ wątek główny musi robić to w sposób jasny, może on służyć jako wskazówka, która pomaga ograniczyć zależność od niego w pozostałej części aplikacji.

Odseparowania potencjalnych problemów

Aplikacje internetowe mogą wykonywać wiele różnych czynności, ale ogólnie rzecz biorąc, można je podzielić na zadania, które bezpośrednio wpływają na interfejs użytkownika, a inne – na te, które nie są wykonywane. Interfejs to:

  • Bezpośrednio dotyka DOM.
  • Korzysta z interfejsów API, które analizują możliwości urządzenia, na przykład powiadomienia czy dostęp do systemu plików.
  • dotyczy tożsamości, na przykład plików cookie użytkownika, pamięci lokalnej lub pamięci sesji.
  • Zarządza multimediami, np. obrazami, dźwiękiem lub filmami.
  • związane z bezpieczeństwem, które wymagają zatwierdzenia interwencji użytkownika, na przykład używanie internetowego interfejsu API;

Praca bez interfejsu użytkownika może obejmować:

  • Czyste obliczenia.
  • Dostęp do danych (pobieranie, IndexedDB itp.).
  • Kryptowaluty.
  • Wiadomości.
  • Tworzenie blobów lub strumieni albo manipulacje.

Zadania inne niż UI są często zarezerwowane przez interfejs użytkownika: użytkownik klika przycisk wywołujący żądanie sieciowe dla interfejsu API, które zwraca przeanalizowane wyniki, które są następnie używane do aktualizacji DOM. Przy pisaniu kodu często brane są pod uwagę takie kompleksowe wrażenia, ale w sytuacjach, gdy nie występują poszczególne części tego procesu. Granice między pracą w interfejsie a pracą bez interfejsu są równie ważne, jak usługi kompleksowe, ponieważ to pierwszy sposób, w którym można zmniejszyć złożoność głównego wątku.

Skoncentruj się na jednym zadaniu

Jednym z najprostszych sposobów uproszczenia kodu jest podział funkcji, aby każda z nich skupiała się na jednym zadaniu. Zadania można określić na podstawie granic określonych w ramach kompleksowej obsługi:

  • Najpierw zareaguj na informacje podane przez użytkownika. To jest interfejs użytkownika.
  • Następnie wyślij żądanie do interfejsu API. To zadanie niewymagające interfejsu użytkownika.
  • Następnie przeanalizuj żądanie do interfejsu API. To zadanie nie dotyczy UI.
  • Następnie określ zmiany w DOM. Może to być interfejs użytkownika, a jeśli używasz np. wirtualnej implementacji DOM, może on nie działać w UI.
  • Na koniec wprowadź zmiany w DOM. To jest interfejs użytkownika.

Pierwsze wyraźne granice to granica między pracą w interfejsie użytkownika a pracą poza nim. Trzeba też wykonać czynności oceny: czy tworzysz i analizuje żądanie do interfejsu API jedno czy dwa zadania? Jeśli zmiany DOM nie działają w interfejsie, czy można je połączyć z interfejsem API? W tym samym wątku? Jesteś w innym wątku? Odpowiedni poziom rozdzielenia ma kluczowe znaczenie dla uproszczenia bazy kodu i skutecznego przenoszenia jej fragmentów z wątku głównego.

Możliwość kompozycji

Jeśli chcesz podzielić duże, kompleksowe przepływy pracy na mniejsze części, musisz zastanowić się nad kompozycją swojej bazy kodu. Chcąc wykorzystać wskazówki z programowania funkcjonalnego, warto pamiętać o tych kwestiach:

  • Podział rodzajów pracy wykonywanej przez aplikację.
  • Tworzymy dla nich wspólne interfejsy wejściowe i wyjściowe.

Na przykład wszystkie zadania pobierania danych z interfejsu API przyjmują i zwracają tablicę obiektów standardowych, a następnie zwracają tablicę standardowych obiektów.

JavaScript ma algorytm klona ustrukturyzowanego przeznaczony do kopiowania złożonych obiektów JavaScript. Instancje robocze używają go do wysyłania wiadomości, a IndexedDB – do przechowywania obiektów. Wybór interfejsów, których można używać z algorytmem klonowania uporządkowanych, sprawi, że będą one jeszcze bardziej elastyczne w działaniu.

Mając to na uwadze, możesz utworzyć bibliotekę funkcji kompozycyjnych, kategoryzując kod i tworząc wspólne interfejsy wejścia-wyjścia dla tych kategorii. Kod kompozycyjny to cecha charakterystyczna prostych baz kodu: luźno sprzężonych, wymiennych elementów, które mogą znajdować się „obok” i oparte na sobie, w przeciwieństwie do kodu złożonego, który jest głęboko ze sobą połączony i dlatego nie może być łatwo oddzielony. W internecie kod kompozycyjny może oznaczać różnicę między przepełnieniem głównego wątku.

Mając kod kompozycyjny, nadszedł czas, aby część z nich zniknąć z wątku głównego.

Zmniejszanie złożoności za pomocą narzędzi internetowych

Narzędzia internetowe, które często nie są wykorzystywane, ale są powszechnie dostępne, pozwalają odejść od głównego wątku.

Instancje robocze pozwalają aplikacji PWA uruchamiać (niektóre) JavaScript poza wątkiem głównym.

Istnieją 3 rodzaje instancji roboczych.

Dedykowane instancje robocze, które są najczęściej kojarzone z opisem pracowników sieciowych, mogą być używane przez pojedynczy skrypt w pojedynczej uruchomionej instancji PWA. W miarę możliwości zadania, które nie wchodzą w bezpośrednią interakcję z DOM, powinny być przenoszone do instancji roboczej, aby zwiększyć wydajność.

Współużytkowani pracownicy są podobni do dedykowanych instancji roboczych, z tą różnicą, że wiele skryptów może udostępniać je w wielu otwartych oknach. Zapewnia to korzyści dedykowanej instancji roboczej, ale ze wspólnym stanem i kontekstem wewnętrznym między oknami i skryptami.

Udostępniona instancja robocza może na przykład zarządzać dostępem i transakcjami do IndexedDB aplikacji PWA oraz przesyłać wyniki transakcji we wszystkich skryptach wywołujących, aby umożliwić reagowanie na zmiany.

Ostatnim, które zostały dokładnie omówione na tym kursie, są skrypty service worker, które działają jako serwery proxy dla żądań sieciowych i są współdzielone przez wszystkie instancje aplikacji PWA.

Zobacz, jak to działa

Czas na kodowanie! Opierając się na informacjach zdobytych w tym module, utwórz aplikację PWA od podstaw.

Zasoby