TGViewer
Библиотека Go-разработчика | Golang Библиотека Go-разработчика | Golang @goproglib · 24.1K subscribers
Post #7287 3.02K
👨‍💻 Строка, похожая на комментарий, которая молча сломала прод

В одной команде была здоровая культура тестов. Каждый важный эндпоинт покрыт, CI не пускал PR в мердж, пока тесты не зелёные. Однажды утром прилетел инцидент на проде. Разработчик правил логику обработки ответа стороннего сервиса, прогнал тесты локально, получил 144 passed, отправил PR, CI стал зелёным, фикс уехал в прод. Через час прод лёг.

При разборе выяснилось, что нужный тест TestCartActionHandler вообще не запускался. Не падал, не скипался, его просто не было в списке из 144 тестов. В первой строке файла, над объявлением пакета, стояла строка, которую все приняли за комментарий:
//go:build integration


Это и оказалось причиной. Дальше разберём, как одна такая строка выкидывает целый файл из сборки.

❓ Что такое build tags

Build tag это директива в начале .go файла, которая говорит тулчейну, включать ли файл в конкретную сборку. Работает как условный шлюз на уровне файла. Когда вы запускаете go build или go test, тулчейн проверяет ограничение каждого файла против текущего контекста сборки.

Выражение истинно, файл компилируется. Выражение ложно, файл исключается полностью, как будто его нет на диске.

Это не скип в рантайме, это исключение на этапе компиляции. Файл вообще не попадает в бинарь, поэтому ни одна тестовая функция из него не регистрируется и никакого вывода от него вы не увидите.

🛠 Синтаксис

Тег это однострочная директива в самом верху файла, до package, до импортов:
//go:build <выражение>


Три правила, которые проверяет тулчейн. Директива стоит в первой строке (выше допустимы только пустые строки и другие //go: директивы). Префикс ровно //go:build, без пробела между // и go. Выражение булево, собирается из имён тегов через &&, ||, ! и скобки.

🛠 Язык выражений

Файл с тегом попадёт в сборку, только если вы явно попросите через -tags. Не попросили, файла как бы не существует.
//go:build tagname              // истинно, когда задан -tags tagname
//go:build !tagname // истинно, когда tagname НЕ задан
//go:build tagA && tagB // оба заданы
//go:build tagA || tagB // задан любой из двух
//go:build (tagA || tagB) && !tagC // группировка скобками

Активируется тег через флаг -tags у любой go команды:
go build -tags linux ./...
go test -tags integration ./...
go run -tags debug main.go
go test -tags integration,gpu ./...


Флаг работает одинаково для go build, go test, go run и go vet. То, что вы передаёте, добавляется в активный набор тегов только для этого вызова и не сохраняется.

🛠 Где это нужно

Платформенные реализации. Один пакет, одна сигнатура функции, разный файл под каждую ОС. Компилятор сам подберёт нужный по GOOS, без switch runtime.GOOS и без мёртвого кода в бинаре:
//go:build linux
func watch(path string) { /* inotify */ }

//go:build darwin
func watch(path string) { /* kqueue */ }


Так устроена и стандартная библиотека. У os.RemoveAll три отдельных файла, каждый со своим тегом под unix, windows и фолбэк на остальное.

Разделение unit и интеграционных тестов. Интеграционным часто нужна живая инфраструктура, юнит тестам нет. Тег прячет тяжёлые тесты, чтобы локально не поднимать базу ради простой проверки:
//go:build integration

package repository_test
func TestUserRepository_Create(t *testing.T) { ... } // ходит в реальный Postgres

Опциональные тяжёлые зависимости и debug инструментация работают так же. Под тегом gpu лежит реализация на CUDA, без тега чистый Go фолбэк. Под тегом debug живут дампы состояния, которые в прод бинарь не попадают вообще.

❓ Чем закончился инцидент

Файл с тестом имел тег //go:build integration, а команда make test запускала go test без -tags integration. Файл молча исключался из прогона, и его тесты не запускались ни разу с момента старта проекта. Скелет теста скопировали из соседнего проекта, где make test сознательно прокидывал нужный тег. Тот, кто копировал, принял первую строку за комментарий. Ревьюер тоже не заметил.

Таких файлов в кодовой базе нашлось ещё 13. После снятия тега и прогона набор показал 208 тестов вместо 144.
--- FAIL: TestHandlePaymentPartyResponse (0.31s)
--- FAIL: TestUserSignatureValidation (0.18s)
...
FAIL: 9 tests failed, 208 tests total


64 теста не выполнялись никогда, а среди 9 падений были баги, уже жившие в проде. Зелёный бейдж CI всё это время был ложноположительным.

➡️ Как чуть не сломался прод

📍 Навигация: Вакансии • Задачи • Собесы • Канал в Max

🐸 Библиотека Go-разработчика

#GoDeep
  • ❤ 17
  • 👍 1
  • 🤔 1
More from @goproglib
  1. Sep 28, 2026🤔 Вопрос с собеседования по Go Что выведет программа? ❤️ — 1 true / 0 false 🔥 — 1 true /…
  2. Sep 28, 2026👩‍💻 Что на самом деле происходит внутри Go map? После Go 1.24 обычный map внутри работае…
  3. Sep 26, 2026🔥 В Go 1.27 появился portable SIMD До этого SIMD-оптимизации в Go требовали архитектурног…
  4. Sep 25, 2026🤡🤡 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека Go-разработчика #GoGiggle
  5. Sep 25, 2026💡 Код работает. А data race уже есть В Go можно записать значение в одной горутине, прочи…
  6. Sep 23, 2026💥 TCP/IP: что происходит с данными в сети Когда Go-приложение отправляет данные по сети,…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →