Линтеры в Go. Как их готовить. Денис Исаев

Предлагаю ознакомиться с расшифровкой доклада Дениса Исаева «Линтеры в Go. Как их готовить.»

В go 50+ линтеров: в чем их профит и как эффективно встроить их в процесс разработки? Доклад будет полезен как тем, кто еще не использует линтеры, так и тем, кто уже применяет их: я раскрою малоизвестные трюки и практики работы с линтерами.


Кому интересно, прошу под кат.

Привет. Меня зовут Деннис Исаев. Мы поговорим о том как готовить линтеры в Go. Доклад будет интересен как новичкам, кто еще не использовал линтеры, так и профессионалам. Я расскажу про некоторые малоизвестные трюки.

n09y_nbwkxomwtpxvpxkjduwv14.png

Немного об о мне. Я автор opensource проекта Golangci-lint. Работал в mail.ru. Сейчас работаю TeamLead в backend в Яндекс.Такси. Мой доклад основан на опыте общения с сотнями пользователей Golangci-lint. О том как они использовали линтер, какие у них были трудности и опыте внедрения Go линтеров в компаниях mail.ru и Яндекс.

71rsvtht1v3xvl8nanocimgz7vc.png

Мы осветим 5 основных вопросов в докладе.

breqygygay_hptqbkx45lu-agpw.png

Я довольно часто вижу что вообще линтеры не используют. Поднимите руки те, кто использует линтер во всех проектах без исключений. Не все.

yuqs44e2b1hl9ykkxlbcmfp72tw.png

Давайте поговорим о том почему их не используют. Чаще всего когда спрашиваю почему вы ребята не используйте, говорят что линтеры нам мешают. Они только замедляют разработку. Ничего хорошего в них нет. Отчасти, это действительно так. Если не знать тонкости по настройке, то они могут действительно мешать. Об этом поговорим чуть позже.

e50u_h2_oioyrkqq3kspoibntza.png

Кроме того и часа свой что линтер находит только мелочовку, что-то стилистическое, какие-то не критичные баги и на самом деле оно того не стоит. Проще ли тратить время.

chfvm7l6u8hqpj2sr5mzjdazfpc.png

Сразу контрпримеры. Баг в Docker найден с помощью go vet. Забытый вызов Cancel функции. Из-за этого фоновая горутина может не завершиться.

kr46johuzdn2edxanefi39d9-30.png

Баг в проекте Etcd. Классный линтер go-critic нашел что аргумент strings.HasPrefix перепутаны местами. Значит не будет работать проверка на небезопасный протокол HTTP.

jnkc34kkfq02wcnkfglixjljy8w.png

В самом Go баг что i элемент сравнивается i-ым, хотя должен сравниваться с j-ым. Тоже найден линтерами.

pucxgnyb1cpcedhh2ezrgkzz25o.png

Три примера в крупных opensource проектах найденные линтерами. Некоторые спросят: Ок и что? Он находит какие-то критичные баги. На один критичный баг находит 100 некритичных или ложных срабатываний. Могу привести свою эмпирическую статистику: у меня обычно где-то 80 процентов всех проблем которые репортят линтеры это: какие-то стилистические issues, например, переменные не используются и так далее, 15 процентов это реальные баги и где-то 5 процентов ложные срабатывания.

t-lefekr_uop6smwkr8hsh-_ndy.png

Теперь поговорим о том зачем нужны линтеры. Самый главный бонус что линтеры экономят время. А время это деньги. Чем раньше вы найдете баги, тем дешевле они стоит для вашей компании. На слайде график примерной стоимости исправления бага зависимости от стадии где он найден. Соответственно с каждой стадии начиная разработки заканчивая production стоимость увеличивается в три раза. Находите баги рано, в IDE идеально и экономьте деньги компании.

djrtvgov0gszrqott6lurea2-ou.png

