JavaRush /Курси /Go SELF /Лінтери в Go: staticcheck і golangci-lint

Лінтери в Go: staticcheck і golangci-lint

Go SELF
Рівень 30 , Лекція 3
Відкрита

1. Вступ

Якщо ви лише починаєте, може здатися логічним: «компілятор же розумний, він усе скаже». Проблема в тому, що компілятор зобовʼязаний бути чесним формалістом: якщо код коректний за правилами мови, він його пропускає. Але коректний код може бути дивним, крихким або просто підозрілим. go vet додає офіційний шар «схоже на помилку», а лінтери розширюють цю ідею: вони знаходять більше шаблонів, які статистично часто виявляються багами або джерелами болю під час ревʼю.

Уявіть, що компілятор — це охоронець на вході до будівлі: «перепустка є? проходьте». go vet — уважний охоронець, який ще й перепитує: «а ви точно туди йдете?». А лінтери — це колега, який давно тут працює й каже: «слухай, зайти можна, але ось тут люди зазвичай провалюються в люк — будь обережний». Іноді колега помиляється, але частіше економить вам години роботи.

Щоб не плутатися, зручно тримати в голові такий розподіл обовʼязків:

Інструмент Що робить Чого не робить
gofmt
приводить код до канонічного формату не виправляє логіку
goimports
форматування + імпорти «як треба» не виправляє структуру залежностей
go vet
офіційний статичний аналіз «схоже на помилку» не перевіряє всі підозри й не займається стилістикою
лінтери розширений аналіз: підозри + спрощення + стиль не гарантують, що кожне попередження треба виправляти

2. staticcheck: практичний статичний аналізатор

staticcheck — один із найпопулярніших лінтерів у Go-світі, тому що він доволі практичний: багато його попереджень справді вказують на баги або на код, який можна зробити простішим без втрати ясності. Важливо правильно його сприймати: це не «екзаменатор», який шукає, до чого причепитися, а інструмент, що намагається зменшити кількість прикрих помилок і зробити код читабельнішим.

Водночас staticcheck — не «офіційний стандарт мови» в тому самому сенсі, що gofmt або go vet. Він може бути прискіпливішим: інколи попередження можна ігнорувати, якщо ви розумієте, навіщо написали саме так. Але ключове слово тут — розумієте. Найнебезпечніший сценарій для новачка — не розуміти попередження, але все одно «заглушити» його, щоб стало зелененько.

Корисна ментальна модель: staticcheck часто ловить три категорії речей.

  • «Підозра на баг». Наприклад, ви обчислили значення, присвоїли його змінній, а потім одразу перезаписали її, так і не використавши. Це часто слід недбалого рефакторингу.
  • «Беззмістовний або надлишковий код». Наприклад, ви порівнюєте bool з true, хоча можна написати простіше, а коду — легше читати.
  • «Спрощення та ідіоми». Це не завжди про швидкість, частіше — про читабельність: менше гілок, менше шуму, менше місць, де можна випадково помилитися.

3. Практика на прикладах: невеликий менеджер завдань

Щоб лінтери не здавалися магічною коробкою, подивімося на конкретному прикладі. Уявімо, що ми продовжуємо навчальний консольний застосунок: простий менеджер завдань, який зберігає їх у памʼяті й виводить на екран. Тут нам не потрібна ідеальна архітектура; важливіше навчитися бачити, чому лінтер свариться, і як це допомагає покращувати код.

Порівняння bool з true: код коректний, але шумний

Коли ви лише звикаєте до if, легко писати «в лоб»: if x == true. Це працює, але в Go так зазвичай не пишуть, бо це читається важче, ніж має бути.

package todo

type Task struct {
	Title string
	Done  bool
}

func (t Task) IsDone() bool {
	if t.Done == true {
		return true
	}
	return false
}

Тут усе коректно, але думка «задача виконана?» потонула в зайвих словах. Лінтер, найімовірніше, запропонує спростити.

Більш читабельний варіант:

package todo

type Task struct {
	Title string
	Done  bool
}

func (t Task) IsDone() bool {
	return t.Done
}

Чому це важливо? Бо за тиждень ви відкриєте код і швидше зрозумієте зміст. А ще: що менше рядків, то менше місць, де можна випадково зробити дурницю.

Марне присвоювання: класика після правок

Дуже поширена ситуація: ви щось порахували, потім передумали й «тимчасово» поставили інше значення, а старе забули видалити. Код компілюється, тестів може не бути, але логіка вже не та.

package todo

