Домовленості проєкту¶
gofmt вирішує питання форматування, а go vet ловить певний клас
помилок. Що лишається — це набір звичок, на які погоджується команда —
речі, які жоден інструмент не перевіряє, і саме тому їх варто записати.
Обгортайте кожну помилку контекстом¶
Помилка, що доходить до логу як sql: no rows in result set, не каже
нічого про те, який саме SQL-запит, який id чи яке звернення. Обгортайте
на кожному рівні, що додає інформацію:
Домовленості, що роблять обгорнуті помилки читабельними:
- Малі літери, без крапки в кінці. Помилки конкатенуються;
Failed to load user.всередині іншого повідомлення читається погано. - Без "failed to". Те, що це помилка, вже сказано самим фактом.
loading user 42: connection refusedкраще, ніжfailed to load user 42: failed to connect: connection refused. %w, не%v, якщо тільки ви свідомо не хочете розірвати ланцюжок.%vсплющує помилку в текст, іerrors.Isперестає працювати.- Додавайте щось. Обгортання без нової інформації — це шум; краще передайте помилку вгору незмінною.
Ніколи не відкидайте помилку мовчки. Якщо вона справді не має значення, скажіть це:
Явне _ каже рецензенту, що це було рішення.
ctx першим, завжди¶
Кожна функція, що виконує введення-виведення чи може заблокуватися,
приймає контекст першим параметром, названим ctx:
Послідовність важливіша за окремий випадок. Якщо половина функцій його приймає, інша половина стає тими, які доводиться щоразу вишукувати.
Рівні логування, що дещо означають¶
Рівні корисні, лише якщо їх використовують однаково всюди:
| Рівень | Значення |
|---|---|
Error |
людина має діяти; щось зламане |
Warn |
деградовано, але оброблено — спрацював фолбек, відбулася повторна спроба |
Info |
значуща подія: запущено, зупинено, завдання завершено |
Debug |
деталь для діагностики, вимкнено в продакшні |
Два правила, що рятують більше, ніж ця таблиця. Логуйте помилку один
раз, на тому рівні, що її обробляє — логування й повернення на
кожному рівні дає п'ять записів на один збій. І не логуйте на рівні
Error те, що ви також повертаєте: виклик залогує це сам, і тепер у
вас той самий збій двічі з різним контекстом.
Ніколи не логуйте секрети, токени чи персональні дані. Це включає
%+v на структурі, що випадково містить поле пароля.
Коментуйте чому, а не що¶
// PreferSimpleProtocol уникає кешу підготовлених запитів, який
// пулер з'єднань перед цією базою даних не підтримує.
PreferSimpleProtocol: true,
Код каже що. Коментар заслуговує на своє місце, пояснюючи те, чого код не може: обхідний шлях, неочевидне обмеження, рішення, що виглядає хибним, але таким не є. Коментарі, що переказують рядок нижче, гниють у мить, коли цей рядок зміниться.
Експортовані ідентифікатори отримують doc-коментар, що починається з назви:
// ByID повертає користувача з даним id, або ErrUserNotFound.
func (s *store) ByID(ctx context.Context, id int64) (User, error)
Саме таку форму рендерять go doc і pkg.go.dev.
Обґрунтовуйте сирий SQL¶
Там, де в проєкті є стандартний спосіб дістатися бази даних, а ви з нього виходите, залиште причину:
// Raw: згенерований стовпець — fts_content обчислюється автоматично,
// тож INSERT має явно називати свої стовпці, а не покладатися на ORM.
Цінність не в самому коментарі; вона в тому, що рецензент бачить, чи
цей виняток — один із тих кількох, які команда приймає, чи це просто
скорочення шляху. Та сама звичка стосується будь-якого запобіжного
клапана: директиви //nolint, виклику unsafe, сну в тесті.
Запобіжні клапани без причин множаться.
Іменування¶
- Короткі назви для коротких областей видимості.
i,r,w,db— добре всередині п'ятирядкової функції й погано як ідентифікатори рівня пакета. - Без затинання.
store.New, неstore.NewStore;http.Client, неhttp.HTTPClient. - Інтерфейси описують поведінку.
Reader,UserStore— неIUserStoreчиUserStoreInterface. - Отримувачі — одна чи дві літери, послідовні на кожному методі типу.
- Абревіатури зберігають регістр:
userID,ServeHTTP,parseURL— ніколи неuserIdчиparseUrl.
Приймайте інтерфейси, повертайте структури¶
Приймайте як параметр найвужчий потрібний інтерфейс; повертайте конкретний тип. Виклики зберігають повну інформацію, а ви вільні додавати методи пізніше, нікого не ламаючи.
Наслідок: не створюйте інтерфейс, доки не з'явиться друга реалізація чи тест, якому він потрібен. Інтерфейс з однією реалізацією — це непрямість без вигоди.
Нульові значення мають працювати¶
Тип, чиє нульове значення придатне до використання, прибирає цілий клас конструкторів:
Проєктуйте під це, де можете. Де не можете, зробіть це очевидним — неекспортоване поле, яке конструктор мусить встановити, щоб нульове значення провалювалося гучно, а не поводилося тонко неправильно.
Де це записувати¶
CONTRIBUTING.md, якого ніхто не читає, гірший за його відсутність.
Що працює:
- Робіть механічним, де можливо. Усе, що може перевірити лінтер, має перевіряти лінтер, а не рецензенти.
- Пояснюйте чому. Правило з причиною виживає; правило без причини обговорюють знову кожні пів року.
- Тримайте коротким. Десять правил, яких дотримуються, кращі за п'ятдесят, які лише проглядають.
З досвіду Python:
gofmtзавершує дискусію про форматування, яку завершивblack, тільки раніше й узагалі без конфігурації. Обгортання помилок — це еквівалентraise ... from err, тільки робити це доводиться вручну щоразу — така ціна того, що помилки є значеннями.
Швидка довідка¶
| Домовленість | Форма |
|---|---|
| обгортати помилки | fmt.Errorf("doing thing: %w", err) — малі літери, без "failed to" |
| ігнорувати помилку | _ =, свідомо |
| контекст | ctx context.Context першим, завжди |
| логувати збій | один раз, там, де його обробляють |
| коментарі | чому, а не що; doc-коментарі починаються з назви |
| запобіжні клапани | залишити письмову причину |
| назви | коротка область видимості — коротка назва; без затинання; ID, URL, HTTP |
| інтерфейси | приймати вузькі, повертати конкретні типи |
| нульові значення | робити придатними до використання, де можете |