Часто бывает такое что на CodeReview разработчики репортят какие-то проблемы, которые могли бы найти линтеры. Зачем они это делают непонятно. Во-первых, автору кода нужно дождаться пока он пройдет CodeReview. Во-вторых, самому ревьюеру нужно потратить время чтобы найти какие-то механические проблемы. Он мог доверить это ли линтеру. Я когда такое замечаю всегда форсирую и мы договариваемся в команде что все что можно найти линтер, мы глазами не ищем на review. Мы доверяем все это ли линтерам. Более того, если мы находим какие-то проблемы которые в review часто бывают и линтеров на них нет мы стараемся найти линтеры, которые могли бы их в теории ловить. Чтобы мы не тратили время на review.

opx5znmyz96fphztoniygacwi7c.png

Линтеры позволяют нам как-то гарантировать и иметь предсказуемое качество кода в проекте. Например, в данном случае это неиспользуемые аргументы функции.

7z_os1fsrsmnf-zml0pagozdn78.png

Линтеры позволяют находить критичные баги, максимально рано, экономить время CodeReview. При этом гарантировать какое-то качество кода по проекту.

zgbv_u6n03huui5tv02mmrptx6u.png

В Go более 50 линтеров, но самые такие популярные 4. Это те, которые на слайде. Их использует просто потому что они классные. С остальными обычно не хочется разбираться. Я сейчас хочу на примерах показать какие вообще есть линтеры. Хочу продемонстрировать 25 линтеров на примерах. Сейчас будет наверное самое важное в докладе.

3ojw3cdlryaspdv7pns7wrmrdks.png

Начнем с линтеров проверяющих форматирование. Gofmt это по сути не линтер. Но мы его можем рассматривать как линтер. Он умеет говорить нам не хватает перевода строк, где-то лишние пробелы. В общем это стандарт для проверки и поддержания форматирования кода.

oh43lmztndg3qrcirv5zatznhnk.png

Также у gofmt есть малоизвестная опция -s, которая позволяет упрощать выражения.

v4yevcz6msilkmoy-s23m4ikgie.png

Goimports содержит себе все то же самое что содержит gofmt, но дополнительно он еще умеет переупорядочить импорт, удалять и добавлять нужные импорты.

mexzea3h4oqpj5rz4_28kgnlfjg.png

Unindent это такой замечательный линтер, который умеет понижать уровень вложенности кода. В данном случае, он говорит нам что если объединить два if в один, то у нас понизиться на единичку уровень вложенности.

s8vwhhcz36gcvqmjzoao9zx0sbo.png

Рассмотрим линтеры проверяющие сложность кода. Самый классный из них это gocyclo. Он же самый занудный. Его многие ненавидят. Он проверяет цикломатическую сложность кода и ругается когда это сложность функции превысит какой-то порог. Порог настраивается. Если упрощенно, цикломатическая сложность — это количество if в коде. Здесь он слишком большой и линтер ругается.

40dhrfgpsg_ipvkiva2f7izoats.png

Nakedret это такой линтер, который умеет говорить что вы используете return без значений и при этом вы его используете в слишком длинной функции. По официальным guide такие return не рекомендуются.

-mh4sal0ijvrzufd1voosn0alt8.png

Есть группа линтеров, проверяющий стиль. Например, gochecknoglobals проверяет что вы не используете глобальные переменные. Конечно же их не надо использовать.

44xsgxsltp04g37dp3cy1y73apu.png

Golint ругается на эту же переменную apiUrl. Говорит что url следует использовать большими буквами. Так как эта аббревиатура.

cvl4cdmxlfi1o9hg-od8yaizdku.png

Gochecknoinits убеждается что вы не используете init функции. Init функции по определенным соображениям не следует использовать.

flnoi-e2ymfxhjuouxrblzmax1i.png

Gosimple классный линтер. Часть staticheck или megacheck. Внутри себя содержит огромное количество паттернов по упрощению кода. В данном случае можете заметить что strings.HasPrefix не нужен, так как strings.TrimPrefix уже содержит внутри себя нужные проверки и можно убрать if.

a1ksl-ic4y4luhbsncm4zzjcfqi.png

Goconst проверяет что у вас в коде нет повторяющихся строковых литералов, которые можно было бы вынести в константы. Количество этих повторов настраиваются. В данном случае два.

ebwgp-tcumgwuboffctw-ultuhc.png

Misspell линтер, который проверяет что у вас в коде в комментариях нет опечаток. В данном случае на слайде опечатка слове else в тексте комментария. Можно настраивать диалект английского: американский, британский.

