- https://github.com/aenix-org/cozyportal/issues/273
- https://github.com/aenix-org/cozyportal/issues/274
- https://github.com/aenix-org/cozyportal/issues/275
- https://github.com/aenix-org/cozyportal/issues/276
- https://github.com/aenix-org/cozyportal/issues/277
- https://github.com/aenix-org/cozyportal/issues/278
- https://github.com/aenix-org/cozyportal/issues/280
У нас 334 ошибки линтера после обновления golangci-lint до v2.10.1 со строгим конфигом. Работа разбита на 7 PR'ов, от безопасного к рискованному. Каждый шаг — отдельный PR, который можно ревьюить и откатывать независимо.
Линтеры:
gofmt(~3 ошибки)
Фундамент для всех остальных шагов. Применяем строгий .golangci.yml — убираем ~60 path-based exclusions, которые подавляли линтеры в production-коде. Запускаем автоформатирование.
Ценность: без этого шага остальные PR'ы будут показывать ложные ошибки. Конфиг — single source of truth для всей команды.
Риски: минимальные. gofmt — чисто косметические изменения, семантика кода не меняется. Но удаление path-exclusions "откроет" все 334 ошибки — до этого они были подавлены.
Важно: несмотря на смену конфига линтера, предлагается до последнего шага не требовать от команды соблюдения линтинга за пределами затронутого ими кода. Пока lint fixes в процессе, CI lint job будет красным — это ожидаемо и не должно блокировать работу.
Линтеры (~78 ошибок):
funcorder(50) — порядок функций: public перед privatewsl_v5(21) — пустые строки между логическими блокамиnlreturn(5) — пустая строка перед returngodot(2) — точка в конце комментариев
Механические правки без изменения логики: перестановка функций, добавление/удаление пустых строк, точки в конце комментариев.
Ценность:
funcorder— единообразная структура файлов, проще навигацияwsl/nlreturn— визуальное разделение блоков логики, читаемостьgodot— корректный godoc
Риски: нулевые по логике, но большой diff (перестановка функций). git blame будет показывать этот коммит — это цена, которую платим один раз.
Линтеры (~56 ошибок):
varnamelen(34) — слишком короткие имена переменныхgodoclint(16) — отсутствие godoc на экспортируемых типахnolintlint(6) — некорректные nolint директивы
Переименование коротких переменных (s → server, r → req), добавление godoc на экспортируемые типы, исправление некорректных nolint директив.
Ценность:
varnamelen— однобуквенные переменные непонятны вне контекста, особенно в длинных функцияхgodoclint— публичный API должен быть задокументированnolintlint— каждое подавление линтера должно объяснять ПОЧЕМУ (иначе это техдолг, который никто не вспомнит)
Риски: низкие. Переименование переменных может конфликтовать с открытыми PR'ами. Некоторые короткие имена оправданы конвенцией (ctx, err, ok) — их добавляем в ignore-names, а не переименовываем.
Линтеры (~55 ошибок):
revive(50) — мета-линтер: stuttering names, unused params, indent-error-flowgoconst(5) — повторяющиеся строковые литералы
Revive — это мета-линтер с ~40 правилами: stuttering names (console.ConsoleImage → console.Image), unused parameters, missing docs, indent-error-flow. Goconst — повторяющиеся строковые литералы выносятся в константы.
Ценность:
revive— покрывает Go code review guidelines, то что ревьюер должен ловить руками. Stuttering names — прямое нарушение Effective Gogoconst— повторяющиеся литералы это time bomb: меняешь одну, забываешь вторую
Риски: средние. Переименование типов — breaking change для внешних потребителей API. Нужно проверять, что переименования не ломают генерированный клиентский код и OpenAPI спеки.
Линтеры (~42 ошибки):
gocritic(30) — hugeParam, unnecessaryDefer, typeSwitchVarperfsprint(6) — заменаfmt.Sprintfнаstrconvprealloc(3) — аллокация слайсов с известной ёмкостьюstaticcheck(3) — deprecated API, мёртвый код
Ценность:
gocritic— ловит реальные баги (hugeParam для больших struct'ов — копирование вместо ссылки)staticcheck— deprecated API сломается при обновлении зависимостейperfsprint/prealloc— микрооптимизации, но в hot paths имеют значение
Риски: средние. Замена value на pointer receiver может изменить семантику (мутабельность). hugeParam fixes нужно проверять через тесты.
Линтеры (~50 ошибок):
dupl(27) — дублированные блоки кодаfunlen(12) — функции длиннее 60 строкgocyclo(6) — цикломатическая сложность > 15gocognit(4) — когнитивная сложность > 15cyclop(1) — комплексная сложность пакета
Самый трудоёмкий шаг. Дублированный код выносится в общие хелперы. Длинные функции разбиваются на более мелкие. Снижается цикломатическая и когнитивная сложность.
Ценность:
dupl— дублирование это главный источник рассинхронизации: правишь баг в одном месте, забываешь второеfunlen/gocyclo/gocognit— функции >60 строк или с complexity >15 невозможно ревьюить и тестировать. Это прямо коррелирует с количеством багов
Риски: высокие. Рефакторинг изменяет control flow. Каждый вынос хелпера — потенциальный regression. Обязательно покрыть unit-тестами ДО рефакторинга, потом проверить что тесты остались зелёными.
Линтеры (~68 ошибок):
gochecknoglobals(13) — глобальные переменныеwrapcheck(11) — ошибки без контекста (unwrapped)mnd(11) — magic numbersgodox(9) — TODO/FIXME в кодеerr113(7) — динамические ошибки вместо sentineltagliatelle(4) — несоответствие стиля struct tagserrcheck(3) — игнорируемые ошибкиnoinlineerr(3) — inline error handlinginamedparam(3) — неименованные параметры в интерфейсахnestif(2) — вложенные if'ыerrchkjson(1) — непроверенный JSON marshalforcetypeassert(1) — type assertion без проверки
Сборная солянка: оборачивание ошибок контекстом, sentinel errors вместо динамических, устранение глобальных переменных, замена magic numbers на константы, удаление TODO/FIXME.
Ценность:
wrapcheck/err113— без контекста в ошибках невозможно дебажить production. "connection refused" — где? какой сервис?gochecknoglobals— глобальное состояние ломает тестируемость и параллелизмmnd—if timeout > 30— 30 чего? секунд? минут?godox— TODO в коде = забытая задача; должна быть в трекере
Риски: высокие. wrapcheck меняет error messages (может сломать тесты, которые проверяют конкретные строки). err113 требует создания sentinel errors — меняет публичный API пакетов. gochecknoglobals может потребовать dependency injection рефакторинг.
- Каждый шаг — отдельный PR, ревьюится и мержится независимо
- Порядок от безопасного к рискованному: если шаг 6 зависнет на ревью, шаги 1–5 уже приносят пользу
- Группировка по типу изменений: легче ревьюить "50 перестановок функций" чем "3 перестановки + 2 переименования + 1 рефакторинг" в одном PR
- Все тесты должны проходить после каждого шага