Zworki funkcyjne

Cześć.

Dzisiaj chciałbym Ci przedstawić zworki funkcyjne.

Zworki to wygodna koncepcja, pozwalająca na łatwe zarządzanie funkcjonalnością naszego systemu. Jeśli zdarzało Ci się komentować lub ukrywać bloki kodu za dziwnym if-em:

1
if (false) {}

To ten artykuł jest dla Ciebie.

Problem

Rozważmy aplikację, która służy do informowania klientów o dostępności produktów w sklepie. W podstawowej wersji powiadomienia wysyłane są tylko za pomocą wiadomości email. W kolejnych sprintach biznes zleca nam dodanie kolejnego rodzaju powiadomienia, czyli wysyłki SMS. Chcielibyśmy mieć możliwość uruchomienia tej funkcjonalności tylko dla wybranych użytkowników (CanaryRelease) lub tylko dla naszego konta testowego. Rozwiązanie musi być ukryte do momentu zakończenia się procesu przygotowywania produktu przez biznes. A czasem po prostu chcemy ukryć funkcjonalność, która nie jest jeszcze gotowa. Oczywiście możemy pójść po linii najmniejszego oporu i zrobić tak:

1
2
$this->sendMailNotification();
//$this->sendSMSNotification();

Trochę lepsze rozwiązanie

Trochę lepszym rozwiązaniem będzie wprowadzenie na “sztywno” w kodzie odpowiedniego warunku. Prosty IF będzie już realizacją koncepcji zworki funkcyjnej.

1
2
3
4
$this->sendMailNotification();
if ($user->getId() === 212) {
  $this->sendSMSNotification();
}

Zanim ogłosimy sukces zastanówmy się przez chwile jakie problemy mogą się pojawić, gdy zastosujemy takie rozwiązanie.

Po pierwsze, taki kod jest kompletnie niezrozumiały dla innych. Kolejna osoba analizująca ten fragment będzie się zastanawiała dlaczego dodano taki warunek. Nie mówi on jasno dlaczego tylko do tego użytkownika wysyłamy SMS. Oczywiście możemy dodać odpowiedni komentarz, ale wtedy… będziemy mieli komentarz. Osobiście uważam, że powinno się unikać komentarzy w kodzie, o ile nie jest to absolutnie konieczne.

Po drugie, jak namierzyć takiego if-a w momencie, gdy będziemy chcieli usunąć warunek (tak wiem, można po komentarzu…).

Kolejny problem pojawia się, gdy będziemy musieli takiego if-a dodać w kilku miejscach. Każda zmiana działania (włączenie/wyłączenie) będzie wymuszała modyfikację każdego warunku. Diametralnie zwiększa to prawdopodobieństwo pomyłki, co ciągnie za sobą trudne do określenia konsekwencje, które w końcu zmuszą nas do żmudnego debuggowania. W przypadku serwerów produkcyjnych/testowych będzie to wymagało deploy’a nowej wersji kodu.

Rozwiązanie

Modelowym rozwiązaniem naszego problemu jest zworka funkcyjna, czyli strategicznie umieszczone rozgałęzienie w przebiegu sterowania naszej aplikacji. Najprostszą implementacją jest najczęściej prosty if, który na podstawie konfiguracji przełączy wykonanie naszego kodu na inną ścieżkę. Zworkę możemy porównać do zwrotnicy na torach kolejowych, która w zależności od tego jak jest ustawiona, pokieruje pociąg na odpowiednie miejsce. Przejdźmy zatem do realizacji naszej zworki.

Wymagania

Zacznijmy od zebrania wymagań. Po kilkugodzinnej dyskusji zespołu z PMem dochodzimy do ustalenia tego, co powinno sterować naszą zworką. Przede wszystkim powinniśmy mieć możliwość włączenia funkcjonalności dla poszczególnych klientów i całkowitego wyłączenia na podstawie konfiguracji.
Dodatkowo podczas rozmowy pojawiły się też kolejne przypadki warunkujące nasza funkcjonalność:

  • użytkownik
  • adres IP