cphkggzyzgrgn8_zcpwapupc_9y.png

Unconvert линтер, который проверяет что вы не делайте лишние конверсии. В данном случае переменная уже типа string. Нет смысла ее конвертировать.

_xmwd846ttkqu3rvjkh1wxnemru.png

Теперь посмотрим линтеры, которые проверяют неиспользуемый код. Во-первых это varcheck. Он проверяет неиспользуемые переменные.

ztttj3tn3b3ihumj_asgyu_ssu4.png

Unused умеет ругаться на неиспользуемые поля структур.

qbwwqdosm6vthcmuna535jnbyoy.png

Deadcode говорит нам, если не используется тип.

ybdjowi8ng8up7uww9amcmfmvri.png

Или не используется функция.

aaicr8arhzyxymspkagbuyfddb4.png

Unparam умеет сообщать когда аргументы функции не используются в самом теле функции.

gnzzccget6iole9xmvqjvf410zm.png

Ineffassign репортит когда изменение переменой не используются дальше в коде. Это бывает либо результат какого-то рефакторинга. Где-то забыли что-то почистить, либо баг. В данном примере count увеличивается. При этом дальше никак не используется. Это очень похоже на баг.

phhnvvyhavzzn7bpaayu3gpprec.png

Есть группа линтеров проверяющих производительность. Например, maligned говорит нам что данную структуру testStruck можно сжать в размере с помощью переупорядочивания полей. Более того, если запускать его как часть golangci-lint, у него есть опция позволяющая распечатать вам сразу нужный порядок полей, чтобы самому не подбирать их.

bgttamoa5j5266shahgap3hihuw.png

Есть такой классный линтер gocritic. Внутри у него множество проверок. Одна из них hugeParam. Она умеет репортить нам о копировании тяжелых структур данных. В данном случае heavyStruct копируется по значению и нам нужно просто передавать ее как указатель.

bw5imecya7pb85b2_6gblmqgl9q.png

Prealloc умеет находить нам места в коде, в которых мы можем заранее преаллоцировать slice. Он это находит так, что ищет где мы делаем константные часовые итерации по slice. И в них дела append. В данном случае, можно сделать на длину slice ss преаллоцирование переменной ret и сэкономить память и ЦПУ.

wo8btytonlzgh3axhc2r2lkntau.png

И наконец линтеры, которые находят баги. Scopelint находит наверное самую часто ошибку новичков в go это захват переменной range цикла по ссылке. В данном случае переменная цикла arg захватывается по ссылке. На следующей итерации там будет уже другое значение.

hpfgi6p2a5rgpxg4wuvo8lmin_y.png

Staticcheck. Раньше именовался megacheck. Теперь он переименовался. Из-за этого есть такая небольшая путаница в комьюнити. Staticcheck умеет находить тонны различных багов. Это совершенно классная штука. Как и go vet. Один из них на слайде — это гонка. Нам нужно конечно же инкрементить sync.WaitGroup до захода в горутину.

ec22b_dn6og0njhppjrfhv7hav0.png

Go vet находит в основном баги. В данном случае переменная i сравнивается так что результат всегда будет true. Поэтому здесь очевидно баг. Вообще всегда стоит использовать go vet.

ew7mimaq8i-hhnnxihqoaidmzpq.png

Gosec расшифровывается как go security. Находит потенциальные проблемы с безопасностью в Go. В данном случае у нас в arg могут поступать пользовательские данные. Поэтому она может проникать в shell команду rm. И тут может быть там shell in action например. Замечу что go security довольно часто выдает false positive. Поэтому я его иногда отключаю.

kj3spdxvgi3wysimti4bvfpl-dw.png

Errchek находит места где мы забыли проверку ошибок. Хорошим, безопасным стилем программирования считается везде всегда проверять все ошибки.

zgdgedtsoo9k-_jb5ozaywumvom.png

Отдельно должен отметить два линтера это staticcheck и go-critic. Потому что внутри себя каждый из них содержит еще по десятки, если не сотни, проверок. Поэтому обязательно посмотрите их попробуйте.

uuat9nlumkoztxvwcc_dvlivacc.png

