Free

3000 linii w jednym commicie: dlaczego złożone funkcje wymagają najpierw plan mode

Instynkt przy złożonych funkcjach to "spróbuję coś" — ale decyzje architektoniczne chowają się w trzecim modelu, piątym edge case. Prawdziwa wartość plan mode to przeniesienie rozmowy architektonicznej do tekstu przed kodem. Realny przypadek: system ławy przysięgłych Pickful — 3032 linie, 119 speców na zielono, jeden commit, zero architektonicznych przeróbek potem.


W obliczu złożonej funkcji instynkt większości ludzi to „spróbujmy coś napisać".

Problem: złożone systemy mają pewną cechę — decyzje architektoniczne ukrywają się w 3. modelu, w 5. edge case, w 8. regule punktów. Zanurkowanie w kod i uderzenie o te decyzje w połowie drogi, a potem cofanie się, by je zmienić, kosztuje 10× więcej niż przegadanie ich w tekście od samego początku.

Użyłem plan mode Claude Code, żeby zbudować TopicReview — system ławy przysięgłych społeczności w Pickful — i zobaczyłem skrajną wersję tej nierówności: jeden commit, 3032 linie, 119 speców na zielono, dostarczone za jednym razem. Żaden z kilkunastu kolejnych commitów nie był przeróbką architektoniczną — wszystkie to tuning parametrów, polish UI, łatki edge case'ów. System działa stabilnie od tamtej pory i jest dziś centralnym elementem tego, jak społeczność moderuje samą siebie.

Ten wpis dotyczy tego, dlaczego złożone funkcje wymagają najpierw plan mode, co tak naprawdę robi faza plan i kiedy rozmowa jest na tyle dojrzała, by zacząć pisać kod.

Jak złożony jest ten system

TopicReview to głosowanie społeczności decydujące, czy post niskiej jakości zostanie usunięty. Jedno zdanie do opisania — ale specyfikacja rozwija się warstwami:

  • 5 stanów: open → voting → decided → appealed → closed
  • 3 werdykty: remove / warn / keep
  • 2 fazy: postępowanie wstępne prowadzi 12 przysięgłych; apelację — 5 sędziów (losowanych spośród 20 użytkowników z największą liczbą punktów)
  • Wielowymiarowy przepływ punktów: kara tymczasowa 10 pt, stake apelacyjny 10 pt, +5 dla przysięgłych głosujących zgodnie z werdyktem, +10 dla sędziów głosujących zgodnie z werdyktem, +3 dla zgłaszających, których zgłoszenie się potwierdziło, zwrot przy wygranej apelacji = stake + bonus + kara tymczasowa
  • 4 rodzaje zaplanowanych jobów: 24-godzinne okno głosowania wstępnego, 24-godzinne okno apelacji, 24-godzinne okno rozpatrzenia apelacji oraz — punkty przysięgłych odroczone o 24h po werdykcie remove (bo apelacja może go przewrócić)
  • Przepływy równoległe + rollback: jeśli głosy się odwrócą podczas tymczasowego usunięcia, post trzeba przywrócić i zwrócić karę tymczasową; jeśli apelacja obali werdykt, zwracany jest stake plus bonus, ewentualnie również kara tymczasowa, a przysięgli są przeliczani wedle nowego werdyktu

Istota złożoności nie tkwi w żadnej pojedynczej regule — tkwi w tym, jak reguły oddziałują między sobą. Każda dodana reguła może wyzwolić rollback gdzieś indziej.

Ściany, w które walisz bez plan

Kiedy piszesz ciągiem, najtrudniejsze problemy to nie widoczne fakty — tylko pytania, których nie pomyślałeś zadać. Czytając kod TopicReview od tyłu, jest co najmniej cztery ściany, w które nieuchronnie walniesz, pomijając plan:

Reguły kwalifikowalności przysięgłych. Wygląda tylko na User.jurors_and_judges.sample(12). Ale rzeczywiste reguły to: wykluczyć autora posta, wykluczyć osobę, która go zgłosiła, wykluczyć każdego, kto już głosował w pierwszej rundzie (żeby ten sam człowiek nie głosował też w apelacji). Trzy warstwy wykluczeń. Pisząc model jednym ciągiem, pominiesz jedną lub dwie.

