Files
transcriber/docs/adr/ADR-2026-08-11-errcheck-check-blank.md
T
av 2559d09fc8 доменные ошибки сравниваются через errors.As, отказ Close не теряется
- признаки «работы нет» и «задача не найдена» узнаются по смыслу, а не
  приведением типа: обёртка `%w` на пути больше не превращает пустой прогон
  воркера в отказ раз в секунду
- отказ закрытия соединения с распознавателем доходит до вызывающего
  (`errors.Join`) либо до журнала; у `errcheck` включён `check-blank`, иначе
  критерий принимал реализацию, выбрасывающую отказ в пустоту
- заведены первые тесты пакета worker и capability `pipeline`; долг из четырёх
  замечаний линтера закрыт, гейт зелёный целиком
2026-08-11 18:04:25 +03:00

4.4 KiB
Raw Blame History

ADR-2026-08-11. Отказ, который решено не проверять, объявляется поимённо

Решение

У errcheck включена настройка check-blank: присваивание отказа в _ больше не снимает замечание линтера. Место, где отказ решено не проверять, вносится в exclude-functions поимённо.

Дословно из источника:

Правило errcheck сегодня молчит на _ = conn.Close(): настройка check-blank не выставлена, а её умолчание — «пропускать». То есть реализация, выбрасывающая отказ в пустоту, удовлетворяет критерию приёмки «линтер не даёт замечаний errcheck», не удовлетворяя самому критерию — «отказ возвращается либо попадает в журнал».

Почему

Решение принято не ради строгости, а потому что оракул не мог упасть. Задача errors-as-instead-of-typecast закрывала два непроверенных Close, и её критерий приёмки опирался на молчание линтера. Ревью дизайна показало, что этому критерию удовлетворяет и негодная реализация: _ = conn.Close() теряет отказ целиком, а линтер молчит. Критерий, который нельзя уронить, не проверяет ничего — и вместе с ним в CLAUDE.md снималась запись о долге, то есть сигнал исчез бы навсегда и без следа.

Очевидный путь был другим и отвергнут намеренно:

Рассмотрено и отвергнуто — дописать оба типа в exclude-functions .golangci.yml. Соблазн сильный: список исключений там уже есть, и в нём записана ровно эта политика […] Отвергнуто: политика в конфиге относится к закрытию, у которого отказ ничего не значит […] Записав их в исключения, мы бы расширили политику молча, самим фактом добавления строки, и потеряли бы оба сигнала навсегда.

Включение проверено прогоном до правки кода: на тогдашнем коде правило не давало ни одного нового замечания, то есть включалось чисто и отдельного коммита приведения не требовало.

Последствия

  • + критерий «отказ не теряется молча» стал проверяемым машиной: мутация (замена обоих мест на _ = …Close()) роняет линтер — проверено прогоном.
  • + умолчание сместилось в сторону заметности: спрятать отказ по месту больше нельзя, отказ от проверки виден в одном файле списком.
  • осознанное игнорирование подорожало: вместо одного символа _ нужна строка в exclude-functions с полным именем метода. Для одноразового случая это заметная церемония.
  • список исключений будет расти, и каждая его строка — это политика на весь проект, а не на одно место. Разрастание списка — сигнал, что правило выбрано неверно, и повод пересмотреть эту запись.