1. Вступ
Якщо ви лише починаєте, може здатися логічним: «компілятор же розумний, він усе скаже». Проблема в тому, що компілятор зобовʼязаний бути чесним формалістом: якщо код коректний за правилами мови, він його пропускає. Але коректний код може бути дивним, крихким або просто підозрілим. go vet додає офіційний шар «схоже на помилку», а лінтери розширюють цю ідею: вони знаходять більше шаблонів, які статистично часто виявляються багами або джерелами болю під час ревʼю.
Уявіть, що компілятор — це охоронець на вході до будівлі: «перепустка є? проходьте». 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 виглядатимуть як усвідомлений виняток, а не як стиль життя.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