Skip to content
← Wszystkie wpisy
4 min czytania Dawid Skłodowski

Feature flagi i trunk-based development

Długo żyjące gałęzie funkcji powodują bolesne merge'e i wielkowybuchowe release'y. Feature flagi pozwalają mergować do trunka nieustannie i oddzielić deploy od release'u. Przewodnik po workflow, rodzajach flag i zarządzaniu długiem flag.

Jest powszechny, bolesny rytm budowania oprogramowania: odgałęziasz się od master, pracujesz nad funkcją dwa–trzy tygodnie, a potem stajesz wobec merge’a, który zdryfował tak daleko od mainline, że jego integracja jest osobnym mini-projektem — a po nim wielkowybuchowy release, gdzie dwa tygodnie zmian wchodzą na żywo naraz, a ty wstrzymujesz oddech. Alternatywą jest trunk-based development z feature flagami, a obie razem zmieniają sposób, w jaki zespół wysyła. Oto jak workflow się składa i jak prowadzić go bez robienia bałaganu.

Sedno: oddziel deploy od release’u

Spostrzeżeniem odblokowującym wszystko jest to, że „wdrożenie kodu” i „udostępnienie funkcji użytkownikom” nie muszą być tym samym zdarzeniem. Feature flaga to przełącznik w czasie działania, który decyduje, czy kawałek kodu jest aktywny:

if Flipper.enabled?(:new_checkout, current_user)
  render "checkout/new_flow"
else
  render "checkout/classic"
end

Kod nowego checkoutu jest wdrożony — jest na produkcji, zmergowany do master, wysłany z każdym release’em — ale nie jest udostępniony, dopóki nie przełączysz flagi. To jedno oddzielenie czyni wszystko inne możliwym: możesz bezpiecznie mergować niedokończoną pracę (jest uśpiona za wyłączoną flagą), włączyć funkcję dla 1% użytkowników i obserwować, i wyłączyć ją natychmiast bez deployu, gdy się źle zachowa.

Trunk-based development

Z flagami w ręku strategia gałęzi dramatycznie się upraszcza. Zamiast długo żyjących gałęzi funkcji, każdy integruje się z master (trunkiem) nieustannie — małe commity, mergowane codziennie, za flagami, gdy jeszcze niekompletne:

  • Gałęzie są krótkowieczne — godziny lub dzień, nie tygodnie. Mergujesz do trunka, gdy funkcja jest wciąż ukryta za wyłączoną flagą.
  • Merge’e pozostają trywialne, bo nic nie dryfuje daleko od mainline. Dręczący trzytygodniowy merge po prostu przestaje istnieć.
  • Problemy integracji ujawniają się natychmiast, gdy są małe, zamiast naraz na końcu.

To przeciwieństwo ciężkiego git-flow z jego długo działającymi gałęziami develop, release i feature. Trunk-based development wymienia tę ceremonię na dyscyplinę: trzymaj trunk zawsze gotowym do wydania i ukrywaj niekompletną pracę za flagami, a nie w gałęziach. To czyni prawdziwą ciągłą integrację — a ostatecznie ciągły deployment — możliwą.

Rodzaje flag

Nie wszystkie flagi są takie same, a mylenie ich to częsty błąd. Mają różne cykle życia i właścicieli:

  • Flagi release’u ukrywają funkcję w toku, dopóki nie jest gotowa. Są tymczasowe — gdy funkcja jest w pełni uruchomiona, flaga (i stara ścieżka kodu) powinna być usunięta. To koń roboczy trunk-based development.
  • Flagi ops (kill switche) pozwalają wyłączyć kosztowny lub ryzykowny podsystem pod obciążeniem — wyłącz ciężki widżet rekomendacji podczas skoku ruchu. To długowieczne kontrolki operacyjne.
  • Flagi eksperymentów kierują różnych użytkowników różnymi ścieżkami do testów A/B, z podpiętym podziałem i metrykami. Tymczasowe, kończące się, gdy eksperyment się zakończy.
  • Flagi uprawnień bramkują funkcje według planu, roli lub dostępu beta. To efektywnie permanentne — są częścią modelu uprawnień twojego produktu, nie tymczasowym rusztowaniem.

