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.