Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 10 из 25 ~20 мин +50 XP

buf CLI: линтинг и breaking changes

Схема protobuf — единственный источник правды для клиента и сервера, значит она заслуживает той же дисциплины, что и код: линт и контроль совместимости. Разбираем buf lint: категории правил от MINIMAL до STANDARD, типовые ошибки вроде PACKAGE_DIRECTORY_MATCH, формат вывода path:line:col. Затем buf breaking: категории FILE и WIRE, сравнение с веткой main через --against и их место в CI. buf lint и buf breaking превращают эволюцию контракта из лотереи в ревью.

Содержание урока · 10 разделов

Мы уже много раз прогоняли buf generate — а значит, у buf в проекте есть конфигурация, и команда, которую вы набирали по привычке, умеет гораздо больше, чем кодогенерация. Этот урок — про две команды, которые охраняют сам контракт. buf lint проверяет протофайлы на соответствие правилам стиля и структуры: имена пакетов, версии, регистр полей, суффиксы сервисов. buf breaking сравнивает текущую схему с предыдущей версией и находит несовместимые изменения — те самые, которые тихо ломают уже собранные клиенты. Зачем это нужно, если компилятор protoc и так отклонит битую схему? Потому что компилятор ловит синтаксис, а не семантику: поле с номером 2, переименованным в name, прекрасно компилируется и при этом меняет JSON-имя для TypeScript-фронта из девятого урока и ломает совместимость по wire-формату. В Go-мире вы проходили эволюцию схем в уроке про версионирование gRPC — buf breaking автоматизирует ровно ту дисциплину. План такой: сначала конфигурация buf.yaml и категории правил buf lint, потом разбор типовых сообщений линтера, затем buf breaking, его категории и интеграция в CI.

buf.yaml: модуль и две секции правил#

Конфигурация buf-а живёт в buf.yaml — файле, который мы завели ещё в втором уроке, объявив в нём модуль с path: proto. Актуальная версия конфига — v2: секция modules перечисляет корни модулей, а секции lint и breaking задают, какие категории правил применять. Категории — это уровни строгости: MINIMAL — только то, что влияет на совместимость и генерацию; BASIC добавляет правила стиля и структуры; STANDARD — рекомендуемый уровень, включающий BASIC плюс правила API-дизайна вроде уникальности имён rpc; COMMENTS требует комментариев на публичные элементы; UNARY_RPC — спорное правило, отговаривающее от unary rpc без reason. В конфиге пишут use: [STANDARD] — и при необходимости выключают или добавляют отдельные правила: кроме except-списка, есть enum-правила и опции по умолчанию. Заметьте, что buf.yaml v2 вытеснил старую пару buf.yaml + buf.work.yaml из v1: если вы встретите в документации проекты с workspace-файлом, знайте, что это legacy-раскладка, и migrated конфиг выглядит проще. Для нашего курса достаточно одного модуля proto — и ровно одного файла buf.yaml в корне репозитория.

buf.yaml курса: конфигурация в шесть строк

GO
version: v2
modules:
  - path: proto
lint:
  use: [STANDARD]
breaking:
  use: [FILE]

Минимальный честный конфиг: один модуль с корнем proto (как во всех уроках курса), линт на категории STANDARD — рекомендуемый набор, breaking на категории FILE — самый строгий уровень, о нём ниже. Команды не требуют аргументов: buf lint сканирует модуль, buf breaking сравнивает его с источником, который вы укажете флагом --against. То, что конфиг не перечисляет файлы поимённо, — принципиально: buf сам находит все .proto внутри корня модуля.

buf lint: что проверяется и как читать вывод#