Wiedza, którego rodzaju jest flaga, mówi ci jej oczekiwany czas życia i czy jest długiem do posprzątania, czy permanentną częścią systemu. W Ruby flipper i rollout to częste biblioteki; obie pozwalają celować flagi po użytkowniku, grupie lub procencie.

Procentowe rollouty i kanary

Funkcją-nagrodą jest stopniowy rollout. Zamiast włączać funkcję dla wszystkich naraz, włączasz ją dla wycinka ruchu i poszerzasz, gdy twoja pewność rośnie:

Flipper.enable_percentage_of_actors(:new_checkout, 5)   # 5% użytkowników
# obserwuj wskaźniki błędów i konwersję, potem:
Flipper.enable_percentage_of_actors(:new_checkout, 25)
Flipper.enable(:new_checkout)                            # wszyscy

To zamienia release z urwiska w gałkę. Jeśli nowy kod skacze z błędami przy 5%, przełączasz go z powrotem — natychmiast, bez deployu, bez rollbacku — a 95% użytkowników nigdy niczego nie zobaczyło. Udostępnianie najpierw wewnętrznemu personelowi, potem użytkownikom beta, potem procentowi, potem wszystkim, to najbezpieczniejszy sposób wysłania czegokolwiek ryzykownego, a flagi są tym, co czyni to decyzją w czasie działania, a nie serią deployów.

Zarządzanie długiem flag

Oto dyscyplina, która oddziela feature flagi jako supermoc od feature flag jako bagna: tymczasowe flagi muszą być faktycznie usuwane. Baza kodu, gdzie każda kiedykolwiek zbudowana funkcja jest wciąż owinięta w if flag_enabled?, staje się kombinatorycznym koszmarem — nikt nie wie, które ścieżki są żywe, testowanie musi pokryć obie gałęzie dziesiątek martwych flag, a warunkowy bałagan czyni kod nieczytelnym. Reguły, których się trzymamy:

  • Nadaj każdej tymczasowej fladze właściciela i oczekiwanie wygaśnięcia, gdy ją tworzysz.
  • Traktuj usunięcie flagi jako część „gotowego”. Funkcja nie jest skończona, gdy jest udostępniona; jest skończona, gdy flaga i stara ścieżka kodu są usunięte i pozostaje tylko nowa ścieżka.
  • Audytuj flagi okresowo. Flaga, która jest na 100% od trzech miesięcy, to dług — usuń ją i martwą gałąź za nią.
  • Trzymaj sprawdzenia flag płytkie. Flaga powinna idealnie bramkować w jednym lub kilku jasnych punktach decyzji, nie być rozsypana przez każdą warstwę, inaczej jej usunięcie później staje się archeologią.

Mantra: flaga release’u to pożyczka, nie prezent. Kupuje ci bezpieczne, ciągłe dostarczanie, a spłacasz ją, usuwając ją, gdy funkcja w pełni wyląduje.

Werdykt

Feature flagi plus trunk-based development to naprawdę lepszy sposób wysyłania. Oddzielając deploy od release’u, uciekasz od piekła merge’a długiej gałęzi i wielkowybuchowego release’u, integrujesz się nieustannie, udostępniasz funkcje stopniowo gałką w czasie działania i zyskujesz natychmiastowy kill switch, gdy coś pójdzie nie tak. Ceną jest dyscyplina: wiedz, którego rodzaju jest każda flaga, i traktuj tymczasowe flagi jako dług do spłacenia przez usunięcie. Prowadź to tak, a twój zespół wysyła mniejsze, bezpieczniejsze i częstsze rzeczy — co, ostatecznie, jest całym sensem narzędzi, które budujemy wokół wysyłania oprogramowania.