Blog technologiczny od 2014 · 1 111 wpisów
RSS

Boolean – co to jest i czym jest boolean obsession? Kiedy true/false to zły wybór

Czy zastanawiałeś się kiedyś, czy Twój kod nie jest przepełniony booleanami? W tym wpisie wyjaśniam, czym jest boolean (typ true/false), czym jest „boolean obsession”, jak jej unikać i jak poprawić czytelność oraz elastyczność kodu.

Boolean (typ logiczny, w wielu językach bool) to typ danych, który ma tylko dwie wartości: true (prawda) i false (fałsz). Boolean obsession to nadużywanie takich wartości tam, gdzie stan ma w rzeczywistości więcej niż dwa warianty albo gdzie sama flaga nie mówi, co oznacza.

Boolean obsession w skrócie
  • Boolean ma dwie wartości: true i false. Nazwa pochodzi od George’a Boole’a, twórcy algebry logiki.
  • Problem zaczyna się, gdy kilka flag opisuje jeden stan (isAdmin + isGuest) albo gdy do true/false dochodzi trzeci stan (null).
  • Objaw: ifologia, czyli rozrastające się drabinki if/else.
  • Lekarstwo: enum lub union type, osobne funkcje zamiast flagi, obiekt konfiguracyjny, maszyna stanów.

Boolean – co to jest?

Boolean to typ danych reprezentujący dwie wartości logiczne: prawdę i fałsz. Nazwa pochodzi od angielskiego matematyka George’a Boole’a (1815–1864), który w pracach The Mathematical Analysis of Logic (1847) i The Laws of Thought (1854) opisał logikę za pomocą algebry – dziś nazywamy ją algebrą Boole’a. Jednym z pierwszych języków z jawnym typem BOOLEAN był ALGOL 60.

W praktyce każdy język podchodzi do typu logicznego trochę inaczej:

  • TypeScript – typ boolean to w rzeczywistości unia dwóch typów literalnych true | false. To ważne, bo za chwilę zobaczysz, że unia literałów jest też najprostszym lekarstwem na boolean obsession.
  • JavaScript – poza true i false liczy się też „prawdziwość” wartości: 0, pusty string, null, undefined i NaN w warunku zachowują się jak false (tzw. falsy).
  • Python – bool jest podklasą int, więc True i False zachowują się jak 1 i 0.
  • C – przez lata nie miał osobnego typu logicznego (0 to fałsz, każda inna liczba to prawda). _Bool pojawił się w C99, a C23 wprowadził słowo kluczowe bool z wartościami true i false.

Zmienna boolean (np. const isLoggedIn: boolean = true) jest najtańszym sposobem zapisania odpowiedzi na pytanie „tak czy nie?”. I właśnie dlatego tak łatwo ją nadużyć.

Czym jest boolean obsession?

„Boolean obsession” to określenie nadmiernego korzystania z typów boolean (prawda/fałsz) w kodzie. Chociaż booleany są wygodnym i prostym sposobem reprezentowania stanu, ich nadmiar może prowadzić do problemów z czytelnością i zrozumieniem kodu. Kiedy kod zaczyna być zbyt zależny od wartości logicznych, staje się mniej intuicyjny, trudniejszy do utrzymania, a także mniej elastyczny na przyszłe zmiany.

To nie jest formalny termin z katalogu refaktoryzacji, tylko szczególny przypadek kilku dobrze opisanych problemów:

  • Primitive obsession – zapach kodu z katalogu Martina Fowlera i Kenta Becka (Refactoring): używanie typów prostych (liczb, stringów, booleanów) zamiast małych obiektów lub typów opisujących dziedzinę.
  • Flag argument – parametr, który przełącza funkcję między różnymi zachowaniami. Fowler w 2011 r. pisał, że zamiast book(martin, false) czytelniej jest wywołać regularBook(martin), a w katalogu refaktoryzacji opisuje to jako Remove Flag Argument.
  • Boolean blindness – określenie Roberta Harpera (2011): sam bit true/false nie niesie informacji, skąd się wziął i co znaczy, więc żeby go użyć, trzeba znać jego pochodzenie.

Gdzie zazwyczaj pojawia się problem?

Najczęściej spotykane miejsca, w których boolean obsession może się pojawić, to:

  1. Parametry metod: Gdy funkcje przyjmują wiele parametrów boolean, kod staje się nieczytelny, ponieważ w miejscu wywołania nie widać, co dany parametr oznacza. To znaczy wiadomo, ale często zachodzi potrzeba, by sprawdzić różne warianty parametrów. Dodatkowo taka metoda tych parametrów może dostawać coraz więcej.
  2. Wewnętrzne flagi w klasach i obiektach: Wzrost liczby flag w klasach i obiektach może spowodować rozrost logiki warunkowej, co utrudnia rozumienie tego, co się dzieje w programie.
  3. Rozbudowane instrukcje warunkowe: Gdy kod zaczyna zawierać wielopoziomowe warunki oparte na wielu booleanach, debugowanie i śledzenie logiki staje się bardziej skomplikowane. Jedyny ratunek to bardzo dobre pokrycie tej pokrętnej logiki testami.