Команда buf lint без аргументов проходит по всем файлам модуля и печатает находки в формате path:line:column: message — ровно как компилятор, только про стиль. Правил несколько десятков, но они запоминаются семьями. Имена: PACKAGE_VERSION_SUFFIX требует версии в конце пакета — наш notes.v1 из первого урока проходил бы, а голый package notes — нет; PACKAGE_DIRECTORY_MATCH требует, чтобы каталог повторял пакет — proto/notes/v1 для notes.v1, и именно это правило мы впервые увидели в третьем уроке при добавлении eliza; FIELD_LOWER_SNAKE_CASE следит за полями, MESSAGE_PASCAL_CASE и SERVICE_PASCAL_CASE — за типами и сервисами. Дизайн API: SERVICE_SUFFIX требует Service на конце имени сервиса, ENUM_ZERO_VALUE_SUFFIX — суффикс _UNSPECIFIED у нулевого значения enum, ENUM_VALUE_PREFIX — префикс с именем enum, RPC_REQUEST_RESPONSE_UNIQUE запрещает переиспользовать один message в разных rpc — потому что схема с общим запросом не может эволюционировать независимо. COMMENTS-категория добавит требований к документации, если вы её включите. buf lint — отдельная команда, а не шаг генерации: она стартует быстрее компиляции и хорошо ставится в pre-commit; большинство команд гоняет buf lint в CI на каждый PR.

Типовой вывод buf lint

GO
$ buf lint
proto/notes/notes.proto:3:1:Files with package "notes" must be
    within a directory "notes" relative to root but were in directory "notes/notes.proto".
proto/notes/notes.proto:3:1:Package name "notes" should be suffixed with a correctly
    formed version, such as "notes.v1".
proto/notes/notes.proto:9:5:Field name "NoteTitle" should be lower_snake_case, such as "note_title".

Формат path:line:column: message делает находки исправимыми за секунду — buf указывает файл и позицию. Здесь три находки одной схемы: пакет без версии и без каталога (два разных правила) и поле в PascalCase. Заметьте: это не придирки ради стиля — каждая из них закрывает конкретную дыру: без версии пакета эволюция API не выражается в имени, без каталога модули конфликтуют по путям, а несогласованный регистр полей рождает расходящиеся JSON-имена в клиентах.

buf lint упал: Package name "notes" should be suffixed with a correctly formed version. Какое правило и что исправить?

buf breaking: что считает несовместимостью#

Линт охраняет стиль, но главная угроза контракту — не стиль, а изменение смысла. buf breaking сравнивает текущую схему с эталонной версией и рапортует о несовместимостях по категориям строгости. FILE — самая строгая, уровень по умолчанию: ловит переименования файлов, пакетов, типов и полей — даже те, что совместимы на проводе, но ломают компиляцию клиентов с чужими импортами. WIRE — совместимость формата на проводе: изменение номера поля, его типа, смена скалярного типа. PACKAGE — правила вокруг пакета: версию нельзя удалить, пакет не должен терять совместимость со своими прошлыми версиями. TYPE — консистентность типов: enum не должен терять значения (клиенты со старым кодом не поймут новый wire-статус), поле не должно менять имя. WIRE_JSON добавляет контроль JSON-представления: переименование поля меняет JSON-имя, и фронтенд, сериализующий по имени, сломается. Эталон задаётся флагом --against, и источник — не обязательно файл: это может быть каталог, git-ветка в нотации .git#branch=main или модуль в BSR — реестре схем Buf, о котором пойдёт речь в двенадцатом уроке. Команда buf breaking --against .git#branch=main — это и есть минимальная страховка совместимости, которую стоит поставить в CI сегодня.

buf breaking против ветки main

GO
$ buf breaking --against '.git#branch=main'
proto/notes/v1/notes.proto:10:3:Field "1" with name "title" on message "Note"
    changed type from "string" to "int32".
proto/notes/v1/notes.proto:16:3:Previously present field "2" with name "created_at"
    on message "Note" was deleted.
proto/notes/v1/notes.proto:5:1:Package name "notes.v2" changed to "notes.v1".

Три находки — три катастрофы, каждая из которых компилируется без единого warning-а: смена типа поля ломает wire-декодирование у старых клиентов, удаление поля обнуляет данные, а смена пакета меняет путь процедуры для всех RPC. Правильные версии этих изменений: новое поле с новым номером вместо смены типа, поле помечается reserved вместо удаления, новая версия — новый пакет notes.v2 рядом со старым. buf breaking переворачивает ревью: не хочу ловить поломку в проде, хочу видеть её в PR.

Что скажет buf breaking с категорией FILE против main?

Прочитайте код и выберите, что он выведет:

// Было в main:
message Note {
  int64 id = 1;
  string title = 2; // переименовали в "name", номер не тронули
}
// Стало в ветке:
message Note {
  int64 id = 1;
  string name = 2;
}