func nextID(tasksCount int) int {
	id := tasksCount + 1
	id = 1 // випадково залишили "тимчасовий" рядок
	return id
}

Це виглядає майже невинно, але фактично tasksCount більше не впливає на результат. Якщо функція має повертати наступний ID, це вже поведінковий баг. staticcheck зазвичай дуже любить такі місця й підсвічує їх, бо саме там часто ховається проблема.

Виправлення залежить від того, що саме ви мали на увазі. Якщо справді потрібне tasksCount + 1, то просто приберіть зайве:

package todo

func nextID(tasksCount int) int {
	id := tasksCount + 1
	return id
}

Текст помилки: важлива звичка для екосистеми

Самі по собі повідомлення про помилки — це частина UX для розробника. У Go прийнято писати текст помилки в нижньому регістрі й не починати його з Error:. Багато лінтерів підсвітять варіант із великої літери, бо потім такі помилки погано склеюються в ланцюжки й негарно виглядають у логах.

Трохи невдалий варіант:

package todo

import "errors"

func ValidateTitle(title string) error {
	if title == "" {
		return errors.New("Title is empty")
	}
	return nil
}

Краще так:

package todo

import "errors"

func ValidateTitle(title string) error {
	if title == "" {
		return errors.New("title is empty")
	}
	return nil
}

Це дрібниця, але у великому проєкті такі дрібниці економлять нерви. У реальному застосунку повідомлення можна зробити ще людянішими, але зараз нам важливо вловити стиль.

Надлишкова обгортка: коли код можна зробити прямішим

Новачки часто пишуть крок за кроком, і це нормально: так легше думати. Але інколи кроків стає забагато, і лінтер делікатно натякає: «здається, можна простіше».

package todo

func Add(a, b int) int {
	sum := a + b
	return sum
}

Це не помилка. Але в багатьох проєктах такий стиль спрощують, бо він додає шум. Пряміший варіант:

package todo

func Add(a, b int) int {
	return a + b
}

Тут важлива заувага: якщо змінна sum потрібна, щоб пояснити зміст, наприклад у навчальному коді, то інколи залишити її — нормально. Лінтер не забороняє «зайву» змінну. Він підказує: «це можна прибрати».

4. golangci-lint: запуск багатьох лінтерів

Коли ви вперше чуєте про golangci-lint, це звучить як щось зі світу сільгосптехніки: «вийшов у поле — зібрав урожай попереджень». По суті так і є: це агрегатор, який уміє запускати багато різних лінтерів за один прогін і збирати результати в єдиний формат.

Чому це зручно? Бо без агрегатора починається зоопарк: один лінтер запускається так, інший — інакше, третій видає повідомлення у своєму стилі. А ще хочеться, щоб уся команда бачила однакові результати.

Водночас golangci-lint небезпечний для новачка саме своєю зручністю: дуже легко увімкнути все й отримати сотні повідомлень, з яких половина — про стиль, чверть — про субʼєктивні вподобання, а лише невелика частина — про реальні проблеми. У цей момент мозок каже: «та ну його», і лінтери вимикаються назавжди. Тому важливо вмикати їх дозовано, щоб сигнали залишалися корисними.

Гарна новина: golangci-lint якраз і створений так, щоб ви могли вибрати набір лінтерів, налаштувати винятки та жити спокійно, а не в режимі вічної пожежі.

5. Як не потонути в попередженнях

Майже всі проблеми з лінтерами починаються не через самі лінтери, а через неправильну тактику. Людина бачить 300 попереджень і намагається героїчно виправити все одразу — а потім ненавидить і лінтер, і код, і людство.

Робоча стратегія починається з простого принципу: попередження мають різну «силу». Деякі схожі на баг і майже завжди потребують правки. Деякі — корисна порада, але не обовʼязково мають бути виконані за будь-яку ціну. Зручно — навіть якщо ви не памʼятаєте конкретні коди перевірок — ділити сигнали за змістом.

«Сила» сигналу Як сприймати Приклад за змістом
Висока «зупинись, перевір логіку» беззмістовне присвоювання, мертвий код, підозріла перевірка
Середня «найімовірніше варто спростити» зайві розгалуження, очевидні спрощення
Низька «порада щодо стилю» косметика, формат повідомлень, спірні вподобання

Далі вмикається правило здорового глузду: якщо попередження високої сили — ми майже завжди виправляємо. Якщо середньої — виправляємо, якщо це справді покращує читабельність і не ламає задум. Якщо низької — обираємо: або приймаємо правило як стандарт проєкту, або послаблюємо його, щоб не створювати зайвого шуму.