Przykłady boolean obsession

Flagi w parametrach funkcji, czyli ifologia

Myślę, że pierwszy przykład świetnie obrazuje problem. Mamy tutaj dwie flagi isAdmin oraz isGuest. W swojej karierze wiele razy widziałem podobny kod. Problem, jaki tutaj się pojawia, to trudne do ogarnięcia instrukcje warunkowe (ifologia), które mają sprawdzić dokładnie rodzaj użytkownika. Nawet nie chcę wiedzieć, jak ta funkcja by wyglądała, gdyby trzeba było dodać więcej typów użytkowników.

zsh
function createUser(name: string, isAdmin: boolean, isGuest: boolean) {
    if (isAdmin && !isGuest) {
        console.log(`${name} jest adminem.`);
    } else if (!isAdmin && isGuest) {
        console.log(`${name} jest gościem.`);
    } else {
        console.log(`${name} jest zwykłym użytkownikiem.`);
    }
}

Dwie flagi to cztery kombinacje, a sensowne są tylko trzy. Wywołanie createUser('Ania', true, true) – admin, który jest jednocześnie gościem – kompilator przepuści bez słowa, a funkcja po cichu zrobi z niego zwykłego użytkownika. Do tego w miejscu wywołania createUser('Ania', false, true) nie widać, która flaga jest która. Każda kolejna flaga podwaja liczbę kombinacji, a razem z nimi rośnie ifologia.

True, false… i null, czyli trzeci stan w API

Innym ciekawym przykładem, z jakim ostatnio miałem styczność, była odpowiedź z API. W jednym z endpointów było pole isOnline, które przyjmowało dwie wartości: true lub false. Wszystko wyglądało całkiem dobrze do momentu, w którym nastąpiła integracja z innym systemem, który nie potrafił określić, czy użytkownik jest online. Co zrobili programiści, by nie psuć kontraktu? Dodali null jako trzeci stan.

zsh
interface UserStatus {
    isOnline: boolean | null; // null = „nie wiadomo”
}

To teraz, cytując klasyka, „biorę od państwa po 200 zł i pytam”, jak to będzie wyglądało, jeśli trzeba będzie dodać kolejne dwa stany? Znaczenie null żyje tylko w komentarzu (albo w głowie autora), a każdy klient API musi pamiętać, że if (!user.isOnline) wrzuca do jednego worka „offline” i „nie wiadomo”.

Dobra wiadomość jest taka, że da się to prosto naprawić.

Jak unikać boolean obsession?

  1. Enumy i typy wyliczeniowe: Zamiast booleanów używaj bardziej opisowych typów, które lepiej wyrażają możliwe stany.
  2. Obiekty konfiguracyjne: Zamiast przekazywać wiele booleanów, lepiej jest używać obiektu konfiguracji, który nazywa każdą opcję w miejscu wywołania. To ogólnie dobra praktyka, aby funkcje miały jak najmniej parametrów. Jeżeli jest ich dużo (powyżej 3), to sygnał, że coś poszło nie tak.
  3. Klarowne nazewnictwo: Zadbaj o to, aby zmienne boolean miały nazwy dokładnie opisujące ich rolę, np. zamiast isActive użyj userHasActiveSubscription, co od razu daje pełniejszy kontekst. W tym przypadku isActive może sugerować, czy dany użytkownik jest zalogowany w systemie.
  4. Unikaj zagnieżdżania logiki warunkowej: Gdy warunki stają się zbyt złożone, rozważ refaktoryzację kodu, np. dzieląc logikę na mniejsze funkcje.

Enum i union types zamiast true/false

Jeśli zmienna opisuje „rodzaj” albo „stan” czegoś, prawie zawsze lepiej sprawdzi się typ z nazwanymi wartościami. W TypeScripcie najprościej zrobić to unią literałów, w innych językach – enumem:

zsh
type Role = 'admin' | 'guest' | 'user';

enum Role2 { Admin, Guest, User } // to samo jako enum

Dodanie czwartej roli to jedna linijka, a kompilator pokaże wszystkie miejsca, które trzeba obsłużyć.

Osobne funkcje zamiast flagi

Jeśli boolean przełącza między dwoma różnymi zachowaniami, często lepiej rozdzielić funkcję na dwie, tak jak radzi Fowler: zamiast sendEmail(user, true) – sendWelcomeEmail(user) i sendReminderEmail(user).

Obiekt konfiguracyjny lub obiekt wartości

Gdy flag nie da się uniknąć, przekaż je w obiekcie – wtedy wywołanie czyta się jak zdanie:

