Piękno operatora warunkowego (ternary operator) w JavaScript

Niebiesko-żółto-różowy neon złożony ze szklanych rurek ułożonych w kształcie przypominającym schematyczną mapę połączeń kolejowych

Poznaj klucz do zwięzłego i eleganckiego kodu w JavaScript - operator warunkowy. W tym artykule zapoznasz się z jego działaniem, dowiesz się, kiedy go używać, a kiedy lepiej skorzystać z if…else. Omówimy również jego rolę w kontekście programowania funkcyjnego, oraz kontrowersyjną kwestię zagnieżdżania.

Spis treści

  1. Wprowadzenie
  2. Kiedy używać operatora warunkowego
  3. Kiedy instrukcja if jest lepszym wyborem
  4. Przyjaciel programowania funkcyjnego
  5. Kilka słów o zagnieżdżaniu operatorów warunkowych
  6. Wnioski

Wprowadzenie

Operator warunkowy w JavaScript, znany także pod angielską nazwą ternary operator, to operator zwracający pojedynczą wartość na podstawie warunku. Jest to jedyny operator w JavaScript, który przyjmuje trzy argumenty:

  • Wyrażenie warunkowe
  • Wyrażenie zwracane, gdy warunek jest prawdziwy
  • Wyrażenie zwracane, gdy warunek jest fałszywy

Oto jak wygląda jego składnia:

warunek ? wyrażenieGdyPrawda : wyrażenieGdyFałsz;

W tej strukturze, warunek to wyrażenie podlegające ocenie, wyrażenieGdyPrawda to kod, który uruchamia się, gdy warunek jest prawdziwy, a wyrażenieGdyFałsz to kod, który uruchamia się, gdy warunek jest fałszywy. To prosta, ale potężna koncepcja.

Programiści często postrzegają operator warunkowy po prostu jako alternatywę dla instrukcji if…else. Ale należy pamiętać o paru istotnych różnicach:

  • Operator warunkowy jest wyrażeniem, a nie instrukcją, dlatego ewaluuje się do pojedynczej wartości. Natomiast if…else jest instrukcją i jako taka, nie ewaluuje się i może też niczego nie zwracać.
  • Będąc wyrażeniem, operator warunkowy nie powinien powodować efektów ubocznych. Natomiast instrukcja if…else ma naturalną tendencję do efektów ubocznych.

Operator warunkowy jest elegancki i zwięzły, ale często źle rozumiany. Dlatego przyjrzyjmy mu się bliżej.

Kiedy używać operatora warunkowego

Wyobraź sobie taką sytuację: późno w nocy, mając pod ręką drugą już filiżankę kawy, pracujesz nad funkcją dla rabatów cenowych na stronie e-commerce. Logika biznesowa jest prosta: klienci z ponad 100 punktów lojalnościowych otrzymują 10% zniżki, dla innych cena pozostaje taka sama.

Zaczynasz pisać:

let discountPrice;
if (loyaltyPoints > 100) {
discountPrice = price * 0.9;
} else {
discountPrice = price;
}

W pewnym momencie odsuwasz się i patrzysz na swój kod. Jest poprawny, ale wydaje się rozwlekły dla tak prostej rzeczy. Wtedy dociera do ciebie, że wszystko, co musisz zrobić, to zwrócić odpowiednią wartość na podstawie warunku: idealna okazja do użycia operatora warunkowego.

Refaktoryzujesz swój kod:

let discountPrice = loyaltyPoints > 1000 ? price * 0.9 : price;

Nieźle, udało się skondensować cztery linie kodu w jedną, nie tracąc na jasności. A właściwie kod jest teraz znacznie bardziej czytelny.

Kiedy instrukcja if jest lepszym wyborem

Kilka dni później masz do zaimplementowania zarządzanie operacjami bazodanowymi w aplikacji. W zależności od danych wprowadzonych przez użytkownika, musisz albo zapisać nowe informacje do bazy, albo odczytać istniejące informacje, a następnie zarejestrować wynik.

Pośpiesznie piszesz:

let result = userInput === "update"
? writeToDatabase(record)
: readFromDatabase(record);
logDatabaseOperation(result)

Zatrzymując się, czujesz, że coś z tym kodem jest nie tak. Twój operator elegancko obsługuje warunek, wygląda czysto, ale… wywołuje efekty uboczne. Wywołania czytajZBazyDanych i zapiszDoBazyDanych zmieniają coś poza tym wyrażeniem. To jest zadanie dla instrukcji if…else, więc refaktoryzujesz kod:

let result;
if (userInput === "update") {
result = writeToDatabase(record);
} else {
result = readFromDatabase(record);
}
logDatabaseOperation(result)

O wiele lepiej! Instrukcja if…else obsługuje warunek i pozwala na efekty uboczne, które są niezbędne do tej operacji.

