Wzorce języka Go
Wzorce Go dla agentów AI to prompty i bramki weryfikacyjne, dzięki którym Claude Code, Codex i Cursor tworzą produkcyjne usługi Go: graceful shutdown, współbieżność ograniczoną przez errgroup, dobrane pule pgx, testy table-driven i Testcontainers oraz mapowanie statusów gRPC. Kod agenta przyjmuje się na podstawie wyników detektora wyścigów (-race), goleak, golangci-lint, govulncheck i testu obciążeniowego, a nie czytania linijka po linijce.
Twoja usługa Go obsługuje 200 żądań na sekundę na stagingu, a potem pada przy pierwszym kontakcie z prawdziwym ruchem. Goroutines uruchamiane per żądanie nigdy się nie kończą, w puli pgx kończą się wolne połączenia, bo brakuje rows.Close(), a wywołanie usługi zależnej bez deadline’u piętrzy się, aż OOM killer zabije pod. Kod agenta wyglądał idiomatycznie i przechodził jego własne testy happy path. Niczego, co wyprodukował, nie sprawdzono pod kątem awarii, które ujawniają się dopiero przy współbieżności.
Ta receptura jest dla programistów, którzy budują backendy w Go z agentem i chcą przestać przeglądać każdą goroutine wzrokiem. Szczegóły produkcyjne trafiają od razu do promptów, a dowód ich poprawności dostarcza maszyna.
Co wyniesiesz z tych receptur Go
Dział zatytułowany „Co wyniesiesz z tych receptur Go”- Jeden blok reguł Go — w
AGENTS.mddla Claude Code i Codeksa oraz skopiowany do reguły projektu w Cursorze — oraz bramkęmake check, którą agent uruchamia, zanim powie „gotowe” - Pięć gotowych do wklejenia promptów: serwer HTTP na chi z graceful shutdown, ograniczony batch na
errgroup, repozytorium pgx z dobraną pulą i bezpiecznymi transakcjami, testy table-driven i Testcontainers oraz usługę gRPC z mapowaniem statusów - Tabelę weryfikacji, która mówi, co łapie każda bramka, więc czytasz wynik bramki zamiast diffu
- Prompty naprawcze na sześć sposobów, w jakie Go pisane przez agenta psuje się na produkcji
Przygotuj konwencje Go i bramkę, którą agent musi przejść
Dział zatytułowany „Przygotuj konwencje Go i bramkę, którą agent musi przejść”Większość pracy wykonują dwa pliki: plik reguł, który agent czyta przy każdym zadaniu, i jedno polecenie, które kończy się błędem, gdy kod nie jest gotowy na produkcję. Napisz je raz na repozytorium, zanim poprosisz o jakikolwiek kod.
-
Przypnij wspierany toolchain w
go.mod. 26 września 2026 najnowszym wydaniem jest Go 1.27.1 (wedługproxy.golang.org), więc wspierane linie to Go 1.26 i 1.27. Jawna dyrektywagopowstrzymuje agenta przed idiomami sprzed wersji 1.22.module github.com/yourorg/ordersgo 1.26require (github.com/go-chi/chi/v5 v5.3.2github.com/jackc/pgx/v5 v5.11.0github.com/stretchr/testify v1.12.1go.uber.org/goleak v1.3.0go.uber.org/zap v1.28.0golang.org/x/sync v0.23.0)To najnowsze wersje z
proxy.golang.orgna dzień 2026-09-26. Później podnoś je przezgo get -u ./...; chodzi o to, żeby agent widział aktualne wersje główne. -
Dodaj bramkę jako cel
make check. To definicja „gotowe” dla każdego promptu na tej stronie..PHONY: checkcheck:test -z "$$(gofmt -l .)"go vet ./...golangci-lint run ./...go test -race -shuffle=on -count=1 ./...govulncheck ./...Zainstaluj raz oba zewnętrzne narzędzia (terminal):
Okno terminala go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0go install golang.org/x/vuln/cmd/govulncheck@latest -
Umieść reguły Go w
AGENTS.mdw katalogu głównym repozytorium. Codex czytaAGENTS.mdnatywnie; zakładki poniżej pokazują, jak Claude Code ładuje ten sam plik i jak skopiować go do reguły Cursora.## Go conventions- Go 1.26+. Loop variables are per-iteration: never write `x := x` copies.- Layout: cmd/, internal/. No package-level mutable state.- Every blocking function takes ctx context.Context first. Never callcontext.Background() outside main() and tests.- Every `go` statement has a documented exit path on ctx cancellation.Prefer errgroup with SetLimit over raw goroutines.- Wrap errors with fmt.Errorf("op: %w", err). Compare with errors.Is/As, never ==.- Transactions use pgx.BeginFunc; do not hand-roll commit/rollback.- Tests are table-driven with t.Parallel(); use testify require/assert.- Never edit an existing *_test.go assertion or add t.Skip to make a test pass.If a test looks wrong, stop and say why.- Done means `make check` passes. Paste its last 20 lines in your final message.
Najważniejsze są dwie ostatnie reguły. Pierwsza nie pozwala agentowi osłabiać testów, żeby dojść do zielonego wyniku; dlaczego to najczęstszy sposób, w jaki praca agenta udaje sukces, wyjaśnia strona ochrona wyroczni testów. Druga zamienia „chyba działa” w dowód, który przeczytasz w dziesięć sekund.
Podłącz reguły i bramkę w każdym narzędziu
Dział zatytułowany „Podłącz reguły i bramkę w każdym narzędziu”Plik reguł i prompty są identyczne we wszystkich narzędziach. Różni się to, jak każde narzędzie ładuje reguły, uruchamia bramkę i przegląda wynik.
Na kanale latest (v2.1.277+) Claude Code czyta AGENTS.md, gdy w projekcie nie ma CLAUDE.md. Jeśli masz już CLAUDE.md albo korzystasz z kanału stable, dodaj import. Składnia importu @path wczytuje wskazany plik do kontekstu przy starcie:
@AGENTS.mdFormatuj każdy plik Go edytowany przez agenta hookiem PostToolUse w .claude/settings.json:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | grep '\\.go$' | xargs -r gofmt -w" } ] } ] }}Nawigację świadomą typów da agentowi plugin gopls-lsp z oficjalnego marketplace’u Anthropic. Plugin tylko rejestruje serwer, więc najpierw zainstaluj gopls:
go install golang.org/x/tools/gopls@latestclaude plugin install gopls-lsp@claude-plugins-officialZanim otworzysz pull request, uruchom w sesji /code-review, żeby uzyskać drugą opinię o diffie.
Codex czyta AGENTS.md bezpośrednio, więc dodatkowy plik nie jest potrzebny. Dodaj serwer MCP wbudowany w gopls (podpolecenie gopls mcp, obecne w gopls v0.23.0), żeby agent odpytywał symbole, referencje i diagnostykę zamiast grepować:
go install golang.org/x/tools/gopls@latestcodex mcp add gopls -- gopls mcpUruchom zadanie interaktywnie z zapisem w obszarze roboczym i prośbą o zgodę na wszystko szersze:
codex -s workspace-write -a on-request "Implement ProcessBatch as described in docs/tasks/batch.md. Done means make check passes."Przejrzyj gałąź, zanim ją wypchniesz:
codex review --base mainUtwórz regułę projektu w ustawieniach Rules Cursora z tą samą listą co w AGENTS.md, zawężoną do plików Go, żeby agent ładował ją przy każdym pliku .go. Dodaj serwer MCP gopls w konfiguracji MCP Cursora (.cursor/mcp.json):
{ "mcpServers": { "gopls": { "command": "gopls", "args": ["mcp"] } }}Do wieloplikowych szkieletów z tej strony używaj Plan Mode, żeby zatwierdzić listę plików, zanim powstanie jakikolwiek kod. Niezależne zadania uruchamiaj w Worktrees, a pull request oddaj do przeglądu Bugbotowi.
Zacznij w każdym narzędziu od jego domyślnego modelu i podnieś poziom effort, zanim zmienisz model; aktualne domyślne modele znajdziesz w przeglądzie modeli.
Zbuduj produkcyjną usługę Go z agentem krok po kroku
Dział zatytułowany „Zbuduj produkcyjną usługę Go z agentem krok po kroku”Każdy krok to jeden prompt, fragment dobrej odpowiedzi i bramka, która ją potwierdza. Wykonuj je po kolei: późniejsze prompty zakładają, że serwer i repozytorium już istnieją.
Jak wygenerować serwer HTTP w Go, który czysto się zamyka?
Dział zatytułowany „Jak wygenerować serwer HTTP w Go, który czysto się zamyka?”Najczęstsze przeoczenie w pierwszym podejściu to zamykanie. Agent uruchamia ListenAndServe w goroutine i kończy proces bez dokończenia trwających żądań, więc każde wdrożenie gubi ruch.
Dobra odpowiedź ma taki rdzeń:
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)defer stop()
go func() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { logger.Fatal("listen", zap.Error(err)) }}()
<-ctx.Done()shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()if err := srv.Shutdown(shutdownCtx); err != nil { logger.Error("shutdown", zap.Error(err))}Nie musisz tego sprawdzać wzrokiem. Test zamykania nie przejdzie, jeśli Shutdown użyje anulowanego ctx, bo Shutdown zwróci wtedy context.Canceled, zanim trwające żądanie się zakończy. Timeouty serwera są ważne, bo http.Server domyślnie nie ma deadline’u odczytu ani zapisu, a go vet ci o tym nie powie.
Jak ograniczyć goroutines, żeby batch nie wyczerpał bazy danych?
Dział zatytułowany „Jak ograniczyć goroutines, żeby batch nie wyczerpał bazy danych?”„Przetwórz te elementy współbieżnie” daje nieograniczony fan-out go func(). Pod obciążeniem otwiera jedno połączenie z bazą na element i wyczerpuje pulę albo limit deskryptorów plików.
func (s *Service) ProcessBatch(ctx context.Context, items []Item) error { g, ctx := errgroup.WithContext(ctx) g.SetLimit(8) for _, item := range items { g.Go(func() error { if err := ctx.Err(); err != nil { return err } return s.processItem(ctx, item) }) } if err := g.Wait(); err != nil { return fmt.Errorf("process batch: %w", err) } return nil}Dowodem jest tu goleak.VerifyTestMain: pakiet nie przejdzie testów, jeśli po ich zakończeniu nadal działa jakakolwiek goroutine. Razem z -race w make check łapie dwa błędy współbieżności, których nie widać w diffie.
Jak skłonić agenta do napisania repozytorium pgx, które wytrzyma obciążenie?
Dział zatytułowany „Jak skłonić agenta do napisania repozytorium pgx, które wytrzyma obciążenie?”Wyczerpanie puli to klasyczny błąd „działa na dev, pada na produkcji”. Jeden szczegół, którego agenci zwykle nie znają: domyślne MaxConns w pgxpool to większa z wartości 4 i runtime.NumCPU() (źródła pgx v5.11.0), więc kontener z dwoma vCPU dostaje cztery połączenia, jeśli nie ustawisz rozmiaru puli.
Prompt celowo wskazuje pgx.BeginFunc. Pozostawiony sam sobie agent pisze własny helper WithTx z odroczonym recover() i nazwanym zwracanym błędem, który jest poprawny tylko wtedy, gdy każda gałąź jest poprawna. BeginFunc zatwierdza transakcję, gdy callback zwraca nil, a w przeciwnym razie ją wycofuje, także gdy callback panikuje, więc jest mniej kodu agenta do przeglądania.
Prompt prosi też o kod 23505, a nie pgx.ErrNoRows, dla zduplikowanego adresu e-mail. INSERT, który narusza ograniczenie unikalności, zwraca *pgconn.PgError, nigdy ErrNoRows, a pomylenie jednego z drugim daje 500 tam, gdzie klient oczekiwał 409.
Żeby agent czytał prawdziwy schemat zamiast zgadywać nazwy kolumn, podłącz serwer MCP bazy danych wyłącznie do bazy deweloperskiej. Przewodnik po serwerach MCP dla baz danych porównuje opcje i ich przełączniki tylko do odczytu. Nie używaj @modelcontextprotocol/server-postgres: pakiet jest oznaczony w npm jako przestarzały i zarchiwizowany.
Jak generować testy Go, które łapią prawdziwe błędy?
Dział zatytułowany „Jak generować testy Go, które łapią prawdziwe błędy?”Nazwij przypadki, inaczej dostaniesz pokrycie wyłącznie happy path. Potem dodaj jeden test integracyjny na prawdziwym Postgresie, bo zamockowane repozytorium nie wyłapie błędnej kolumny w SQL ani brakującego indeksu unikalnego.
Nazwy funkcji w drugim prompcie pochodzą z testcontainers-go v0.44.0 (sprawdzone 2026-09-26). Starszy punkt wejścia RunContainer nadal się kompiluje, więc bez jawnych nazw agent często po niego sięga.
Prośba o wypisanie przypadków przed kodem daje ci jedyną rzecz, którą warto przeczytać: listę testów. Jeśli lista pokrywa zachowanie, na którym ci zależy, a make check jest zielone, czytanie ciał testów nie było potrzebne. Więcej o projektowaniu testów z agentem znajdziesz na stronach o testach jednostkowych z agentem i testach integracyjnych.
Jak postawić usługę gRPC, która nie ujawnia błędów wewnętrznych?
Dział zatytułowany „Jak postawić usługę gRPC, która nie ujawnia błędów wewnętrznych?”Wygenerowany handler, który zwraca surowy err, potrafi wysłać klientowi treść nieudanego zapytania SQL, więc poproś o jedną funkcję, która odpowiada za mapowanie błędów domenowych na kody statusu gRPC.
Dobra odpowiedź ma taki rdzeń:
func toStatus(err error) error { switch { case errors.Is(err, users.ErrNotFound): return status.Error(codes.NotFound, "user not found") case errors.Is(err, users.ErrDuplicateEmail): return status.Error(codes.AlreadyExists, "email already registered") case errors.Is(err, context.DeadlineExceeded): return status.Error(codes.DeadlineExceeded, "deadline exceeded") default: slog.Error("unmapped error", "err", err) // szczegóły zostają w logach return status.Error(codes.Internal, "internal error") }}Kluczowa jest funkcja mapująca: każdy błąd opuszcza usługę przez nią, a gałąź default zostawia wewnętrzne szczegóły w logach zamiast w odpowiedzi dla klienta. Testy na bufconn potwierdzają każde mapowanie bez sieci i bez człowieka czytającego handlery.
Jak zweryfikować Go napisane przez agenta bez czytania każdej linijki?
Dział zatytułowany „Jak zweryfikować Go napisane przez agenta bez czytania każdej linijki?”Czytaj wynik bramek i listę testów, nie diff. Każda bramka poniżej łapie klasę błędów, które w przeglądzie wyglądają dobrze, i dlatego czytanie jest najsłabszą kontrolą kodu współbieżnego w Go.
| Bramka | Polecenie | Co łapie |
|---|---|---|
| Formatowanie i vet | gofmt -l ., go vet ./... | Niesformatowane pliki, błędne czasowniki Printf, kopiowane blokady, nieosiągalny kod |
| Lint | golangci-lint run ./... | Niesprawdzone błędy (errcheck), nieskuteczne przypisania, uwagi staticcheck; == na opakowanych błędach po włączeniu errorlint |
| Race detector | go test -race -shuffle=on -count=1 ./... | Wyścigi danych i testy zależne od kolejności |
| Wycieki goroutines | goleak.VerifyTestMain(m) w każdym pakiecie współbieżnym | Goroutines bez ścieżki wyjścia |
| Podatności | govulncheck ./... | Znane CVE w ścieżkach kodu, które faktycznie wywołujesz |
| Integracja | Testy Testcontainers bez -short | Błędny SQL, brakujące ograniczenia, złe migracje |
| Obciążenie | Dwukrotność oczekiwanego szczytu na stagingu, np. echo "GET https://staging.example.com/api/v1/items" | vegeta attack -rate=400 -duration=60s | vegeta report | Wyczerpanie puli, brakujące timeouty, rosnące zużycie pamięci |
| Agent przeglądający | /code-review (Claude Code), codex review --base main (Codex), Bugbot (Cursor) | Błędy logiki i brakujące przypadki, których bramki nie wyrażą |
Pierwszych sześć uruchamiaj w CI przy każdym pull requeście; plik workflow znajdziesz we wzorcach potoków CI/CD. Inżynier odpowiedzialny za usługę zatwierdza trzy rzeczy: listę przypadków testowych, zieloną bramkę i wykres z testu obciążeniowego. Człowiek czyta tylko fragmenty oznaczone przez agenta przeglądającego oraz kod dotykający transakcji lub goroutines. Jak podzielić tę pracę w zespole, opisuje strona o przeglądzie PR z agentem.
Gdy Go napisane przez agenta psuje się na produkcji
Dział zatytułowany „Gdy Go napisane przez agenta psuje się na produkcji”Każda awaria poniżej przechodzi zestaw testów happy path. Każda ma objaw, który możesz wykryć, i sposób naprawy, który możesz wkleić.
-
Wyciek goroutines. Objaw:
go_goroutinesw metrykach rośnie i nigdy nie spada. Przyczyna:go func()zablokowane na wysyłce do kanału bez odbiorcy albo bez gałęzictx.Done(). Naprawa: dodajgoleak.VerifyTestMaindo pakietu, a potem wklej prompt: „Audit everygostatement in this package. For each one, name its exit path under context cancellation, and fix any that have none. Done meansmake checkpasses.” -
Kontekst zgubiony w połowie stosu. Objaw: żądania działają dalej po rozłączeniu klienta, a deadline’y nic nie robią. Przyczyna:
context.Background()lubcontext.TODO()głęboko w handlerze. Naprawa:grep -rn "context.Background()\|context.TODO()" --include=*.go internal/i poproś agenta, żeby przeprowadziłctxprzez każdą znalezioną funkcję. -
Wyczerpanie puli. Objaw: skoki opóźnień, a potem żądania wiszą w oczekiwaniu na połączenie. Przyczyna: brakujące
rows.Close(), zapytanie napoolwewnątrz transakcji (dwa połączenia na żądanie) albo domyślny rozmiar puli. Naprawa: ustawMaxConnsświadomie, wystawpool.Stat()w endpoincie diagnostycznym i wklej prompt: „Find everyQuerycall whose rows are not closed on every path, and every pool query made while apgx.Txis open.” -
Przestarzałe idiomy z dawnych danych treningowych. Objaw: kopie
item := item, importyioutilalbo wszędzieinterface{}. Przyczyna: model czerpie z kodu sprzed Go 1.22. Naprawa: dyrektywagoi reguła wAGENTS.mdzapobiegają większości przypadków; na resztę wklej prompt: „Modernise this package for Go 1.26: remove loop-variable copies, replaceioutil, and useany. Change no behaviour.” -
Agent osłabia test, żeby dojść do zielonego wyniku. Objaw: poluzowana asercja, usunięty przypadek albo dodany
t.Skipw tym samym diffie co poprawka. Naprawa: przywróć plik testu, przypomnij regułę zAGENTS.mdi skonfiguruj CI tak, żeby odrzucało pull request zmieniający, przenoszący lub usuwający istniejący*_test.gobez etykietytest-change. Krok jest bramką tylko wtedy, gdy pull request nie może go usunąć: GitHub uruchamia plik workflow z samego pull requesta, więc obejmij/.github/regułąCODEOWNERSi wymuś tę recenzję przez ochronę gałęzi lub ruleset. Ten krok zakładaactions/checkoutzfetch-depth: 0; szerszą politykę opisuje strona ochrona wyroczni testów.- name: Block edits to existing tests without a test-change labelif: ${{ !contains(github.event.pull_request.labels.*.name, 'test-change') }}# Needs actions/checkout with fetch-depth: 0: origin/main... diffs from the merge base.run: |# --name-status lists a rename as R<score> OLD NEW; field 2 is the path that existed beforeif git diff --name-status --diff-filter=MDR origin/main... | awk -F'\t' '{print $2}' | grep '_test\.go$'; thenecho "Existing tests changed without the test-change label"; exit 1fi -
golangci-lint wywraca się, zanim cokolwiek sprawdzi. Objaw: błąd, że wersja Go użyta do zbudowania golangci-lint jest niższa niż docelowa, albo błąd konfiguracji w
.golangci.ymlnapisanym przez agenta w formacie v1. Naprawa: zainstaluj golangci-lint v2 zbudowany wersją Go co najmniej tak nową jak twoja dyrektywagoi uruchomgolangci-lint migratena starej konfiguracji.
Dokąd dalej z usługami w Go
Dział zatytułowany „Dokąd dalej z usługami w Go”- Jeśli repozytorium nie jest jeszcze gotowe na agenta, zacznij od strony repozytorium gotowe na agenta.
- Utwardź warstwę danych: wzorce optymalizacji SQL i wzorce migracji baz danych.
- Wdróż usługę: wzorce Dockera i wzorce Kubernetesa.
- Zdecyduj, co agent może uruchamiać bez nadzoru: uprawnienia i sandboxing.
- Porównaj z innym kompilowanym backendem: wzorce programowania w Rust albo wróć do przeglądu receptur backendowych.