Сейчас мы рассмотрели 25 линтеров на примерах. И я еще говорил что у нас более 50 линтеров в Go. Какие использовать? Я советую обычно использовать все по максимуму. Включите просто все линтеры какие вы можете. А дальше потратьте час и начинайте их выключать по одному. Выключайте те которые вам кажутся несущественными. Например, он находит вам какие-то оптимизации производительности, которые вам вообще не интересны. Вы потратите час и сформируйте для себя свой список линтеров и с ним можете уже дальше жить.

05yto2j_ituxze5vszdzkln3m4s.png

Полный каталог всех линтеров есть по ссылке на слайде.

hregmsph6oqwrwbremeqjr3hdom.png

Поговорим про то как запускать линтеры. Иногда линтеры запускают с помощью таких вот makefile. Проблема в том что оно работает медленно. Оно все выполняется последовательно.

pa24p8mowqtcdxc09v1bd--k8zm.png

Мы можем сделать выполнение параллельно через xargs -P. Здесь тоже остается проблема. Во-первых, это всего лишь 4 линтера. А уже 10 строчек кода. Что будет если мы 20 линтеров включим. Во-вторых, это распараллеливание ну мягко говоря не самое оптимальное.

4ryyuq9idfuppazujvbfm0-jv84.png

На помощь приходит gometalinter. Gometalinter это такой агрегатор линтеров, которые он может запускать буквально в пару команд. На слайде аналогичная предыдущему слайду команда запуска этих же линтеров. Но их не надо самостоятельно устанавливать и не нужно шаманить с параллельным запуском. Gometalinter уже под капотом все распараллеливает. Но у него есть одна фундаментальная проблема. Он запускает каждый линтер как отдельный процесс. Форкает его. Если добавить к этому знание о том что каждый линтер внутри себя 80 процентов времени тратит на парсинг кода и лишь 20 процентов на сам анализ, то получается что 80 процентов работы мы тратим впустую. И никак не переиспользуем данные. Мы могли бы 1 раз распарсить программу и дальше скормите 50 линтерам.

9kcie0zugz6ndeuz4dkup3qbcxo.png

К счастью, есть golangci-lint, который делает ровно это. Он один раз парсит. Один раз достает типы. Дальше прогоняет на них уже анализаторы. За счет этого оно работает значительно быстрее. Аналогичная команда запуска на слайде.

xgnfr-8sfk9kktxqg7m-w-gtzuk.png

Можно посмотреть график на одном из моих проектов 30 тысяч строк кода. Небольшой проект и всего 4 линтера. Можно заметить там колоссальную разницу по скорости работы в разы, как между последовательным запуском, так и между gometalinter, так и golangci-lint. Если этих линтеров будет не 4, а 20, то разница будет намного больше.

wtodhhewmvbr6ntlhmticbyboqo.png

Важное уточнение про gometalinter. C 7 апреля автор проекта gometalinter объявив его deprecated. Репозиторий заархивировал и всем советуют переходить на golangci-lint, так как он более быстрый, там у него больше плюшек. Например, поддержка go модулей и так далее.

3wo-rltzm-28zyzktklgokya__m.png

И кроме производительности и Go модулей в golangci-lint есть такие плюшки как YAML конфигурация, возможность как-то пропускать предупреждения, исключать их так далее.

qemtj4mww4ghwgevvrldygfsrcs.png

Golangci-lint конфигурируется с помощью файла golangci-lint.yaml. Пример этого файла с описанием всех опций есть по ссылке на слайде. Рассмотрим, например, секцию linters-settings. В этой секции рассмотрим конфигурацию goimports. У него есть такая редкая опция local-prefixes. В ней можно указать путь к текущему проекту. В данном случае для примера github.com/local/repo.

zwlmru9ittbjcnpaqtsm1kjsiso.png

Когда goimports будет видеть локальные импорты и в github.com/local/repo, он будет обращать внимание на то чтобы они были в отдельной секции.

jexznpn95qga4k1tajcxhzlc7tq.png

Чтобы они были в самом конце. Чтобы они были отдельны от всех внешних импортов. Это позволяет просто визуально удобнее отличать внешние от внутренних импортов. Если он заметит что это не так, то он будет ругаться.