Czy to oznacza, że pierwsza wersja nie będzie działać? Nie, technicznie działałaby bez zarzutu! Więc dlaczego jest zła? Ponieważ nie taka jest intencja operatora warunkowego, a w czystym kodzie, w dużej mierze, chodzi właśnie o intencje. Chcemy widzieć intencje, czytając czyjś kod. Intencją operatora warunkowego jest oszacowanie wyrażenia i zwrócenie pojedynczej wartości, i nic ponad to. Kiedy widzę operator warunkowy, oczekuję, że nie będzie miał efektów ubocznych. A jeśli występują, to cóż, mamy do czynienia z zapachem kodu (code smell).

Przyjaciel programowania funkcyjnego

Programowanie funkcyjne to przede wszystkim czyste funkcje, niezmienne dane i unikanie efektów ubocznych. Z tego powodu, operator warunkowy doskonale wpisuje się w ten paradygmat.

Używany prawidłowo, operator warunkowy po prostu zwraca wartość na podstawie warunku. Wartości tej możesz użyć bezpośrednio, tak jak używałbyś wartości zwróconej przez funkcję. W tym sensie, działa jak czysta funkcja, która zwraca wartość bez jakichkolwiek efektów ubocznych. To czyni go prawdziwym sojusznikiem podczas pisania kodu funkcyjnego w JavaScript.

Kilka słów o zagnieżdżaniu operatorów warunkowych

W społeczności programistów toczy się debata co do zagnieżdżania operatorów warunkowych. Spróbuj wyszukać frazę “nested ternary operator opinion”, a znajdziesz mnóstwo opinii, przeważnie przeciwko zagnieżdżaniu. Niektórzy twierdzą, że zagnieżdżone operatory warunkowe są w porządku, podczas gdy inni mówią, że są one “czystym złem” i że żaden szanujący się deweloper nigdy by ich nie użył. Bez wątpienia, jest to temat polaryzujący.

Ale jeśli poszukasz głębiej, znajdziesz również ludzi, którzy podają dobre i sensowne argumenty za użyciem zagnieżdżonych operatorów warunkowych zamiast instrukcji if…else. Eric Elliott napisał bardzo dobry artykuł, argumentujący, że zagnieżdżone operatory warunkowe są świetne. Zdecydowanie polecam przeczytać cały artykuł, ale oto jego główne punkty:

Według Erica, preferencja dla zagnieżdżonych instrukcji if wynika z uprzedzeń wynikających z przyzwyczajenia. Argumentuje, że w przypadku wyrażeń warunkowych, bardziej adekwatne byłoby mówienie, że są one łańcuchowane, a nie zagnieżdżone. Ponieważ wyrażenia warunkowe można łatwo ułożyć w linii prostej od góry do dołu, są naturalnie łatwe do czytania i zmniejszają obciążenie poznawcze. Mając spore doświadczenie w programowaniu funkcyjnym, Eric zauważa również, że wyrażenia warunkowe są generalnie lepsze od instrukcji if, ponieważ ewaluują się do pojedynczej wartości i nie powodują efektów ubocznych.

Przyznam, że zdecydowanie zgadzam się z Eric’iem. Weźmy na przykład ten kod napisany za pomocą zagnieżdżonych instrukcji if:

const greetingMessage = (isMorning, isWeekend) => {
if (isMorning) {
if (isWeekend ) {
return 'Good morning, enjoy your weekend!';
} else {
return 'Good morning!';
}
} else {
return 'Hello!';
}
};

A teraz jego odpowiednik przy użyciu wyrażeń warunkowych:

const greetingMessage = (isMorning, isWeekend) =>
isMorning
? (isWeekend ? 'Good morning, enjoy your weekend!' : 'Good morning!')
: 'Hello!';

Moglibyśmy również zrefaktoryzować go do bardziej łańcuchowej formy:

const greetingMessage = (isMorning, isWeekend) =>
!isMorning
? 'Hello!'
: isWeekend
? 'Good morning, enjoy your weekend!'
: 'Good morning!';

Dla mnie obie wersje z zagnieżdżonymi operatorami warunkowymi wyglądają bardziej czytelnie i elegancko. Ale nie musisz ze mną zgadzać. Jako że jest to temat dyskusyjny, Ty i twój zespół powinniście dojść do konsensusu i postępować zgodnie z przyjętą w zespole praktyką.

Wnioski

Tak więc, operator warunkowy (ternary operator) jest potężnym narzędziem. Odpowiednio użyty sprawi, że Twój kod będzie czystszy, bardziej zwięzły i elegancki. Ale pamiętaj, aby używać go z rozwagą. Kiedy twój kod musi wywoływać efekty uboczne, zdecydowanie nie powinieneś używać operatora warunkowego, lecz sięgnąć po instrukcję if.

Jak zawsze w programowaniu, nie ma jednego uniwersalnego rozwiązania, używaj swojej wiedzy i osądu, aby podejmować dobre decyzje. Udanego programowania, niech Twój kod będzie czysty i elegancki!

Ikona Smiley Obserwuj DevSchool!
Logo FacebookLogo Twitter

Copyright 2022 – 2023 ©
Michał Wilkosiński CONIFER MEDIA
Wszystkie prawa zastrzeżone