Odroczona wypłata punktów. Werdykt remove normalnie wyzwala wypłatę przysięgłym. Ale apelacja może odwrócić remove — a gdy to zrobi, przysięgli głosujący wedle starego werdyktu lądują po złej stronie, więc wypłaty trzeba przeliczyć wedle nowego werdyktu. Dlatego werdykt remove musi poczekać na zamknięcie 24-godzinnego okna apelacji, zanim wypłaci przysięgłym. Pominiesz tę regułę, zapłacisz wcześniej — będziesz potem odbierał punkty, 10× bardziej uciążliwe niż wypłacenie z opóźnieniem.

Projekt interfejsu zaplanowanego joba. CloseTopicReviewJob z wyglądu „zamyka review". W praktyce obsługuje trzy sytuacje:

# Wygaśnięcie okna głosowania wstępnego
CloseTopicReviewJob.set(wait_until: voting_ends_at).perform_later(review.id)
# Odroczona wypłata przysięgłym po werdykcie remove
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, award_juror_points: true)
# Wygaśnięcie okna apelacji
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, appeal_id: appeal.id)

Bez wcześniejszego planowania napiszesz pierwszą sygnaturę, a w drugim przypadku zorientujesz się, że interfejs musi się zmienić.

Najdroższa ściana: czy można apelować podczas tymczasowego usunięcia? Gdy głosy remove przekraczają próg, post jest natychmiast ukrywany (tymczasowo), ale cały review pozostaje w stanie voting — nie decided. Czy użytkownik może już apelować?

  • Może → trzeba rozszerzyć przejścia między decided i appealed
  • Nie może → kiepski UX: post jest ukryty, a użytkownik musi czekać 24h, zanim w ogóle zaprotestuje
  • Realny wybór TopicReview: tak, apelacja jest dozwolona podczas tymczasowego usunięcia — ale review musi najpierw przejść finalize → decided, zanim otworzy się appeal

Ta jedna decyzja rozchodzi się wstecz i zmienia sprawdzenia parametrów w open_appeal! oraz logikę maszyny stanów. Podejmowanie jej w połowie = przepisywanie połowy systemu.

Co naprawdę robi faza plan

Plan mode Claude Code to tryb, w którym pisanie kodu jest zabronione — Claude może czytać repo, przemyśleć podejścia, dyskutować z tobą, ale każda modyfikacja pliku jest hard-blokowana, dopóki nie zatwierdzisz planu.

To mechaniczne ograniczenie jest sednem: wymusza, by rozmowa na poziomie architektury odbywała się w tekście.

Kilka rzeczy, które faza plan robi w praktyce:

1. Rysowanie maszyny stanów i ról. 5 stanów, 4 role (przysięgły / sędzia / autor posta / zgłaszający), co każda rola może i czego nie może w każdym stanie — wszystko w kilku linijkach markdown. Kilka linii vs. dziesiątki plików. Koszt zmiany różni się o dwa rzędy wielkości.

2. Przejście przez przepływ każdej roli:

  • Przysięgły: dostaje powiadomienie → otwiera review → czyta post i powód → głosuje z reasoning → otrzymuje punkty
  • Autor posta: dostaje powiadomienie → widzi werdykt → jeśli remove rozważa apelację → wpłaca stake → czeka
  • Sędzia: otrzymuje apelację wylosowaną z top-20 → głosuje → otrzymuje punkty

Gdzie marsz się zacina, wyskakuje ukryte pytanie: „czy przysięgli widzą głosy innych przysięgłych?", „co autor może zrobić podczas tymczasowego usunięcia?"

3. Przesłuchiwanie edge case'ów. Faza plan nie projektuje „ścieżki normalnej" — celowo zadaje pytania, które zwykle się nie pojawiają:

  • Co, jeśli okno zamknie się z 0 głosami? (Końcowy wybór: domyślnie keep.)
  • Co, jeśli apelacja zamknie się z 0 głosami? (dismissed; stake przepada.)
  • Czy sędzia może głosować nad postem, który sam zgłosił? (Nie — ta sama reguła wykluczenia.)
  • Wielu przysięgłych jednocześnie wyzwala finalize — czy race zdubluje wypłatę? (Dodać pole juror_points_awarded dla idempotencji.)