jrk_whrcx5nxvjcunwpojfc6blq.png

А если вы еще и используйте опцию golangci-lint run --fix, то golangci-lint еще и зафиксит за вас автоматом и пересортируют импорты.

omhzrz39llatxdcwh903291uyig.png

Поговорим про то какие есть линтеры есть в терминологии golangci-lint. Линтеры делятся на быстрые и медленные. Быстрые называются fast, помечены флажком fast в help. Отличаются они тем что быстрые линтеры требуют довольно ограниченное представление программы, например AST дерево и какую-то информацию о типах. В то время как медные линтеры еще дополнительно требует SSA представления программой и меньше переиспользуют кэш. Медленных линтеров всего шесть. Они помечены на слайде. Есть определенные кейсы, когда имеет смысл запускать только быстрые линтеры.

hybjmnqjc_sx6yupmtu3bmp4yqk.png

Можно заметить разницу в скорости. Она колоссальная в три раза между быстрым запуском и медленным. Собственно golangci-lint run --fast запускается только быстрые линтеры.

mza_cytq5xuunzovfpkpw0l7uhc.png

Про build cache. Есть такая штука как build cache. Это кэш, который строится бинарником Go при компилировании программы при загрузке типов, чтобы следующий раз эта компиляция была быстрее. Этот же кеш переиспользуют линтеры для парсинга программы для построений информации о типах. Можно заметить что если кеш очистить, то первый свежий запуск будет довольно долгим. А последующий будет уже в 3 раза быстрее. Обратите внимание на ваш первый запуск линтеров на вашем проекте. Он всегда будет значительно медленнее.

srt-wffukanjk1wsnf7udixlppc.png

Здесь же можно сделать вывод что есть смысл в CI между запусками CI переиспользовать ваш кэш. Вы ускорите не только линтеры, вы ускорите еще и запуск тестов, просто компиляцию и возможно ещё что-то. Всем советую.

dht640a9ima2euast-lyw2m0ycm.png

Не могу не поговорить про go analysis. Это новый фреймворк, который появился начиная с Go 1.12. Он унифицирует интерфейсы таким образом что линтеры становится легко писать, линтеры легко использовать, запускать. Go vet начинается 1.12 вообще перешел целиком на go analysis. Очевидно что за этим будущее. Что оно сильно изменит всю экосистему Go. Но пока вообще довольно рано говорить об этом. Чтобы что будет дальше? Потому что я видел всего несколько линтеров на go analysis и практически ни один из существующих на него еще не перешел.

pdaiwrm4exla4vob6jude1q5ag4.png

Если сделать краткий вывод по секции как запускать линтеры, то советую всем использовать golangci-lint. Вы будете быстро удобно запускать линтеры. Вам не надо шаманить с другими инструкциями, командами.

np5ert5mpx8duxndaz3z4jyfxaa.png

Поговорим про то как внедрять линтеры в проект. Я думаю там все кто пытались внедрить линтеры сталкивались с такой проблемой. Вот у вас проект на миллион строк кода с историей. Вы убедили TeamLead внедрить линтеры. Запускаете и видите миллион сообщений. Понимаете что вам сидеть безвылазно недели чтобы все это исправить. Что делать? Можно просто сдаться и бросить все это. Или можно что-нибудь придумать.

r620nhcfiheftehfd95wvcubqxg.png

Во-первых, самый простой вариант, можно попробовать исключить какие-то замечания линтеров по тексту по регулярке с помощью конфига golangci-lint.yaml. Если вы видите что там на комментарии ругаются линтеры, а вам на эти комментарии в общем-то все равно, то можно добавить в исключения.

k3e2r06d6zqjr-udzpytsgl3wsa.png

Можно исключить путям. Например, у вас есть директория third-party и там лежит не ваш код. Вам его не нужно проверять. Можно исключить по именам файлов.

7oxd1y9ydrw7izbbgcz44fil67w.png

Если вы не хотите исключать целиком весь файл, вы можете исключить функцию с помощью nolint перед функцией. У nolint можно указать через двоеточие список линтеров, для которых оно действует как исключение. Либо не указывать, тогда будет игнорировать все линтеры.

bau0u73eyak4rfzpawoxsdn38ys.png