zsh
exportReport(data, { includeCharts: true, compress: false });

Krok dalej to obiekt wartości (value object), np. klasa Subscription z metodą isActive() zamiast luźnej flagi – logika siedzi wtedy w jednym miejscu.

Maszyna stanów zamiast kilku flag

Klasyczny przykład z frontendu: isLoading, isError i isSuccess w jednym komponencie. Trzy flagi to osiem kombinacji, z czego sensowne są trzy lub cztery. Zamiast tego warto opisać stan jako jedną wartość (tzw. discriminated union), w której nie da się zapisać stanu niemożliwego – Yaron Minsky ujął to hasłem „make illegal states unrepresentable”.

zsh
type RequestState =
    | { status: 'idle' }
    | { status: 'loading' }
    | { status: 'success'; data: User[] }
    | { status: 'error'; message: string };

Jak można poprawić przytoczone wcześniej przykłady?

Pierwszy przykład można zrefaktoryzować w następujący sposób – dwa booleany zamienić na jeden parametr role.

zsh
function createUser(name: string, role: 'admin' | 'guest' | 'user') {
    const labels = { admin: 'adminem', guest: 'gościem', user: 'zwykłym użytkownikiem' };
    console.log(`${name} jest ${labels[role]}.`);
}

createUser('Ania', 'guest');

Ifologia znika, wywołanie mówi samo za siebie, a admin-gość przestaje być możliwy. W przypadku odpowiedzi API można zrobić podobnie:

zsh
type OnlineStatus = 'online' | 'offline' | 'unknown';

Kiedy boolean jest w porządku?

Boolean obsession nie oznacza, że booleanów trzeba unikać. Pole emailVerified albo hasDiscount to naturalnie pytania typu „tak/nie”, które nie dostaną trzeciej odpowiedzi. Problem zaczyna się, gdy flaga opisuje coś, co ma więcej wariantów, gdy kilka flag razem koduje jeden stan albo gdy true/false w wywołaniu funkcji nic nie mówi czytelnikowi. Dobrym testem jest pytanie: „czy za pół roku może pojawić się trzecia opcja?”.

Takie decyzje dobrze sprawdzać na code review – również wtedy, gdy kod pisze AI, o czym więcej we wpisie o vibe codingu i jakości oprogramowania. Jeśli piszesz w JavaScripcie, przyda się też tekst o tym, kiedy używać var, let i const, a przed rozmową o pracę – lista pytań rekrutacyjnych dla frontend developera.

01Boolean – co to jest?+
Boolean to typ danych, który przyjmuje tylko dwie wartości: true (prawda) albo false (fałsz). Służy do zapisywania wyników warunków i odpowiedzi „tak/nie”. Nazwa pochodzi od matematyka George’a Boole’a.
02Czym różni się boolean od bool?+
To ta sama idea pod inną nazwą. W Javie i TypeScripcie typ nazywa się boolean, w Pythonie, C++ i C (od C23) – bool.
03Co oznacza true i false w programowaniu?+
True to logiczna prawda, false – fałsz. To wartości zwracane przez porównania (np. 5 > 3 daje true) i sprawdzane przez instrukcje warunkowe, takie jak if.
04Co to jest ifologia?+
Ifologia to żartobliwe określenie kodu przeładowanego instrukcjami if/else. Często jest skutkiem boolean obsession: każda nowa flaga dokłada kolejne gałęzie warunków.
05Czym zastąpić boolean, gdy pojawia się trzeci stan?+
Typem z nazwanymi wartościami: enumem albo unią literałów (np. 'online’ | 'offline’ | 'unknown’). Przy kilku powiązanych flagach lepiej sprawdzi się jeden obiekt stanu, czyli maszyna stanów.

Podsumowanie

Boolean obsession to jeden z tych ukrytych problemów, które mogą sprawić, że kod staje się trudniejszy do zrozumienia, rozbudowy i utrzymania. Co więcej, bezmyślne brnięcie w to może skutkować tworzeniem coraz większego długu technologicznego.

Używając enumów, obiektów konfiguracyjnych i dobrego nazewnictwa, możemy zwiększyć czytelność kodu oraz ułatwić jego przyszłe rozwijanie.

Dlatego zawsze przed użyciem booleana warto zadać sobie pytanie, czy istnieje szansa, że może pojawić się trzecia (i kolejne) opcja.

Opublikowano · Znalazłeś błąd? Napisz
Piotr Cichosz
Autor

Od ponad 10 lat jestem zaangażowany w świat elektroniki użytkowej, zdobywając szeroką wiedzę i doświadczenie w testowaniu oraz recenzowaniu najnowszych technologii. Moja kariera obejmuje pracę w wiodących firmach technologicznych, gdzie specjalizowałem się…

Czytaj dalej
← Poprzedni Brawl Stars: Co oznacza kręcenie się podczas bitwy?