Вы удалили поле из message и пометили его номер в reserved. buf breaking молчит. Что это значит?

Место в CI: где стоят линт и breaking#

Две команды имеют смысл только тогда, когда они запускаются автоматически — вручную их забудут на третий день. Разумный расклад из трёх точек. Первая — pre-commit: buf lint на изменённых файлах, быстро и локально, стиль не доезжает до ревью. Вторая — CI на pull request: buf lint по всему модулю плюс buf breaking --against .git#branch=main; теперь несовместимое изменение не пройдёт ревью молча — оно упадёт с конкретным path:line:col и именем правила. Третья — релиз: при публикации модуля и генерации артефактов buf breaking против последней опубликованной версии, потому что ветка main может уйти вперёд с тех пор, как схема выехала к клиентам. Отдельно стоит договориться о политике: что делать, когда breaking нужен по-настоящему — правильный путь в proto-мире не обходить проверку, а выпускать новую версию пакета (notes.v2 рядом с notes.v1) или расширять сообщение совместимым способом — новое поле вместо смены типа. Категории use в buf.yaml и есть ваш договор с командой: STRICT-набор запрещает почти всё, WIRE — разрешает косметику, если она честно не трогает провод. Запишите политику в сам buf.yaml и в CONTRIBUTING — конфигурация, которую никто не понимает, превращается в шум, который в конце концов просто выключают.

Категории buf breaking: что ловит каждая#

КатегорияЧто ловитПример нарушения
FILEпереименования файлов, пакетов, типов, полейполе title переименовано в name
WIREсовместимость бинарного форматаномер поля переиспользован под другой тип
TYPEконсистентность типов и enumenum потерял существующее значение
PACKAGEсовместимость на уровне пакетаверсия удалена из имени пакета
WIRE_JSONJSON-представление для клиентовсменилось JSON-имя поля для фронта

Какое изменение buf breaking с use: [FILE] пропустит, а с use: [WIRE] тоже пропустит, но с WIRE_JSON — поймает?

Линт как документация API-дизайна#

Есть у buf lint и вторая, менее очевидная роль: правила — это выжимка лучших практик protobuf-сообщества, и чтение их списка само по себе учит дизайну схем. RPC_REQUEST_RESPONSE_UNIQUE — потому что общий message в двух rpc превращает любое расширение одного вызова в ломку другого; ENUM_ZERO_VALUE_SUFFIX — потому что нулевое значение enum обязано означать «неизвестно», и только его; ENUM_VALUE_PREFIX — потому что C++-скоупы и мультиязычные генераторы превращают безымянные значения в коллизии; SERVICE_SUFFIX и PASCAL-правила — потому что генераторы разных языков выводят из них имена своих типов. Когда линтер ругается, полезно не просто молча исправить, а понять, какую аварию правило предотвращает — это быстрее, чем наступать на грабли самому. И у этого есть организационное следствие: новый разработчик, подключившийся к проекту с включённым STANDARD, получает весь накопленный опыт в виде машины, которая отклоняет плохие схемы до ревью — дешевле любой Wiki. Если в проекте есть уникальные ограничения — например, обязательный префикс пакетов компании или запрет сторонних импортов, — buf позволяет объявлять собственные правила, но это уже тема продвинутых конфигов, а базового STANDARD для 90 процентов проектов хватает с головой.

Соберите buf.yaml и команду CI

Восстановите конфигурацию buf-а и команду проверки несовместимостей против ветки main.

version: modules: - path: proto lint: use: [] breaking: use: [] # В CI: несовместимость против main $ buf breaking --against '#branch=main'

Путь одного опасного изменения схемы

Расставьте события правки контракта с buf-охраной по порядку.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: buf lint и buf breaking

Термин

buf lint

нажмите, чтобы перевернуть
Определение

Проверка протофайлов по правилам стиля и структуры; вывод в формате path:line:column: message. Ставится в pre-commit и CI на каждый PR.

1 / 6

Что дальше по курсу#

