Krótka opowieść o switchach i alternatywach w JavaScripcie
Lubicie instrukcje warunkowe? Ja też nie… Są super i tak dalej, ale po napisaniu trzeciego z kolei else if powinniście zatrzymać się na chwilę i zastanowić, co robicie ze swoim życiem. Gdyby tylko istniał lepszy sposób na wykonanie konkretnego bloku kodu w zależności od zmiennej… Dobra, żarty na bok i przechodzimy do konkretów.
Spis treści
Rozdział pierwszy: „The Switch Situation”
Nie będę tutaj wchodzić w szczegóły, przypomnę tylko pokrótce, jak wygląda składnia konstrukcji switch:
``` switch(userInput) { case 'LEFT': x -= 1; break; case 'RIGHT': x += 1; break; case 'UP': y += 1; break; default: y -= 1; } ```
Jak widać, jest ona jasna i czytelna. Silniki przeglądarek różnie rozwiązują kwestię optymalizacji, ale najważniejsze jest to, że jeśli twój kod spełnia warunek znajdujący się na samym końcu konstrukcji switch, to nie musisz czekać, aż pozostałe warunki zostaną sprawdzone – od razu wykona się właściwa instrukcja. To oznacza, że liczba zdefiniowanych warunków nie ma wpływu na szybkość działania skryptu. Możemy też wielokrotnie wykonać tę samą operację sprawdzania dla różnych warunków, zachowując czytelność kodu.
Rozdział drugi: „Zapomniane opowieści”
Wiemy już, czym są instrukcje switch i jakie są ich plusy i minusy, ale switch to tylko jedna z możliwości rozwiązania naszego problemu. Podsumujmy krótko, jakie inne opcje mamy do dyspozycji:
Instrukcja if/else
Tej akurat możliwości staraj się unikać. if/else wprowadza bałagan w kodzie i spowalnia działanie skryptu. Jeżeli warunek, który sprawdzasz, jest uznany za prawdziwy (true) dopiero w dziesiątej z kolei instrukcji, to tak czy inaczej wszystkie wcześniejsze warunki muszą zostać sprawdzone. W większości sytuacji nie będzie to przeszkodą, jednak im więcej warunków, tym skrypt wolniejszy i mniej czytelny. Jeżeli na przykład używamy pętli, możemy pozbyć się wyrażenia else i wstawić wewnątrz każdego if instrukcję return, która natychmiast przerwie wykonywanie kodu, gdy tylko warunek zostanie spełniony.
``` if(userInput === 'LEFT') x -= 1; else if(userInput === 'RIGHT') x += 1; else if(userInput === 'UP') y += 1; else y -= 1; ```
Obiekty
Tak, nie przesłyszeliście się: obiekty! Chociaż generalnie służą one do czegoś innego, możemy “zhakować” je tak, żeby pasowały do naszych potrzeb. Mimo że instrukcja switch zawsze będzie na miejscu i znakomicie pasuje do imperatywnego stylu programowania, to jeżeli tak jak ja należycie do tych świrów, którzy uważają, że każda kolejna zadeklarowana zmienna to zło, pewnie będziecie chcieli ją jakoś ulepszyć. Oto jak można wykorzystać obiekty, aby wyglądały niemal jak programowanie funkcyjne:
``` (({ LEFT: () => (x -= 1), RIGHT: () => (x += 1), UP: () => (y += 1) })[userInput] || (() => (y -= 1)))(); ```
W tym przykładzie przypisujemy funkcje do poszczególnych kluczy, aby je wywołać. Jeżeli żądany klucz nie zostanie znaleziony w obiekcie, możemy też stworzyć domyślną funkcję, która zostanie uruchomiona w takiej sytuacji.
Tablice
Mieliśmy już obiekty, dlaczego więc zatem nie spróbować z tablicami? Możemy dostać się do konkretnego elementu w tablicy za pomocą jego indeksu, więc tablica zadziała podobnie jak obiekt – z tą różnicą, że zamiast nazwy klucza będziemy mieli liczbę.
``` (([ () => (x -= 1), () => (x += 1), () => (y += 1) ])[userInput] || (() => (y -= 1)))(); ```
Rozdział trzeci: Optymalizacja
Mam nadzieję, że lubicie matmę, liczby i kolorowe wykresy, bo mam dla was coś wyjątkowego. Przygotowałem benchmarki omówionych rozwiązań – możecie obejrzeć je tutaj i sprawdzić, które działa najszybciej na waszych komputerach.
W każdym teście przypisuję wartość do y bazując na stałej x, gdzie x jest zawsze umieszczony w ostatnim bloku if/case/właściwości, etc., żeby sprawdzić najgorszy scenariusz.
Tablica
Jak widać (albo i nie), wywoływanie funkcji wprost z tablicy nie jest najszybszym sposobem. Mówiąc wprost, jest on tak wolny, że nawet nie widać go na wykresie. To chyba najgorsze z omówionych rozwiązań: możemy używać tylko liczb, aby odwołać się do elementów tablicy, a co gorsza, te elementy mogą zmieniać swoją kolejność. Test prędkości mówi sam za siebie – odradzam!
Sytuacja jest jednak inna, gdy w tablicy używamy typów prymitywnych (górny czerwony pasek). Ku mojemu zaskoczeniu, ta opcja uzyskała najlepszy wynik w Chrome, a w przypadku Firefoksa była przynajmniej widoczna na wykresie – czy to znaczy, że warto jej używać? Powiedziałbym, że nie – tablice nie są przeznaczone do tego celu. Ale hej, na tym właśnie polega hakowanie.
Instrukcje if
Zarówno if (ciemnozielony pasek) jak i if/else (pomarańczowy pasek) uzyskały całkiem przyzwoity wynik. Powinny być one jednak dla nas punktem odniesienia, więc nie pozwólmy im jeszcze spocząć na laurach. Warto tutaj zauważyć, że instrukcje z else nie są wcale wolniejsze. Oczywiście wynik będzie zależał od silnika przeglądarki i konkretnego kodu, ale w tym przykładzie uzyskałem właśnie taki rezultat. Jako że wylałem już sporo żółci na używanie wielu ifów jeden po drugim, możemy zakończyć ten temat i przejść dalej.
Instrukcje switch
Tu zaczyna się zabawa. Przygotowałem trzy testy dla instrukcji switch. W dwóch z nich w sprawdzanych warunkach używam liczb (fioletowa linia) i stringów (jasnozielona linia) – oba przypadki widoczne są na obrazku powyżej.
Liczby wypadają lepiej w silnikach SpiderMonkey (Firefox) i V8 (Chromium). Czy to znaczy, że warto używać tego sposobu? Raczej nie, biorąc pod uwagę, że i tak trzeba przypisać te liczby do jakichś konkretnych wartości. W przypadku stringów natomiast kod jest znacznie bardziej przejrzysty, co ułatwia jego późniejsze utrzymanie.
Różowy pasek na wykresie pokazuje przypadek, gdzie przypisałem switch do funkcji i zwróciłem jej wartość. Wynik funkcji natomiast przypisałem do zmiennej y. Ten test miał sprawdzić, czy zwracanie wartości ze switcha będzie szybsze niż przerywanie wykonywania kodu za pomocą break. Jak widać, raczej nie.
Ostatni benchmark (ciemnoczerwona linia) to switch, który w każdym przypadku zwraca wartość (podobnie jak robiliśmy to wcześniej z tablicą). Ponieważ używamy return, nie ma potrzeby za każdym razem przerywać kodu wyrażeniem break. Wychodzi na to, że lepiej jest przetwarzać dane wewnątrz poszczególnych bloków case, zamiast próbować zawężać liczbę bloków do tych, które zawierają tylko unikalne zmienne.
Epilog: „Coś się kończy, coś się zaczyna”
Po tych wszystkich wyszukanych kolorach i liczbach, czas zebrać przemyślenia w całość i spróbować je podsumować. Jeżeli chodzi o szybkość wykonania kodu, to najlepszym wyborem będzie instrukcja if lub switch. Obiekty i tablice są po prostu zbyt wolne, i to do tego stopnia, że możemy całkowicie je wyeliminować. Co prawda tablice z typami prymitywnymi osiągnęły najlepszy wynik w Chrome, ale nie rekompensuje to ich wad, omówionych wcześniej.
Zostaje nam więc if i switch. Cóż… w zasadzie to zostaje tylko switch! Jeżeli mamy do sprawdzenia tylko dwa lub trzy warunki, if wystarczy, ale większa liczba przypadków zdecydowanie powinna być obsługiwana przez switch. Switch właśnie po to został stworzony, i nasz kod będzie o wiele bardziej czytelny.
A skoro już jesteśmy przy temacie czytelności kodu, to wspomniałem już, że przy sprawdzaniu warunków lepiej używać stringów niż liczb, nawet jeżeli ta druga opcja jest szybsza. W rzeczywistości różnica będzie niemal niezauważalna, więc lepiej skupić się na tym, by kod był czysty i łatwy w utrzymaniu.
Jaki jest wniosek z tego wszystkiego? Nie możemy w jeden wieczór pobić lat doświadczenia developerów Firefoksa i Chromium. Dzięki za uwagę!
Artykuł został pierwotnie opublikowany na boldare.com. Zdjęcie główne artykułu pochodzi z unsplash.com.