Когда вообще использовать nolint? Например, я использую nolint: deepguard, который умеет заблеклистить импорты, т.е. импорты нельзя использовать. Я заблеклистил импорт библиотеки logrus для того чтобы случайно не использовать его вместо моего нужного логера. Но в самом моем логере я использую logrus. Поэтому мне нужно только в одном месте в проекте только в одном файле сделать от импорт. Я помечаю его с помощью nolint.

zulyjsetqrvw7kkuamogbsfrif0.png

Допустим вы все это сделали, exclude добавили, nolint проставили. Видим что все равно осталось тысячи сообщений. Исправлять это несколько дней. Есть классный хак. Рассмотрим на примере. Есть файлик main.go, в котором 5 строчка добавлена давно, а 6 строчка добавлена только сегодня. Что можем сделать?

vueo8a22i6im38vl8ucmrcbqkak.png

Мы можем использовать revgrep. Revgrep позволяет нам указать git ревизию, после которой нужно искать баги. То есть оставлять сообщение линтеров только после заданной ревизии. Если 6 строчка у нас изменена после origin master, то он за зарепорит только ее. А все предыдущие сообщения, 5 строчку он не зарепортит. Это очень классный трюк. Именно с помощью него можно внедрить линтер любой проект за час. Как мы это делаем. Мы берем запускаем там golangci-lint на проекте в миллион строк кода. Он выдает тысячу предупреждений. Мы немножко понастроили, подшаманили. Дальше мы договариваемся с командой что прямо сейчас мы делаем git ревизию или используем hash commit. После которого мы не допускаем ошибки линтеров. Но до которого мы все ошибки линтеров оставляем и пока их не правим или правим медленно. Мы указываем это этот hash commit или tag в revgrep и начинаем запускать CI. Отныне линтер будут репортить нам только ошибки в новом коде. При этом старом коде они никак не будут реагировать на ошибки. и таким образом можно вот реально за час внедрить линтеры в любой проект. Именно так я сделал в mail.ru, когда внедрял линтеры в огромные проекты.

jrgxyhhduiqynrf3zk247jdf7gg.png

Более того revgrep уже внедрен в golangci-lint. Достаточно просто указать опцию --new-from-rev или --new. Всем обязательно советую.

hioxviksgb9hn41ptwcy69ukfvc.png

Здесь есть еще одна тонкость. Допустим мы постепенно со временем все ошибки зафиксили в старом коде и убрали вообще опцию --new. У нас сейчас есть 20 линтеров, мы их запускаем. Новых ошибок нет. В один момент добавляется новый линтер. Мы хотим этот линтер тоже запускать. Но он выдает очень много ошибок. Что делать? Если мы добавим --new-from на все линтеры, то будет не круто. Мы хотим все прошлые линтеры запускать на всем проекте.

htsxdlhg87cpsd58nzt8zyr4pv4.png

Решение простое. Можем запускать golangci-lint дважды. Один раз запускать его на новом коде с новым линтером. Второй раз запускать его целиком со всеми старыми линтерами на всем проекте. Такой трюк сильно помогает внедрять новые линтеры, когда они выходят.

y3ciotx_sk_m0ionhcntvlwap_u.png

Мы поговорили про внедрение линтеров в любой проект. Теперь поговорим про удобство работы. Во-первых, нужно добиться воспроизводимости в CI. Как только вы добавляете линтер в CI, вам нужно чтобы она была стабильна. Никогда не делайте go get. Потому что оно не версионировано. Линтер в любой момент изменился, обновился и у вас все CI build начали фейлиться. Это я видел десятки раз. Всегда используйте конкретные версии. Лучше с помощью wget ее поставьте. Она еще быстрее будет. Кроме того, не рекомендую использовать опцию ---enable-all для линтеров, потому что в один день вы обновляете golangci-lint, например, у вас добавляется 5 новых линтеров и у вас все build начинает фейлиться. Потому что вы эти линтеры случайно включили. Лучше явно прописываете какие линтеры включаете.

9ovd4ldx8hr9_sv2znb0ozmytn4.png