Kierując się zasadą YAGNI przygotujemy implementację tylko dla tych przypadków, które są nam aktualnie potrzebne.

Implementacja

Implementacje zaczniemy od przygotowania kontraktu na podstawie wymagań zebranych przed chwilą. Da nam to pewność, że nasza klasa realizuje dokładnie te wymagania, które przed chwilą zdefiniowaliśmy. Wykorzystamy do tego TDD. Każdy test zdefiniuje nam konkretną ścieżkę biznesową.
Połączenie TDD i wspomnianej wcześniej YAGNI da nam też stuprocentowe pokrycie kodu testami. Nazwy testów opisują nasze zastosowanie biznesowe.

1
2
3
4
5
function featureIsActiveCheckByCustomerTest(){};
function featureIsNotActiveCheckByCustomerTest(){};
function featureIsActiveCheckByConfigTest(){};
function featureIsNotActiveCheckByConfigTest(){};
function featureNotExistTest(){};

Gdy ktoś będzie chciał rozszerzyć funkcjonalności naszej klasy, wystarczy że przygotuje odpowiedni test i dopisze implementacje.

Teraz możemy przygotować prostą implementację:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FeatureManager
{
public function isActive(string $featureName): bool
{
    if ($this->checkGlobalConfig($featureName) || $this->checkUserConfig($featureName)) {
        return true;
    }
    return false;
}
protected function checkGlobalConfig(string $featureName): bool
{
    return true;
}
protected function checkUserConfig(string $featureName): bool
{
    return true;
}
}

A następnie zmodyfikować IF-a o wykorzystanie managera.

1
2
3
if ($featureMenager->isActive('SMSNotification')) {
$this->sendSMSNotification();
}

Nasza zworka jest gotowa. Możemy już łatwo sterować aktywnymi funkcjonalnościami.

Wady

Niestety zworki nie są cudownym rozwiązaniem i mają też swoje wady, o których musimy pamiętać.
Pierwszym problemem jest wprowadzenie dodatkowej złożoności do logiki biznesowej. Dodanie kilku zworek, a co gorsze zagnieżdżenie ich, może doprowadzić do sytuacji, w której będziemy musieli przetestować wiele ścieżek przejścia. Niestety nigdy nie możemy mieć pewność, że nasz kod nie wpływa na inne części systemu.

Kolejną rzeczą, na którą musimy zwrócić uwagę jest kompatybilność danych. Czy po wyłączeniu zworki klient będzie mógł dalej pracować? Niestety w niektórych miejscach nie będziemy mogli użyć zworki lub będzie to wymagało dopisania mappera, tak, żeby dane miały odpowiednią postać.

Zakończenie

Jak widać zworki funkcyjne ułatwiają nam sterowanie działaniem konkretnych funkcjonalności. Pomimo kilku minusów, o których musimy pamiętać, jest to proste i skuteczne rozwiązanie. Mam nadzieję, że z jego wykorzystaniem już nigdy nie będziecie musieli komentować kodu, by wyłączyć jakiś jego fragment.    

Autorem wpisu jest Łukasz Krawczyk, programista z kilkuletnim doświadczeniem, specjalizujący się w integracji i optymalizacji systemów informatycznych. A nade wszystko – czujny obserwator procesu dostarczania oprogramowania.

Dzięki za wizytę,
Mateusz Mazurek
Łukasz Krawczyk

Pokaż komentarze

  • GE-NIAL-NE!

    Nie słyszałem o tym koncepcie, a po przeczytaniu artykułu wygląda to na oczywiste!
    Analogia do zwrotnicy też jest tutaj wybitna!

    Z YANIGI i TDD dopiero się zaznajamiam ale to połączenie tutaj wygląda genialnie i na autouzupełniające się!

    Więć powtórzę: GE-NIAL-NE!

Ostatnie wpisy

Podsumowanie: maj, czerwiec, lipiec i sierpień 2024

Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More

2 miesiące ago

Podsumowanie: kwiecień 2024

Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More

6 miesięcy ago

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

7 miesięcy ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

9 miesięcy ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

10 miesięcy ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

10 miesięcy ago