Контракт под охраной: buf lint держит стиль и структуру схем, buf breaking не пускает несовместимые правки мимо ревью, а CI превратил дисциплину из надежды в механизм. Осталось автоматизировать соседний труд — кодогенерацию, и в одиннадцатом уроке мы дожмём buf generate: buf.gen.yaml во всех подробностях, локальные и удалённые плагины, воспроизводимость генерации без установки тулчейна. После этого — реестр схем BSR в двенадцатом, где buf breaking уже умеет сравнивать с опубликованной версией модуля. Для глубины полезно держать под рукой, как те же вопросы эволюции решаются руками в gRPC-мире: урок про оптимизацию и совместимость proto из курса gRPC — buf breaking автоматизирует ровно те правила, которые там выписаны. Программа всех двадцати пяти уроков — на странице курса.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • buf.yaml v2 описывает модули и две секции правил: lint.use и breaking.use; для курса — один модуль с корнем proto.
  • buf lint печатает находки в формате path:line:column: message; правила идут семьями: имена пакетов, каталоги, регистр полей, суффиксы сервисов.
  • Категории линта: MINIMAL, BASIC, STANDARD (рекомендуемый), COMMENTS, UNARY_RPC; выключение — except-список в конфиге.
  • buf breaking сравнивает схему с эталоном --against: каталог, git-ветка .git#branch=main или модуль BSR.
  • Категории breaking: FILE — имена, WIRE — бинарный формат, TYPE — типы и enum, PACKAGE — пакеты, WIRE_JSON — JSON-имена.
  • Удаление поля безопасно с reserved-номером; настоящая смена смысла — новая версия пакета, а не правка старой.
  • Минимальный CI: buf lint на PR плюс buf breaking против main; релизный контур сравнивает с последней опубликованной версией.

Домашнее задание

Включите охрану контракта в своём проекте из второго урока: убедитесь, что buf.yaml объявляет version: v2, модуль с корнем proto, lint.use: [STANDARD] и breaking.use: [FILE], затем прогоните buf lint на своей схеме и разберите каждую находку — выпишите правило, которое её породило, и какую аварию оно предотвращает. Сломайте нарочно: скопируйте notes.proto в отдельную ветку, поменяйте тип поля id с string на int32, удалите поле title без reserved и запустите buf breaking --against '.git#branch=main' — соберите весь вывод команды и к каждой строке подпишите, какая категория сработала и какой клиент бы сломался (Go-сервер из третьего урока, TS-фронт из девятого). Затем исправьте честно: верните поля с reserved, добавьте новые поля новыми номерами и убедитесь, что buf breaking молчит. Финальный шаг: добавьте в CI-конфиг проекта шаг buf lint и шаг buf breaking против main и проверьте на тестовом PR, что несовместимое изменение реально блокирует слияние.

Личные заметкиПусто
Хранятся только в вашем браузере (Connect · buf CLI: линтинг и breaking changes)

Вопросы и ответы по уроку

buf lint ругается на легаси-схемы, которые я не могу переписать. Как быть?

Выключите конкретные правила через except в секции lint вместо понижения категории целиком: use: [STANDARD] с except-списком сохранит остальную защиту. Легаси-модули можно вынести в отдельный модуль buf.yaml со своим набором правил, а новые схемы держать на полном STANDARD.

Чем отличается FILE-категория от WIRE в buf breaking?

WIRE проверяет бинарную совместимость формата на проводе: номера и типы полей. FILE строже: она ловит любые переименования файлов, пакетов, типов и полей, даже wire-совместимые, потому что они ломают компиляцию клиентов и JSON-контракты. Дефолт и безопасный выбор — use: [FILE].

buf breaking нужен, когда breaking change всё-таки требуется. Что делать?

Не выключать проверку, а менять схему честно: расширять сообщение новыми полями с новыми номерами, помечать удаляемое reserved или выпускать новую версию пакета (notes.v2) рядом со старой. Отключение правила — осознанное решение с ревью, а не обходной путь для каждой удобной правки.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Fyne25 уроков~13 чпрактика25 уроков уже доступны

Fyne: десктопные и мобильные GUI на Go

Курс Fyne: окна, виджеты, контейнеры, canvas, темы, binding, сборка под мобильные и десктоп. 25 уроков и итоговый менеджер задач.

  • Создавать окна и интерфейсы из виджетов
  • Управлять компоновкой: Box, Grid, Form
  • Рисовать примитивами canvas и строить свои виджеты
  • Связывать данные через binding