Классная штука pre-commit hook. Кто использует pre-commit hook поднимите руки? Довольно мало. Pre-commit hook это файл в git, который позволяет вам исполнять произвольный код после того как вы захотели закоммитить. Но до того как этот commit завершится успешно. Если pre-commit hook возвращает вам ошибку, to commit не пройдет. Туда обычно удобно встраивать быстрые тесты, статический анализ и так далее. Всем советую встраивать golangci-lint. Можно делать это вручную через shell скрипт. Можно через утилиту pre-commit. Пример того как настроить на слайде. Устанавливается pre-commit с помощью pip — утилиты для установки пакетов Python. pip install pre-commit устанавливает конфиг. Golangci-lint уже поддерживает интеграцию с pre-commit.

kqbrfpzjvqocjfu4unpnm7ezt98.png

Опция --fast. Мы к ней вернулись. Всем советую использовать в IDE именно ее. Вообще в IDE конечно же стоит использовать интеграцию с линтерами. Для того чтобы ваша IDE не подвисала обязательно используйте опцию --fast.

xx0ptfqhvp4oo7_imfbxxvdeu4o.png

Думаю это довольно очевидно. В CI линтеры надо встраивать. Если вы их не встроите, то будет классическая картина: «давай сейчас забьем, сейчас у нас релиз, не до этого». Постепенно у вас будет все больше и больше замечаний. Вы просто перестанете смотреть на линтеры как класс. Поэтому строго в CI. Более того, можно просто установить линтеры в CI, при build fail мы залезаем в build log, ищем почему он там свалился. Где какое замечание, на какой строчке? Это не очень удобно.

q8hklenfqstclvq9e-mmurlm4au.png

Есть способ круче. Можно сделать так чтобы линтеры выступали как человек, как reviewer. Они могут комментировать вам в github.com, gitlab.com строчки кода с ошибкой на вашем Pull request. Линтер могут писать что нашёл проблему. Это невероятно круто. Это позволяет экономить время авторов кода. Потому что не нужно лезть в build log. Плюс человек может прокомментировать, если он не согласен с этим замечанием линтера. В примере на слайде это делается с помощью утилиты reviewdog. Утилита opensource. Она бесплатная. Можно у себя установить.

_-kpuscagzfkeyd8szqkk9x2l7a.png

Кроме reviewdog есть еще такие проекты как GolangCI, Code Climate, Hound. Они позволяют подключить буквально в один клик к своим opensouce или приватным репозиториям вот эти вот линтеры и комментировать inline в Pull Request. Есть еще классная штука SonarQube.

ruax2wvnx5vsvmesb4vuoelaupa.png

Не могу не отметить еще goreportcard. Этот проект позволяет вам построить отчет по по вашему репозиторию. Там ставятся оценки, дается бэйджик, пишется насколько хороший чистый у вас код для десятка линтеров. Тоже советую.

pouphj7ed7qsrt8y9lmfmywq2vm.png

Я хотел бы чтобы вы прямо в понедельник пришли на работу и смогли что-то применить из того что я сказал. Вот кратко что можно применить. Во-первых установите golangci-lint. Включаете там все линтеры. Дальше тратите 1 час. За этот час выключаете все линтеры, которые вам кажутся бредовыми. После этого встраиваете golangci-lint в CI, в IDE и настраивайте pre-commit hook. Cразу после этого можно настроить --new-from-rev и указать что с текущего коммита мы ищем баги. А все предыдущие баги будем исправлять потом отдельно. После этого опционально настраиваете reviewdog, чтобы он еще комментировал вам в ваш github или в gitlab. Вы повысите этим качество проекта просто колоссально. Обрадуете всю команду.

xcuioz2pwqak8ercso3w8z2glc0.png

Всем спасибо за внимание. Мои контакты на слайде.

Вопрос: Подскажите у вас есть уже готовые конфигурационные файлы, которые вы выложили в открытый доступ и которые можно просто скачать и использовать чтобы не разбираться в тонне настроек golangci-lint? То что вы рекомендуете.

Ответ: Хорошая идея. В самом golangci-lint уже есть свой golangci-lint.yaml, который он использует. Можно его использовать как стартовую точку.