95% tych pytań nie wyłania się naturalnie przy pisaniu kodu. Faza plan zmusza cię do przejścia przez nie po kolei.

4. Księga punktów. System punktów jest wystarczająco złożony, by dyskusja nie wystarczyła — trzeba faktycznie narysować tabelę: każdy przepływ punktów z wyzwalaczem, kwotą i ścieżką rollback. Gdy księga się bilansuje, wszystkie edge case'y (zwrot tymczasowy, zwrot apelacji, bonusy) rozliczają się.

Jak daleko trzeba dojść w rozmowie, zanim zacznie się pisać kod

Twardy próg:

  • Umiesz przejść każdą ścieżkę bez zawieszenia — każdą kombinację od otwarcia review aż po closed (keep / remove / warn × z apelacją / bez × tymczasowa / nietymczasowa) — opowiadasz od początku do końca
  • Każdy edge case ma zdefiniowane zachowanie — nie „zobaczymy później", ale „0 głosów domyślnie keep", „tymczasowa dopuszcza apelację, ale najpierw finalize"
  • Nie ma „aha, jeszcze o tym nie pomyślałem" — na każde pytanie, które potrafisz sformułować, jest odpowiedź

Gdy osiągniesz ten poziom, pisanie kodu staje się tłumaczeniem planu na Ruby.

Jak wygląda faza realizacji ciągiem

Pierwsze wdrożenie tego systemu:

d162f1e Add community moderation system with jury/judge review and appeals
  63 files changed, 3032 insertions(+)
  119 specs, 0 failures

63 pliki, 3032 linie, 119 speców na zielono. Dostarczone za jednym razem.

Kilkanaście kolejnych commitów potwierdza, że architektura się utrzymała:

Apply legacy penalty (1pt) for posts created before 2026-03-26
Fix topic review appeal bugs: window mismatch and verdict not updated
Add topic_removed status for posts removed by community review
Default to keep verdict when review expires with zero votes
Reduce provisional removal penalty to 1pt during trial period
Allow topic creator to withdraw active reviews
Add handled tab to jury dashboard, fix tabs styling

Każdy to tuning / fix / polish / mała funkcja. Żaden nie jest „wróćmy i przeprojektujmy maszynę stanów" albo „model punktów trzeba zmienić". Szkielet był w całości trafiony.

To prawdziwa premia planu: realizacja pozostaje nieprzerwana. Nie ma „poczekaj, jak działa ta ścieżka" — plan to pokrył. Nie ma „nie pomyślałem o tym edge case" — faza plan zapytała. Nie ma „ten interfejs trzeba zaprojektować od nowa" — plan to już poukładał.

Godziny pisania kodu, robiąc tylko jedno: przekład jasnego projektu na klawiaturę.

Kiedy nie używać planu

Nie każde zadanie zasługuje na plan:

  • Proste bugfiksy — zlokalizować + naprawić + dodać test regresyjny; bez dyskusji wstępnej
  • Mechaniczne refaktory — zmień nazwę, wyodrębnij, przenieś; ścieżka jest jedna, plan to krok w zbyć
  • Tylko jedna oczywista ścieżka — dodać endpoint GET, toggle UI; nie ma o czym dyskutować
  • Prototypy eksploracyjne — sam nie wiesz, czego chcesz; uruchomienie czegoś toporego bije dalsze myślenie

Zysk z plan mode płynie z obniżenia kosztu przepisywania. Jeśli w zadaniu nie ma ryzyka przepisywania, plan to tylko narzut.

Zakończenie

„Najpierw pomyśl, potem rób" brzmi jak rada dotycząca charakteru. Plan mode nie o tym.

Plan mode polega na tym, by rozmowa na poziomie architektury działa się tam, gdzie 10 słów może ją zmienić — a nie tam, gdzie trzeba zmieniać 30 plików.

W warunkach złożoności trzeba odwrócić stare branżowe „words are cheap, code is expensive" — nie oznacza to „pisz kod najpierw, zobaczymy" (to działa tylko, gdy różnica kosztów jest mała), oznacza wykorzystanie tej różnicy: przenieść drogą rozmowę do przodu, do taniego medium.

3000 linii w jednym commicie to świetne uczucie. Nie dlatego, że szybko piszę — dlatego, że plan zamienił „pisanie" w czysto mechaniczny akt.