Ще одна важлива звичка: не замовчувати попередження, доки не зрозумієте, про що вони. Якщо ви не можете вголос пояснити, що означає попередження і чому ви його ігноруєте, то, найімовірніше, ви ігноруєте потенційний баг.

Мінімальне налаштування golangci-lint

Налаштування лінтера — тонка річ: хочеться зробити все ідеально, а в результаті його ніхто не запускає. Тому новачкам краще починати з короткої конфігурації: увімкнути кілька справді корисних лінтерів і не влаштовувати конкурс на найсуворіший стиль.

Приклад мінімальної конфігурації .golangci.yml, яка зазвичай дає добрий баланс:

linters:
  enable:
    - staticcheck
    - govet
    - errcheck

run:
  timeout: 2m

Це не єдиний правильний варіант. Це радше ідея: ви вмикаєте невеликий набір лінтерів, які частіше ловлять реальні проблеми. А далі вже дивитеся за досвідом: занадто шумно — зменшуєте, мало корисних знахідок — додаєте точково.

Якщо якийсь лінтер постійно конфліктує з вашим стилем і заважає читати код, його краще вимкнути, ніж «перемагати» його в кожному рядку. Мета — читабельний і коректний код, а не «нульова кількість попереджень за будь-яку ціну».

Коли допустимо ігнорувати попередження

Іноді попередження потрібно проігнорувати. Не тому, що «лінь», а тому, що в цьому місці ви свідомо обрали такий код. Наприклад, ви залишили докладніший варіант заради навчальної ясності або вам справді потрібна нестандартна поведінка.

Але ігнорування має бути усвідомленим і локальним. В екосистемі Go поширений підхід: якщо ви вимикаєте попередження, ви робите це максимально точково й бажано з коротким поясненням.

Часто це виглядає приблизно так (приклад не привʼязаний до конкретного лінтера, важлива ідея):

package todo

import "fmt"

func debugTask(title string) {
	//nolint:forbidigo // у навчальному прикладі свідомо друкуємо налагоджувальне повідомлення
	fmt.Println("налагодження:", title) // налагодження: купити молоко
}

Сенс такий: ви не вимикаєте лінтер назавжди, а кажете «ось тут я розумію, що роблю». Якщо //nolint розповзається по проєкту, мов пліснява, це сигнал, що правила підібрані невдало або що код час переписати простіше.

6. Типові помилки під час роботи з лінтерами

Помилка №1: увімкнути всі лінтери одразу й отримати «снігову лавину».
Коли попереджень сотні, вони перестають бути сигналами й перетворюються на фон. Новачок швидко звикає ігнорувати все підряд, а це зводить нанівець сенс інструмента. Набагато ефективніше почати з малого набору й розширювати його лише тоді, коли ви справді розумієте, навіщо вам нове правило.

Помилка №2: виправляти попередження, не розуміючи його змісту.
Це небезпечніше, ніж здається. Можна «поправити» код так, що лінтер замовкне, але логіка стане гіршою або зʼявиться прихований баг. Правильна реакція на попередження — спочатку зрозуміти, що саме підозріло, і лише потім змінювати код. Якщо розібратися не вдається, краще зупинитися й пройтися прикладом вручну.

Помилка №3: робити код менш читабельним заради «зеленого звіту».
Іноді лінтер пропонує спрощення, але у вашому контексті воно робить код менш зрозумілим, особливо в навчальних прикладах. Це нормально. Лінтер — радник, а не закон. Якщо правка погіршує ясність, краще залишити як є або послабити правило, ніж перетворювати код на «перемогу над лінтером» ціною читача.

Помилка №4: сприймати лінтер як заміну тестів або як істину в останній інстанції.
Лінтери — це статичний аналіз: він дуже сильний у пошуку деяких класів проблем, але не перевіряє поведінку програми в сценаріях. А ще лінтери можуть помилятися або не знати ваших бізнес-правил. Тому правильна позиція спокійна: лінтери доповнюють компілятор і go vet, але не замінюють ні мислення, ні перевірки поведінки.

Помилка №5: замовчувати попередження масово, замість того щоб налаштувати правила.
Якщо проєкт увесь у //nolint, це означає, що ви боретеся з інструментом, а не використовуєте його. Зазвичай краще зробити навпаки: налаштувати список лінтерів і правил так, щоб переважна більшість попереджень були справді значущими. Тоді рідкісні //nolint виглядатимуть як усвідомлений виняток, а не як стиль життя.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