Вопрос: На слайде про build cache ссылаешься на кэш для модулей. В кэше указываешь полностью кэш модулей. Можно указать .cache/downloads тогда будет достаточно большое различие: 400 мегабайт против 10. Этого достаточно для того чтобы модули просто экстрактились. Но это только если модуль используется.

Вопрос: Будете также поддерживать и go модули или dep или уходить во что-то в одно?

Ответ: Не нужно одно поддерживает. Сейчас есть библиотечка go packages. Она занимается подгрузкой исходного кода. Она поддерживает и модули и не модули. Она не собирается пока, но убирать поддержку не модулей.

Вопрос: Планируете ли вы делать различные плагины для интеграции с не только с Travis, но и с другими серверами автоматизации?

Ответ: golangci-lint не делает никакой интеграции. Чтобы в CI запустить достаточно просто вызвать golangci-lint --run.

Вопрос: Чтобы парсить какие-нибудь репорты, например в дженкинсе мы сохраняем html-файл.

Ответ: Есть формат вывода junit, csv, json xml. Это все уже можно парсить.

Вопрос: мы использовали раньше gometalinter и он был медленный. Потом мы переключились на линтер кто называется revive. Вы его вообще никак не упоминали. А я с моей стороны я вообще не эксперт в теме. Я не знал про ваш линтер, который вы рассказывали. Вы не могли бы в конце писать допустим плюсы вашего линтера или плюсы revive.

Ответ: revive это переписанный golint. Это всего лишь один из линтеров. Там есть настройки. Он еще добавил несколько линтеров, несколько проверок. Golangci-lint это внутри себя 30–50 линтеров. Это в revive полтора линтера. Revive классен тем что он взял golint и сделал параллельным. Revive работой быстрее чем golint. Но это лишь один линтер. Revive можно сделать частью golangci-lint.

Вопрос: У тебя был слайд в обзоре линтеров про gocritic: hugeParam, который рекомендуют передавать жирные структуры по указателю. Но это же приведет к тому что все эти структуры будут аллоцироваться исключительно в HEAP. И не приведет ли это к большим проблемам, чем преимуществам? Если таких структур, например, передается много.

Ответ: я согласен полностью. Не стоит использовать эти предупреждения и не стоит просто им следовать. Это может быть как преждевременная оптимизация, так может навредить проекту. Я обычно выключаю вообще этот класс линтеров, если у меня нет performance критической задачи. где я с профайлером нахожу слабые места.

Вопрос: Я из Яндекса. Мы пользуемся вашим линтером на большом репозитории. Заметили что он большом репозитории уже начинает не очень быстро работать. Буквально за пару дней написали простую утилиту, которая через go package находит пакеты, которые изменились с момента введения ветки от мастера и пакеты, которые от них зависят. И запускаем линтер только на них. И проверка линтером в разы ускорилась.

Ответ: Может быть вы idssue создадите, приложите скриптик и вполне возможно я все это встрою в golangci-lint.

Вопрос: Планируется ли уровни серьезности для найденных замечаний, чтобы некоторые можно было включать в отчет, но он не фейлить ими CI процесс? Например, через код завершения.

Ответ: Много человек просило и сразу скажу сложность том, что вот эти уровни серьезность их поддерживать всего там 3 или 4 линтера из с 30. Что делать со стальными? Не понятно. Нужно получаться в ручную парсить их замечания, как-то размечать их. Обрабатывать ложные срабатывания. Это вообще большой объем работ. Я не уверен что это когда ли будет сделано. Есть другие способы как достичь той же самой цели.

Вопрос: На хабре есть статьи. А точнее серия статей про C++ линтер. Компания развивает это дело. Они зарабатывают на этом деньги. На самом деле их работа, а точнее эта серия публикаций направлена больше не на разработчиков, а больше на тех кто разработчиками управляет. Эта по сути чистота кода, стилизация. Это наша задача, но и задача руководителей, тимлидов. Планируется ли вот популяризация вот этого направления, линтеров в СМИ, в крупных таких ресурсах, чтобы люди это читали и потом это внедряли своих командах? А не мы снизу стучались.

Ответ: У меня было в план. Спасибо за предложение. У меня было в планах написать всеобъемлющую статью как вот это выступление, но там более подробно и широко в течение года. Возможно, я напишу на русском и на английском.

© Habrahabr.ru