summaryrefslogtreecommitdiff
path: root/content/pages/gostyleguide/google/decisions.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/pages/gostyleguide/google/decisions.md')
-rw-r--r--content/pages/gostyleguide/google/decisions.md4057
1 files changed, 4057 insertions, 0 deletions
diff --git a/content/pages/gostyleguide/google/decisions.md b/content/pages/gostyleguide/google/decisions.md
new file mode 100644
index 0000000..acb17b7
--- /dev/null
+++ b/content/pages/gostyleguide/google/decisions.md
@@ -0,0 +1,4057 @@
+---
+order: 2
+title: Google Go Style Guide — Решения
+---
+
+# Решения по стилю Go
+
+Оригинал: https://google.github.io/styleguide/go/decisions
+
+[Обзор](https://neonxp.ru/pages/gostyleguide/google/) | [Руководство](https://neonxp.ru/pages/gostyleguide/google/guide) | [Решения](https://neonxp.ru/pages/gostyleguide/google/decisions) |
+[Лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices)
+
+
+**Примечание:** Это часть серии документов, описывающих [Стиль Go](https://neonxp.ru/pages/gostyleguide/google/) в
+Google. Этот документ является **[нормативным](https://neonxp.ru/pages/gostyleguide/google/#normative), но не
+[каноническим](https://neonxp.ru/pages/gostyleguide/google/#canonical)** и подчиняется [основному руководству по
+стилю](https://neonxp.ru/pages/gostyleguide/google/guide/). Подробнее см. [в обзоре](https://neonxp.ru/pages/gostyleguide/google/#about).
+
+<a id="about"></a>
+
+## Об этом документе
+
+В этом документе содержатся решения по стилю, призванные унифицировать и дать
+стандартные рекомендации, пояснения и примеры для советов, которые дают
+наставники по читаемости Go.
+
+Этот документ **не является исчерпывающим** и будет пополняться со временем. В
+случаях, когда [основное руководство по стилю](https://neonxp.ru/pages/gostyleguide/google/guide/) противоречит приведенным
+здесь рекомендациям, **руководство по стилю имеет приоритет**, и этот документ
+должен быть обновлен соответственно.
+
+Полный набор документов по стилю Go см. в
+[Обзоре](https://google.github.io/styleguide/go#about).
+
+Следующие разделы были перемещены из "Решений по стилю" в другие части
+руководства:
+
+* **MixedCaps**: см. [guide#mixed-caps](https://neonxp.ru/pages/gostyleguide/google/guide/#mixed-caps) <a
+ id="mixed-caps"></a>
+
+* **Форматирование**: см. [guide#formatting](https://neonxp.ru/pages/gostyleguide/google/guide/#formatting) <a
+ id="formatting"></a>
+
+* **Длина строки**: см. [guide#line-length](https://neonxp.ru/pages/gostyleguide/google/guide/#line-length) <a
+ id="line-length"></a>
+
+<a id="naming"></a>
+
+## Именование
+
+Общие рекомендации по именованию см. в разделе об именовании в [основном
+руководстве по стилю](https://neonxp.ru/pages/gostyleguide/google/guide/#naming). Следующие разделы дают дальнейшие
+разъяснения по конкретным областям именования.
+
+<a id="underscores"></a>
+
+### Подчеркивания
+
+Имена в Go, как правило, не должны содержать подчеркиваний. Существует три
+исключения из этого принципа:
+
+1. Имена пакетов, которые импортируются только сгенерированным кодом, могут
+ содержать подчеркивания. Подробнее о том, как выбирать имена многословных
+ пакетов, см. в разделе [имена пакетов](#package-names).
+1. Имена тестовых (`Test`), бенчмарк (`Benchmark`) и примеров (`Example`)
+ функций в файлах `*_test.go` могут содержать подчеркивания.
+1. Низкоуровневые библиотеки, взаимодействующие с операционной системой или
+ cgo, могут повторно использовать идентификаторы, как это сделано в
+ [`syscall`]. Ожидается, что это будет очень редко встречаться в большинстве
+ кодовых баз.
+
+**Примечание:** Имена файлов исходного кода не являются идентификаторами Go и не
+должны следовать этим соглашениям. Они могут содержать подчеркивания.
+
+[`syscall`]: https://pkg.go.dev/syscall#pkg-constants
+
+<a id="package-names"></a>
+
+### Имена пакетов
+
+<a id="TOC-PackageNames"></a>
+
+В Go имена пакетов должны быть краткими и использовать только строчные буквы и
+цифры (например, [`k8s`], [`oauth2`]). Многословные имена пакетов должны
+оставаться целыми и в нижнем регистре (например, [`tabwriter`] вместо
+`tabWriter`, `TabWriter` или `tab_writer`).
+
+Избегайте выбора имен пакетов, которые могут быть [затенены] часто используемыми
+локальными именами переменных. Например, `usercount` — лучшее имя пакета, чем
+`count`, так как `count` — часто используемое имя переменной.
+
+Имена пакетов Go не должны содержать подчеркиваний. Если вам нужно импортировать
+пакет, который содержит их в своем имени (обычно из сгенерированного или
+стороннего кода), его необходимо переименовать при импорте в имя, подходящее для
+использования в коде Go.
+
+Исключением является то, что имена пакетов, которые импортируются только
+сгенерированным кодом, могут содержать подчеркивания. Конкретные примеры
+включают:
+
+* Использование суффикса `_test` для модульных тестов, проверяющих только
+ экспортированный API пакета (пакет `testing` называет это ["черным
+ ящиком"](https://pkg.go.dev/testing)). Например, пакет `linkedlist` должен
+ определять свои модульные тесты "черного ящика" в пакете с именем
+ `linkedlist_test` (не `linked_list_test`)
+
+* Использование подчеркиваний и суффикса `_test` для пакетов, содержащих
+ функциональные или интеграционные тесты. Например, интеграционный тест
+ сервиса связного списка может называться `linked_list_service_test`
+
+* Использование суффикса `_test` для [примеров документации на уровне
+ пакета](https://go.dev/blog/examples)
+
+[`tabwriter`]: https://pkg.go.dev/text/tabwriter
+[`k8s`]: https://pkg.go.dev/k8s.io/client-go/kubernetes
+[`oauth2`]: https://pkg.go.dev/golang.org/x/oauth2
+[shadowed]: best-practices#shadowing
+
+Избегайте неинформативных имен пакетов, таких как `util`, `utility`, `common`,
+`helper`, `model`, `testhelper` и т.д., которые могут побуждать пользователей
+пакета [переименовывать его при импорте](#import-renaming). См.:
+
+* [Рекомендации по так называемым "служебным
+ пакетам"](https://neonxp.ru/pages/gostyleguide/google/best-practices/#util-packages)
+* [Go Tip #97: Что в
+ имени](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #108: Сила хорошего имени
+ пакета](https://google.github.io/styleguide/go/index.html#gotip)
+
+Когда импортированный пакет переименовывается (например, `import foopb
+"path/to/foo_go_proto"`), локальное имя пакета должно соответствовать правилам
+выше, так как локальное имя определяет, как на символы в пакете ссылаются в
+файле. Если данный импорт переименован в нескольких файлах, особенно в одном и
+том же или соседних пакетах, по возможности следует использовать одно и то же
+локальное имя для согласованности.
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+См. также: [Пост в блоге Go об именах
+пакетов](https://go.dev/blog/package-names).
+
+<a id="receiver-names"></a>
+
+### Имена получателей (Receiver)
+
+<a id="TOC-ReceiverNames"></a>
+
+Имена [получателей] (receiver) должны быть:
+
+* Краткими (обычно одна или две буквы)
+* Сокращениями для самого типа
+* Применяться последовательно для каждого получателя этого типа
+
+Длинное имя | Лучшее имя
+----------------------------- | -------------------------
+`func (tray Tray)` | `func (t Tray)`
+`func (info *ResearchInfo)` | `func (ri *ResearchInfo)`
+`func (this *ReportWriter)` | `func (w *ReportWriter)`
+`func (self *Scanner)` | `func (s *Scanner)`
+
+[получателей]: https://golang.org/ref/spec#Method_declarations
+
+<a id="constant-names"></a>
+
+### Имена констант
+
+Имена констант должны использовать [MixedCaps], как и все остальные имена в Go.
+([Экспортируемые] константы начинаются с заглавной буквы, а неэкспортируемые —
+со строчной.) Это применимо, даже если это нарушает соглашения в других языках.
+Имена констант не должны быть производными от их значений и должны объяснять,
+что означает это значение.
+
+```go
+// Хорошо:
+const MaxPacketSize = 512
+
+const (
+ ExecuteBit = 1 << iota
+ WriteBit
+ ReadBit
+)
+```
+
+[MixedCaps]: guide#mixed-caps
+[Экспортируемые]: https://tour.golang.org/basics/3
+
+Не используйте имена констант не в стиле MixedCaps или константы с префиксом
+`K`.
+
+```go
+// Плохо:
+const MAX_PACKET_SIZE = 512
+const kMaxBufferSize = 1024
+const KMaxUsersPergroup = 500
+```
+
+Называйте константы в соответствии с их ролью, а не значениями. Если у константы
+нет роли, кроме ее значения, то нет необходимости определять ее как константу.
+
+```go
+// Плохо:
+const Twelve = 12
+
+const (
+ UserNameColumn = "username"
+ GroupColumn = "group"
+)
+```
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+<a id="initialisms"></a>
+
+### Аббревиатуры и акронимы (Initialisms)
+
+<a id="TOC-Initialisms"></a>
+
+Слова в именах, которые являются аббревиатурами или акронимами (например, `URL`
+и `NATO`), должны иметь одинаковый регистр. `URL` должен появляться как `URL`
+или `url` (как в `urlPony` или `URLPony`), но никогда как `Url`. Как общее
+правило, идентификаторы (например, `ID` и `DB`) также должны быть написаны с
+заглавной буквы, аналогично их использованию в английской прозе.
+
+* В именах с несколькими аббревиатурами (например, `XMLAPI`, потому что оно
+ содержит `XML` и `API`) каждая буква в данной аббревиатуре должна иметь один
+ регистр, но каждая аббревиатура в имени не обязана иметь одинаковый регистр.
+* В именах с аббревиатурой, содержащей строчную букву (например, `DDoS`,
+ `iOS`, `gRPC`), аббревиатура должна отображаться, как в стандартной прозе,
+ если только вам не нужно изменить первую букву ради [экспортируемости
+ (exportedness)]. В этих случаях вся аббревиатура должна быть в одном
+ регистре (например, `ddos`, `IOS`, `GRPC`).
+
+[экспортируемости (exportedness)]:
+ https://golang.org/ref/spec#Exported_identifiers
+
+<!-- Keep this table narrow. If it must grow wider, replace with a list. -->
+
+Использование в английском | Область видимости | Правильно | Неправильно
+-------------------------- | ----------------- | --------- | --------------------------------------
+XML API | Экспортировано | `XMLAPI` | `XmlApi`, `XMLApi`, `XmlAPI`, `XMLapi`
+XML API | Не экспортировано | `xmlAPI` | `xmlapi`, `xmlApi`
+iOS | Экспортировано | `IOS` | `Ios`, `IoS`
+iOS | Не экспортировано | `iOS` | `ios`
+gRPC | Экспортировано | `GRPC` | `Grpc`
+gRPC | Не экспортировано | `gRPC` | `grpc`
+DDoS | Экспортировано | `DDoS` | `DDOS`, `Ddos`
+DDoS | Не экспортировано | `ddos` | `dDoS`, `dDOS`
+ID | Экспортировано | `ID` | `Id`
+ID | Не экспортировано | `id` | `iD`
+DB | Экспортировано | `DB` | `Db`
+DB | Не экспортировано | `db` | `dB`
+Txn | Экспортировано | `Txn` | `TXN`
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+<a id="getters"></a>
+
+### Геттеры (Getters)
+
+<a id="TOC-Getters"></a>
+
+Имена функций и методов не должны использовать префикс `Get` или `get`, если
+только базовое понятие не использует слово "get" (например, HTTP GET).
+Предпочитайте начинать имя с существительного напрямую, например, используйте
+`Counts` вместо `GetCounts`.
+
+Если функция включает выполнение сложных вычислений или удаленного вызова,
+вместо `Get` можно использовать другое слово, например `Compute` или `Fetch`,
+чтобы читателю было ясно, что вызов функции может занять время и может
+блокироваться или завершиться неудачей.
+
+<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
+
+<a id="variable-names"></a>
+
+### Имена переменных
+
+<a id="TOC-VariableNames"></a>
+
+Общее эмпирическое правило заключается в том, что длина имени должна быть
+пропорциональна размеру его области видимости и обратно пропорциональна
+количеству раз, которое оно используется в этой области. Переменная, созданная
+на уровне файла, может потребовать несколько слов, тогда как переменная в
+области видимости одного внутреннего блока может быть одним словом или даже
+всего одним-двумя символами, чтобы сохранить код понятным и избежать лишней
+информации.
+
+Вот приблизительный базовый уровень. Эти численные рекомендации не являются
+строгими правилами. Применяйте суждение, основанное на контексте, [ясности] и
+[лаконичности].
+
+* Малая область видимости — это область, в которой выполняется одна или две
+ небольшие операции, скажем, 1-7 строк.
+* Средняя область видимости — это несколько небольших или одна большая
+ операция, скажем, 8-15 строк.
+* Большая область видимости — это одна или несколько больших операций, скажем,
+ 15-25 строк.
+* Очень большая область видимости — это все, что занимает больше страницы
+ (скажем, более 25 строк).
+
+[ясности]: guide#clarity
+[лаконичности]: guide#concision
+
+Имя, которое может быть совершенно понятным (например, `c` для счетчика) в
+маленькой области видимости, может оказаться недостаточным в большей области и
+потребует уточнения, чтобы напомнить читателю о его назначении дальше по коду.
+Область видимости, в которой много переменных или переменных, представляющих
+похожие значения или понятия, может потребовать более длинных имен переменных,
+чем предполагает область видимости.
+
+Специфичность понятия также может помочь сохранить имя переменной кратким.
+Например, если используется только одна база данных, короткое имя переменной
+вроде `db`, которое обычно зарезервировано для очень малых областей видимости,
+может оставаться совершенно понятным даже при очень большой области видимости. В
+этом случае одно слово `database`, вероятно, приемлемо в зависимости от размера
+области видимости, но не обязательно, поскольку `db` — очень распространенное
+сокращение для этого слова с малым количеством альтернативных интерпретаций.
+
+Имя локальной переменной должно отражать то, что она содержит, и как она
+используется в текущем контексте, а не откуда взялось значение. Например, часто
+бывает, что лучшее локальное имя переменной не совпадает с именем поля структуры
+или поля protobuf.
+
+В общем:
+
+* Однобуквенные имена, такие как `count` или `options`, — хорошая отправная
+ точка.
+* Дополнительные слова могут быть добавлены для различения похожих имен,
+ например `userCount` и `projectCount`.
+* Не просто выбрасывайте буквы, чтобы сэкономить на печати. Например,
+ `Sandbox` предпочтительнее, чем `Sbx`, особенно для экспортируемых имен.
+* Опускайте [типы и слова, похожие на типы] из большинства имен переменных.
+ * Для числа `userCount` — лучшее имя, чем `numUsers` или `usersInt`.
+ * Для среза `users` — лучшее имя, чем `userSlice`.
+ * Допустимо включать квалификатор, похожий на тип, если в области
+ видимости есть две версии значения, например, у вас может быть ввод,
+ сохраненный в `ageString`, и `age` для распарсенного значения.
+* Опускайте слова, которые ясны из [окружающего контекста]. Например, в
+ реализации метода `UserCount` локальная переменная с именем `userCount`,
+ вероятно, избыточна; `count`, `users` или даже `c` так же читаемы.
+
+[типы и слова, похожие на типы]: #repetitive-with-type
+[окружающего контекста]: #repetitive-in-context
+
+<a id="v"></a>
+
+#### Однобуквенные имена переменных
+
+Однобуквенные имена переменных могут быть полезным инструментом для минимизации
+[повторов](#repetition), но также могут сделать код излишне непрозрачным.
+Ограничьте их использование случаями, когда полное слово очевидно и где было бы
+излишне повторять его вместо однобуквенной переменной.
+
+В общем:
+
+* Для [переменной-получателя метода] предпочтительно одно- или двухбуквенное
+ имя.
+* Использование знакомых имен переменных для распространенных типов часто
+ полезно:
+ * `r` для `io.Reader` или `*http.Request`
+ * `w` для `io.Writer` или `http.ResponseWriter`
+* Однобуквенные идентификаторы допустимы в качестве целочисленных переменных
+ цикла, особенно для индексов (например, `i`) и координат (например, `x` и
+ `y`).
+* Сокращения могут быть допустимыми идентификаторами цикла, если область
+ видимости короткая, например `for _, n := range nodes { ... }`.
+
+[переменной-получателя метода]: #receiver-names
+
+<a id="repetition"></a>
+
+### Повторы
+
+<!--
+Примечание для будущих редакторов:
+
+Не используйте термин "stutter" (заикание) для обозначения случаев, когда имя повторяется.
+-->
+
+Исходный код Go должен избегать ненужных повторов. Один из распространенных
+источников этого — повторяющиеся имена, которые часто включают ненужные слова
+или повторяют свой контекст или тип. Сам код также может быть излишне
+повторяющимся, если один и тот же или похожий сегмент кода появляется несколько
+раз в непосредственной близости.
+
+Повторяющееся именование может принимать многие формы, включая:
+
+<a id="repetitive-with-package"></a>
+
+#### Имя пакета vs. экспортируемый символ
+
+При именовании экспортируемых символов имя пакета всегда видно за пределами
+вашего пакета, поэтому избыточную информацию между ними следует сократить или
+устранить. Если пакет экспортирует только один тип, и он назван в честь самого
+пакета, каноническое имя для конструктора — `New`, если он требуется.
+
+> **Примеры:** Повторяющееся имя -> Лучшее имя
+>
+> * `widget.NewWidget` -> `widget.New`
+> * `widget.NewWidgetWithName` -> `widget.NewWithName`
+> * `db.LoadFromDatabase` -> `db.Load`
+> * `goatteleportutil.CountGoatsTeleported` -> `gtutil.CountGoatsTeleported`
+> или `goatteleport.Count`
+> * `myteampb.MyTeamMethodRequest` -> `mtpb.MyTeamMethodRequest` или
+> `myteampb.MethodRequest`
+
+<a id="repetitive-with-type"></a>
+
+#### Имя переменной vs. тип
+
+Компилятор всегда знает тип переменной, и в большинстве случаев читателю также
+понятен тип переменной по тому, как она используется. Уточнять тип переменной
+необходимо только если ее значение появляется дважды в одной и той же области
+видимости.
+
+Повторяющееся имя | Лучшее имя
+------------------------------- | ----------------------
+`var numUsers int` | `var users int`
+`var nameString string` | `var name string`
+`var primaryProject *Project` | `var primary *Project`
+
+Если значение появляется в нескольких формах, это можно уточнить либо с помощью
+дополнительного слова, например `raw` и `parsed`, либо с помощью базового
+представления:
+
+```go
+// Хорошо:
+limitRaw := r.FormValue("limit")
+limit, err := strconv.Atoi(limitRaw)
+```
+
+```go
+// Хорошо:
+limitStr := r.FormValue("limit")
+limit, err := strconv.Atoi(limitStr)
+```
+
+<a id="repetitive-in-context"></a>
+
+#### Внешний контекст vs. локальные имена
+
+Имена, включающие информацию из окружающего их контекста, часто создают лишний
+шум без пользы. Имя пакета, имя метода, имя типа, имя функции, путь импорта и
+даже имя файла могут предоставить контекст, который автоматически квалифицирует
+все имена внутри.
+
+```go
+// Плохо:
+// В пакете "ads/targeting/revenue/reporting"
+type AdsTargetingRevenueReport struct{}
+
+func (p *Project) ProjectName() string
+```
+
+```go
+// Хорошо:
+// В пакете "ads/targeting/revenue/reporting"
+type Report struct{}
+
+func (p *Project) Name() string
+```
+
+```go
+// Плохо:
+// В пакете "sqldb"
+type DBConnection struct{}
+```
+
+```go
+// Хорошо:
+// В пакете "sqldb"
+type Connection struct{}
+```
+
+```go
+// Плохо:
+// В пакете "ads/targeting"
+func Process(in *pb.FooProto) *Report {
+ adsTargetingID := in.GetAdsTargetingID()
+}
+```
+
+```go
+// Хорошо:
+// В пакете "ads/targeting"
+func Process(in *pb.FooProto) *Report {
+ id := in.GetAdsTargetingID()
+}
+```
+
+Повторение, как правило, следует оценивать в контексте использования символа, а
+не изолированно. Например, следующий код содержит множество имен, которые могут
+быть хороши в некоторых обстоятельствах, но избыточны в контексте:
+
+```go
+// Плохо:
+func (db *DB) UserCount() (userCount int, err error) {
+ var userCountInt64 int64
+ if dbLoadError := db.LoadFromDatabase("count(distinct users)", &userCountInt64); dbLoadError != nil {
+ return 0, fmt.Errorf("failed to load user count: %s", dbLoadError)
+ }
+ userCount = int(userCountInt64)
+ return userCount, nil
+}
+```
+
+Вместо этого информацию об именах, которые ясны из контекста или использования,
+часто можно опустить:
+
+```go
+// Хорошо:
+func (db *DB) UserCount() (int, error) {
+ var count int64
+ if err := db.Load("count(distinct users)", &count); err != nil {
+ return 0, fmt.Errorf("failed to load user count: %s", err)
+ }
+ return int(count), nil
+}
+```
+
+<a id="commentary"></a>
+
+## Комментарии
+
+Соглашения, касающиеся комментариев (что комментировать, какой стиль
+использовать, как предоставлять исполняемые примеры и т.д.), предназначены для
+поддержки удобства чтения документации публичного API. Подробнее см. [Effective
+Go](http://golang.org/doc/effective_go.html#commentary).
+
+Раздел о [соглашениях по документации] в документе о лучших практиках
+рассматривает это подробнее.
+
+**Лучшая практика:** Используйте [предпросмотр документации (doc preview)] во
+время разработки и проверки кода, чтобы увидеть, является ли документация и
+исполняемые примеры полезными и представлены ли они так, как вы ожидаете.
+
+**Совет:** Godoc использует очень мало специального форматирования; списки и
+фрагменты кода обычно должны иметь отступ, чтобы избежать переноса строк. Кроме
+отступа, декорации, как правило, следует избегать.
+
+[предпросмотр документации (doc preview)]: best-practices#documentation-preview
+[соглашениям по документации]: best-practices#documentation-conventions
+
+<a id="comment-line-length"></a>
+
+### Длина строк комментария
+
+Убедитесь, что комментарии читаемы из исходного кода даже на узких экранах.
+
+Когда комментарий становится слишком длинным, рекомендуется разбить его на
+несколько однострочных комментариев. По возможности стремитесь к комментариям,
+которые будут хорошо читаться на терминале шириной 80 колонок, однако это не
+жесткий предел; в Go нет фиксированного ограничения длины строки для
+комментариев. Стандартная библиотека, например, часто предпочитает разрывать
+комментарий по пунктуации, что иногда оставляет отдельные строки ближе к отметке
+60-70 символов.
+
+Существует множество существующего кода, в котором комментарии превышают 80
+символов в длину. Эти рекомендации не следует использовать как оправдание для
+изменения такого кода в ходе проверки на читаемость (см.
+[согласованность](https://neonxp.ru/pages/gostyleguide/google/guide/#consistency)), хотя командам рекомендуется использовать
+возможность обновлять комментарии в соответствии с этим руководством в рамках
+других рефакторингов. Основная цель этого руководства — гарантировать, что все
+наставники по читаемости Go дают одинаковые рекомендации, когда и если
+рекомендации даются.
+
+Подробнее о комментариях см. в [этом посте из блога Go о документации].
+
+[этом посте из блога Go о документации]:
+ https://blog.golang.org/godoc-documenting-go-code
+
+```text
+# Хорошо:
+// Это абзац комментария.
+// Длина отдельных строк не имеет значения в Godoc;
+// но выбор переноса делает его легко читаемым на узких экранах.
+//
+// Не беспокойтесь слишком о длинном URL:
+// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
+//
+// Аналогично, если у вас есть другая информация, которая становится неудобной
+// из-за слишком большого количества разрывов строк, используйте свое суждение и включите длинную строку,
+// если она помогает, а не мешает.
+```
+
+Избегайте комментариев, которые будут многократно переноситься на маленьких
+экранах, что ухудшает удобство чтения.
+
+```text
+# Плохо:
+// Это абзац комментария. Длина отдельных строк не имеет значения в Godoc;
+// но выбор переноса создает неровные строки на узких экранах или при просмотре кода,
+// что может раздражать, особенно в блоке комментариев, который будет переноситься
+// многократно.
+//
+// Не беспокойтесь слишком о длинном URL:
+// https://supercalifragilisticexpialidocious.example.com:8080/Animalia/Chordata/Mammalia/Rodentia/Geomyoidea/Geomyidae/
+```
+
+<a id="doc-comments"></a>
+
+### Документирующие комментарии (Doc comments)
+
+<a id="TOC-DocComments"></a>
+
+Все экспортируемые имена верхнего уровня должны иметь документирующие
+комментарии, как и неэкспортируемые объявления типов или функций с неочевидным
+поведением или смыслом. Эти комментарии должны быть [полными предложениями],
+которые начинаются с имени описываемого объекта. Артикль ("a", "an", "the")
+может предшествовать имени, чтобы оно читалось более естественно.
+
+```go
+// Хорошо:
+// Request представляет запрос на выполнение команды.
+type Request struct { ...
+
+// Encode записывает JSON-кодировку req в w.
+func Encode(w io.Writer, req *Request) { ...
+```
+
+Документирующие комментарии появляются в [Godoc](https://pkg.go.dev/) и
+выводятся в IDE, поэтому их следует писать для всех, кто использует пакет.
+
+[полными предложениями]: #comment-sentences
+
+Документирующий комментарий относится к следующему символу или группе полей,
+если он появляется в структуре.
+
+```go
+// Хорошо:
+// Options настраивает сервис управления группами.
+type Options struct {
+ // Общая настройка:
+ Name string
+ Group *FooGroup
+
+ // Зависимости:
+ DB *sql.DB
+
+ // Кастомизация:
+ LargeGroupThreshold int // опционально; по умолчанию: 10
+ MinimumMembers int // опционально; по умолчанию: 2
+}
+```
+
+**Лучшая практика:** Если у вас есть документирующие комментарии для
+неэкспортируемого кода, следуйте тому же обычаю, как если бы он был
+экспортируемым (а именно, начиная комментарий с неэкспортируемого имени). Это
+упрощает его экспорт позже простой заменой неэкспортируемого имени на новое
+экспортируемое в комментариях и коде.
+
+<a id="comment-sentences"></a>
+
+### Предложения в комментариях
+
+<a id="TOC-CommentSentences"></a>
+
+Комментарии, которые являются полными предложениями, должны начинаться с
+заглавной буквы и заканчиваться пунктуацией, как стандартные английские
+предложения. (В качестве исключения можно начать предложение с
+некапитализированного имени идентификатора, если оно иначе понятно. Такие
+случаи, вероятно, лучше делать только в начале абзаца.)
+
+Комментарии, которые являются фрагментами предложений, не имеют таких требований
+к пунктуации или капитализации.
+
+[Документирующие комментарии] всегда должны быть полными предложениями, и
+поэтому всегда должны начинаться с заглавной буквы и заканчиваться пунктуацией.
+Простые комментарии в конце строки (особенно для полей структуры) могут быть
+простыми фразами, которые предполагают, что имя поля является подлежащим.
+
+```go
+// Хорошо:
+// Server обрабатывает подачу цитат из собрания сочинений Шекспира.
+type Server struct {
+ // BaseDir указывает на базовый каталог, в котором хранятся работы Шекспира.
+ //
+ // Ожидается следующая структура каталога:
+ // {BaseDir}/manifest.json
+ // {BaseDir}/{name}/{name}-part{number}.txt
+ BaseDir string
+
+ WelcomeMessage string // отображается при входе пользователя
+ ProtocolVersion string // проверяется для входящих запросов
+ PageLength int // строк на странице при печати (опционально; по умолчанию: 20)
+}
+```
+
+[Документирующие комментарии]: #doc-comments
+
+<a id="examples"></a>
+
+### Примеры
+
+<a id="TOC-Examples"></a>
+
+Пакеты должны четко документировать предполагаемое использование. Постарайтесь
+предоставить [исполняемый пример]; примеры появляются в Godoc. Исполняемые
+примеры принадлежат тестовому файлу, а не файлу исходного кода продакшена.
+Смотрите этот пример ([Godoc], [исходный код]).
+
+[исполняемый пример]: http://blog.golang.org/examples
+[Godoc]: https://pkg.go.dev/time#example-Duration
+[исходный код]:
+ https://cs.opensource.google/go/go/+/HEAD:src/time/example_test.go
+
+Если предоставить исполняемый пример нецелесообразно, пример кода может быть
+предоставлен внутри комментариев к коду. Как и другие фрагменты кода и командной
+строки в комментариях, он должен следовать стандартным соглашениям
+форматирования.
+
+<a id="named-result-parameters"></a>
+
+### Именованные возвращаемые параметры
+
+<a id="TOC-NamedResultParameters"></a>
+
+При именовании параметров учитывайте, как сигнатуры функций отображаются в
+Godoc. Имя самой функции и тип возвращаемых параметров часто достаточно ясны.
+
+```go
+// Хорошо:
+func (n *Node) Parent1() *Node
+func (n *Node) Parent2() (*Node, error)
+```
+
+Если функция возвращает два или более параметра одного типа, добавление имен
+может быть полезным.
+
+```go
+// Хорошо:
+func (n *Node) Children() (left, right *Node, err error)
+```
+
+Если вызывающая сторона должна выполнить действие над определенными
+возвращаемыми параметрами, их именование может помочь подсказать, какое это
+действие:
+
+```go
+// Хорошо:
+// WithTimeout возвращает контекст, который будет отменен не позднее, чем через длительность d
+// от текущего момента.
+//
+// Вызывающая сторона должна обеспечить вызов возвращенной функции cancel, когда
+// контекст больше не нужен, чтобы предотвратить утечку ресурсов.
+func WithTimeout(parent Context, d time.Duration) (ctx Context, cancel func())
+```
+
+В приведенном выше коде отмена — это конкретное действие, которое должна
+выполнить вызывающая сторона. Однако, если бы возвращаемые параметры были
+записаны просто как `(Context, func())`, было бы неясно, что подразумевается под
+"функцией отмены".
+
+Не используйте именованные возвращаемые параметры, когда имена создают [ненужные
+повторы](#repetitive-with-type).
+
+```go
+// Плохо:
+func (n *Node) Parent1() (node *Node)
+func (n *Node) Parent2() (node *Node, err error)
+```
+
+Не называйте возвращаемые параметры, чтобы избежать объявления переменной внутри
+функции. Эта практика приводит к излишней многословности API в обмен на
+незначительную краткость реализации.
+
+[Голые возвраты (Naked returns)] допустимы только в маленькой функции. Как
+только это функция среднего размера, будьте явны со своими возвращаемыми
+значениями. Аналогично, не называйте возвращаемые параметры только потому, что
+это позволяет вам использовать голые возвраты. [Ясность](https://neonxp.ru/pages/gostyleguide/google/guide/#clarity) всегда
+важнее, чем сэкономить несколько строк в вашей функции.
+
+Всегда приемлемо назвать возвращаемый параметр, если его значение должно быть
+изменено в отложенном замыкании.
+
+> **Совет:** Типы часто могут быть понятнее, чем имена в сигнатурах функций.
+> [GoTip #38: Функции как именованные типы] демонстрирует это.
+>
+> В [`WithTimeout`] выше, реальный код использует [`CancelFunc`] вместо простого
+> `func()` в списке возвращаемых параметров и требует небольших усилий для
+> документирования.
+
+[Голые возвраты (Naked returns)]: https://tour.golang.org/basics/7
+[GoTip #38: Функции как именованные типы]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[`WithTimeout`]: https://pkg.go.dev/context#WithTimeout
+[`CancelFunc`]: https://pkg.go.dev/context#CancelFunc
+
+<a id="package-comments"></a>
+
+### Комментарии к пакетам
+
+<a id="TOC-PackageComments"></a>
+
+Комментарии к пакету должны появляться непосредственно перед объявлением пакета,
+без пустой строки между комментарием и именем пакета. Пример:
+
+```go
+// Хорошо:
+// Package math предоставляет базовые константы и математические функции.
+//
+// Этот пакет не гарантирует битовой идентичности результатов на разных архитектурах.
+package math
+```
+
+Должен быть ровно один комментарий к пакету на пакет. Если пакет состоит из
+нескольких файлов, ровно в одном из файлов должен быть комментарий к пакету.
+
+Комментарии для пакетов `main` имеют немного другую форму, где имя правила
+`go_binary` в BUILD-файле заменяет имя пакета.
+
+```go
+// Хорошо:
+// Команда seed_generator — это утилита, которая генерирует файл сида Finch
+// из набора JSON-конфигураций исследований.
+package main
+```
+
+Другие стили комментариев допустимы, если имя бинарника точно такое же, как
+написано в BUILD-файле. Когда имя бинарника — первое слово, его заглавная буква
+обязательна, даже если оно не совпадает с написанием вызова в командной строке.
+
+```go
+// Хорошо:
+// Binary seed_generator ...
+// Command seed_generator ...
+// Program seed_generator ...
+// The seed_generator command ...
+// The seed_generator program ...
+// Seed_generator ...
+```
+
+Советы:
+
+* Примеры вызовов командной строки и использования API могут быть полезной
+ документацией. Для форматирования Godoc сделайте отступ для строк
+ комментария, содержащих код.
+
+* Если нет очевидного основного файла или если комментарий к пакету необычайно
+ длинный, допустимо поместить документирующий комментарий в файл с именем
+ `doc.go`, содержащий только комментарий и объявление пакета.
+
+* Многострочные комментарии могут использоваться вместо нескольких
+ однострочных. Это в первую очередь полезно, если документация содержит
+ разделы, которые могут быть полезны для копирования и вставки из исходного
+ файла, как, например, примеры командной строки (для бинарников) и примеры
+ шаблонов.
+
+ ```go
+ // Хорошо:
+ /*
+ Команда seed_generator — это утилита, которая генерирует файл сида Finch
+ из набора JSON-конфигураций исследований.
+
+ seed_generator *.json | base64 > finch-seed.base64
+ */
+ package template
+ ```
+
+* Комментарии, предназначенные для сопровождающих и относящиеся ко всему
+ файлу, обычно размещаются после объявлений импорта. Они не отображаются в
+ Godoc и не подчиняются приведенным выше правилам для комментариев к пакетам.
+
+<a id="imports"></a>
+
+## Импорты
+
+<a id="TOC-Imports"></a>
+
+<a id="import-renaming"></a>
+
+### Переименование импортов
+
+Импорты пакетов обычно не должны переименовываться, но есть случаи, когда они
+должны быть переименованы или когда переименование улучшает читаемость.
+
+Локальные имена для импортированных пакетов должны следовать [рекомендациям по
+именованию пакетов](#package-names), включая запрет на использование
+подчеркиваний и заглавных букв. Старайтесь быть
+[последовательными](https://neonxp.ru/pages/gostyleguide/google/guide/#consistency), всегда используя одно и то же локальное
+имя для одного и того же импортированного пакета.
+
+Импортированный пакет *должен* быть переименован, чтобы избежать конфликта имен
+с другими импортами. (Следствие из этого: [хорошие имена
+пакетов](#package-names) не должны требовать переименования.) В случае конфликта
+имен предпочтительнее переименовать наиболее локальный или специфичный для
+проекта импорт.
+
+Сгенерированные пакеты протоколов буфера *должны* быть переименованы, чтобы
+удалить подчеркивания из их имен, и их локальные имена должны иметь суффикс
+`pb`. Подробнее см. [лучшие практики для proto и заглушек
+(stubs)](https://neonxp.ru/pages/gostyleguide/google/best-practices/#import-protos).
+
+```go
+// Хорошо:
+import (
+ foosvcpb "path/to/package/foo_service_go_proto"
+)
+```
+
+Наконец, импортированный, несгенерированный пакет *может* быть переименован,
+если он имеет неинформативное имя (например, `util` или `v1`) Делайте это
+экономно: не переименовывайте пакет, если код, окружающий использование пакета,
+передает достаточно контекста. По возможности предпочитайте рефакторинг самого
+пакета с более подходящим именем.
+
+```go
+// Хорошо:
+import (
+ core "github.com/kubernetes/api/core/v1"
+ meta "github.com/kubernetes/apimachinery/pkg/apis/meta/v1beta1"
+)
+```
+
+Если вам нужно импортировать пакет, имя которого конфликтует с распространенным
+локальным именем переменной, которое вы хотите использовать (например, `url`,
+`ssh`), и вы хотите переименовать пакет, предпочтительный способ сделать это —
+использовать суффикс `pkg` (например, `urlpkg`). Обратите внимание, что
+возможно затенение пакета локальной переменной; это переименование необходимо
+только если пакет все еще нужно использовать, когда такая переменная находится в
+области видимости.
+
+<a id="import-grouping"></a>
+
+### Группировка импортов
+
+Импорты должны быть организованы в следующие группы по порядку:
+
+1. Пакеты стандартной библиотеки
+
+1. Другие пакеты (проекта и вендорные)
+
+1. Импорты Protocol Buffer (например, `fpb "path/to/foo_go_proto"`)
+
+1. Импорт для [побочных эффектов (side
+ effects)](https://go.dev/doc/effective_go#blank_import) (например, `_
+ "path/to/package"`)
+
+```go
+// Хорошо:
+package main
+
+import (
+ "fmt"
+ "hash/adler32"
+ "os"
+
+ "github.com/dsnet/compress/flate"
+ "golang.org/x/text/encoding"
+ "google.golang.org/protobuf/proto"
+
+ foopb "myproj/foo/proto/proto"
+
+ _ "myproj/rpc/protocols/dial"
+ _ "myproj/security/auth/authhooks"
+)
+```
+
+<a id="import-blank"></a>
+
+### "Пустой" импорт (`import _`)
+
+<a id="TOC-ImportBlank"></a>
+
+Пакеты, которые импортируются только для их побочных эффектов (используя
+синтаксис `import _ "package"`), могут импортироваться только в главном пакете
+(`main`) или в тестах, которые требуют их.
+
+Некоторые примеры таких пакетов:
+
+* [time/tzdata](https://pkg.go.dev/time/tzdata)
+
+* [image/jpeg](https://pkg.go.dev/image/jpeg) в коде обработки изображений
+
+Избегайте пустых импортов в библиотечных пакетах, даже если библиотека косвенно
+зависит от них. Ограничение импортов с побочными эффектами главным пакетом
+помогает контролировать зависимости и позволяет писать тесты, которые полагаются
+на другой импорт без конфликта или лишних затрат на сборку.
+
+Следующие исключения являются единственными для этого правила:
+
+* Вы можете использовать пустой импорт, чтобы обойти проверку на запрещенные
+ импорты в [статическом анализаторе nogo].
+
+* Вы можете использовать пустой импорт пакета
+ [embed](https://pkg.go.dev/embed) в исходном файле, который использует
+ директиву компилятора `//go:embed`.
+
+**Совет:** Если вы создаете библиотечный пакет, который косвенно зависит от
+импорта с побочным эффектом в продакшене, задокументируйте предполагаемое
+использование.
+
+[статическом анализаторе nogo]:
+ https://github.com/bazelbuild/rules_go/blob/master/go/nogo.rst
+
+<a id="import-dot"></a>
+
+### "Точечный" импорт (`import .`)
+
+<a id="TOC-ImportDot"></a>
+
+Форма `import .` — это языковая возможность, позволяющая переносить
+идентификаторы, экспортированные из другого пакета, в текущий пакет без
+квалификации. Подробнее см. [спецификацию
+языка](https://go.dev/ref/spec#Import_declarations).
+
+Не **используйте** эту возможность в кодовой базе Google; это затрудняет
+понимание, откуда взялась функциональность.
+
+```go
+// Плохо:
+package foo_test
+
+import (
+ "bar/testutil" // также импортирует "foo"
+ . "foo"
+)
+
+var myThing = Bar() // Bar определен в пакете foo; квалификация не нужна.
+```
+
+```go
+// Хорошо:
+package foo_test
+
+import (
+ "bar/testutil" // также импортирует "foo"
+ "foo"
+)
+
+var myThing = foo.Bar()
+```
+
+<a id="errors"></a>
+
+## Ошибки
+
+<a id="returning-errors"></a>
+
+### Возврат ошибок
+
+<a id="TOC-ReturningErrors"></a>
+
+Используйте `error` для сигнализации о том, что функция может завершиться
+неудачей. По соглашению, `error` — последний параметр результата.
+
+```go
+// Хорошо:
+func Good() error { /* ... */ }
+```
+
+Возврат `nil` в качестве ошибки — идиоматический способ сигнализировать об
+успешной операции, которая в противном случае могла бы завершиться неудачей.
+Если функция возвращает ошибку, вызывающие стороны должны рассматривать все
+не-ошибочные возвращаемые значения как неопределенные, если явно не
+задокументировано иначе. Обычно не-ошибочные возвращаемые значения являются их
+нулевыми значениями, но на это нельзя полагаться.
+
+```go
+// Хорошо:
+func GoodLookup() (*Result, error) {
+ // ...
+ if err != nil {
+ return nil, err
+ }
+ return res, nil
+}
+```
+
+Экспортируемые функции, возвращающие ошибки, должны возвращать их, используя тип
+`error`. Конкретные типы ошибок подвержены тонким ошибкам: конкретный `nil`
+указатель может быть завернут в интерфейс и таким образом стать ненулевым
+значением (см. [FAQ по Go на эту тему][nil error]).
+
+```go
+// Плохо:
+func Bad() *os.PathError { /*...*/ }
+```
+
+**Совет:** Функция, принимающая аргумент [`context.Context`], обычно должна
+возвращать `error`, чтобы вызывающая сторона могла определить, была ли отменена
+контекст во время выполнения функции.
+
+[nil error]: https://golang.org/doc/faq#nil_error
+
+<a id="error-strings"></a>
+
+### Строки ошибок
+
+<a id="TOC-ErrorStrings"></a>
+
+Строки ошибок не должны начинаться с заглавной буквы (если только не начинаются
+с экспортируемого имени, имени собственного или аббревиатуры) и не должны
+заканчиваться пунктуацией. Это потому что строки ошибок обычно появляются внутри
+другого контекста перед выводом пользователю.
+
+```go
+// Плохо:
+err := fmt.Errorf("Something bad happened.")
+```
+
+```go
+// Хорошо:
+err := fmt.Errorf("something bad happened")
+```
+
+С другой стороны, стиль для полного отображаемого сообщения (логирование,
+неудача теста, ответ API или другой UI) зависит от ситуации, но обычно должен
+быть написан с заглавной буквы.
+
+```go
+// Хорошо:
+log.Infof("Operation aborted: %v", err)
+log.Errorf("Operation aborted: %v", err)
+t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)
+```
+
+<a id="handle-errors"></a>
+
+### Обработка ошибок
+
+<a id="TOC-HandleErrors"></a>
+
+Код, который сталкивается с ошибкой, должен принять осознанное решение о том,
+как ее обработать. Обычно нецелесообразно отбрасывать ошибки, используя
+переменные `_`. Если функция возвращает ошибку, сделайте одно из следующих
+действий:
+
+* Обработайте и исправьте ошибку немедленно.
+* Верните ошибку вызывающей стороне.
+* В исключительных ситуациях вызовите [`log.Fatal`] или (если абсолютно
+ необходимо) `panic`.
+
+**Примечание:** `log.Fatalf` здесь не из стандартной библиотеки log. См.
+[#logging].
+
+В редких обстоятельствах, когда уместно игнорировать или отбросить ошибку
+(например, вызов [`(*bytes.Buffer).Write`], который, как задокументировано,
+никогда не завершается неудачей), сопровождающий комментарий должен объяснять,
+почему это безопасно.
+
+```go
+// Хорошо:
+var b *bytes.Buffer
+
+n, _ := b.Write(p) // никогда не возвращает ненулевую ошибку
+```
+
+Для дальнейшего обсуждения и примеров обработки ошибок см. [Effective
+Go](http://golang.org/doc/effective_go.html#errors) и [лучшие
+практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/.md#error-handling).
+
+[`(*bytes.Buffer).Write`]: https://pkg.go.dev/bytes#Buffer.Write
+
+<a id="in-band-errors"></a>
+
+### Ошибки "в потоке" (In-band errors)
+
+<a id="TOC-In-Band-Errors"></a>
+
+В C и подобных языках распространено, что функции возвращают значения, такие как
+-1, null или пустую строку, чтобы сигнализировать об ошибках или отсутствующих
+результатах. Это известно как обработка ошибок "в потоке".
+
+```go
+// Плохо:
+// Lookup возвращает значение для ключа или -1, если нет соответствия для ключа.
+func Lookup(key string) int
+```
+
+Неспособность проверить значение ошибки "в потоке" может привести к ошибкам и
+может приписать ошибки не той функции.
+
+```go
+// Плохо:
+// Следующая строка возвращает ошибку, что Parse завершился неудачей для входного значения,
+// тогда как на самом деле неудача в том, что нет соответствия для missingKey.
+return Parse(Lookup(missingKey))
+```
+
+Поддержка Go нескольких возвращаемых значений предоставляет лучшее решение (см.
+[раздел Effective Go о множественных возвращаемых значениях]). Вместо того
+чтобы требовать от клиентов проверять значение ошибки "в потоке", функция должна
+возвращать дополнительное значение, чтобы указать, являются ли ее другие
+возвращаемые значения валидными. Это возвращаемое значение может быть ошибкой
+или булевым значением, когда объяснение не нужно, и должно быть последним
+возвращаемым значением.
+
+```go
+// Хорошо:
+// Lookup возвращает значение для ключа или ok=false, если нет соответствия для ключа.
+func Lookup(key string) (value string, ok bool)
+```
+
+Такой API предотвращает ошибочное написание вызывающей стороной
+`Parse(Lookup(key))`, что вызывает ошибку времени компиляции, поскольку
+`Lookup(key)` имеет 2 вывода.
+
+Возврат ошибок таким образом способствует более надежной и явной обработке
+ошибок:
+
+```go
+// Хорошо:
+value, ok := Lookup(key)
+if !ok {
+ return fmt.Errorf("no value for %q", key)
+}
+return Parse(value)
+```
+
+Некоторые функции стандартной библиотеки, например в пакете `strings`,
+возвращают значения ошибок "в потоке". Это значительно упрощает код для
+манипуляций со строками за счет необходимости большей внимательности от
+программиста. В целом, код Go в кодовой базе Google должен возвращать
+дополнительные значения для ошибок.
+
+[раздел Effective Go о множественных возвращаемых значениях]:
+ http://golang.org/doc/effective_go.html#multiple-returns
+
+<a id="indent-error-flow"></a>
+
+### Отступы для потока ошибок
+
+<a id="TOC-IndentErrorFlow"></a>
+
+Обрабатывайте ошибки перед продолжением выполнения остального кода. Это улучшает
+читаемость кода, позволяя читателю быстро найти нормальный путь выполнения. Эта
+же логика применяется к любому блоку, который проверяет условие, а затем
+завершается терминальным условием (например, `return`, `panic`, `log.Fatal`).
+
+Код, который выполняется, если терминальное условие не выполнено, должен
+появляться после блока `if` и не должен быть вложен в предложение `else`.
+
+```go
+// Хорошо:
+if err != nil {
+ // обработка ошибки
+ return // или continue, и т.д.
+}
+// нормальный код
+```
+
+```go
+// Плохо:
+if err != nil {
+ // обработка ошибки
+} else {
+ // нормальный код, который выглядит ненормальным из-за отступа
+}
+```
+
+> **Совет:** Если вы используете переменную более чем для нескольких строк кода,
+> как правило, не стоит использовать стиль `if` с инициализатором. В этих
+> случаях обычно лучше вынести объявление наружу и использовать стандартный `if`
+> оператор:
+>
+> ```go
+> // Хорошо:
+> x, err := f()
+> if err != nil {
+> // обработка ошибки
+> return
+> }
+> // много кода, который использует x
+> // на нескольких строках
+> ```
+>
+> ```go
+> // Плохо:
+> if x, err := f(); err != nil {
+> // обработка ошибки
+> return
+> } else {
+> // много кода, который использует x
+> // на нескольких строках
+> }
+> ```
+
+Подробнее см. [Go Tip #1: Линия видимости (Line of Sight)] и [TotT: Reduce Code
+Complexity by Reducing
+Nesting](https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html).
+
+[Go Tip #1: Линия видимости (Line of Sight)]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="language"></a>
+
+## Язык
+
+<a id="literal-formatting"></a>
+
+### Форматирование литералов
+
+Go обладает исключительно мощным [синтаксисом составных литералов], с помощью
+которого можно выражать глубоко вложенные, сложные значения одним выражением.
+По возможности следует использовать этот синтаксис литералов вместо построения
+значений поле за полем. Форматирование `gofmt` для литералов, как правило,
+довольно хорошее, но существуют некоторые дополнительные правила для сохранения
+читаемости и поддерживаемости этих литералов.
+
+[синтаксисом составных литералов]:
+ https://golang.org/ref/spec#Composite_literals
+
+<a id="literal-field-names"></a>
+
+#### Имена полей
+
+Литералы структур должны указывать **имена полей** для типов, определенных вне
+текущего пакета.
+
+* Включайте имена полей для типов из других пакетов.
+
+ ```go
+ // Хорошо:
+ // https://pkg.go.dev/encoding/csv#Reader
+ r := csv.Reader{
+ Comma: ',',
+ Comment: '#',
+ FieldsPerRecord: 4,
+ }
+ ```
+
+ Положение полей в структуре и полный набор полей (оба из которых необходимо
+ указать правильно, когда имена полей опущены) обычно не считаются частью
+ публичного API структуры; указание имени поля необходимо, чтобы избежать
+ ненужной связи.
+
+ ```go
+ // Плохо:
+ r := csv.Reader{',', '#', 4, false, false, false, false}
+ ```
+
+* Для локальных типов пакета имена полей не обязательны.
+
+ ```go
+ // Хорошо:
+ okay := Type{42}
+ also := internalType{4, 2}
+ ```
+
+ Имена полей все же следует использовать, если это делает код яснее, и это
+ очень распространено. Например, структуру с большим количеством полей почти
+ всегда следует инициализировать с указанием имен полей.
+
+ <!-- TODO: Maybe a better example here that doesn't have many fields. -->
+
+ ```go
+ // Хорошо:
+ okay := StructWithLotsOfFields{
+ field1: 1,
+ field2: "two",
+ field3: 3.14,
+ field4: true,
+ }
+ ```
+
+<a id="literal-matching-braces"></a>
+
+#### Совпадение скобок
+
+Закрывающая часть пары фигурных скобок всегда должна появляться на строке с
+таким же количеством отступов, как и открывающая скобка. Однострочные литералы
+обязательно имеют это свойство. Когда литерал занимает несколько строк,
+сохранение этого свойства сохраняет соответствие скобок для литералов таким же,
+как соответствие скобок для обычных синтаксических конструкций Go, таких как
+функции и операторы `if`.
+
+Самая распространенная ошибка в этой области — ставить закрывающую скобку на той
+же строке, что и значение в многострочном литерале структуры. В этих случаях
+строка должна заканчиваться запятой, а закрывающая скобка должна появляться на
+следующей строке.
+
+```go
+// Хорошо:
+good := []*Type\{\{Key: "value"\}\}
+```
+
+```go
+// Хорошо:
+good := []*Type{
+ {Key: "multi"},
+ {Key: "line"},
+}
+```
+
+```go
+// Плохо:
+bad := []*Type{
+ {Key: "multi"},
+ {Key: "line"}}
+```
+
+```go
+// Плохо:
+bad := []*Type{
+ {
+ Key: "value"},
+}
+```
+
+<a id="literal-cuddled-braces"></a>
+
+#### "Объединенные" скобки (Cuddled braces)
+
+Удаление пробела между фигурными скобками (так называемое "объединение" их) для
+литералов срезов и массивов допустимо только когда оба следующих условия
+истинны.
+
+* [Отступы совпадают](#literal-matching-braces)
+* Внутренние значения также являются литералами или сборщиками protobuf (то
+ есть не переменной или другим выражением)
+
+```go
+// Хорошо:
+good := []*Type{
+ { // Не объединены
+ Field: "value",
+ },
+ {
+ Field: "value",
+ },
+}
+```
+
+```go
+// Хорошо:
+good := []*Type{\{ // Правильно объединены
+ Field: "value",
+}, {
+ Field: "value",
+}\}
+```
+
+```go
+// Хорошо:
+good := []*Type{
+ first, // Не могут быть объединены
+ {Field: "second"},
+}
+```
+
+```go
+// Хорошо:
+okay := []*pb.Type{pb.Type_builder{
+ Field: "first", // Сборщики Proto могут быть объединены для экономии вертикального пространства
+}.Build(), pb.Type_builder{
+ Field: "second",
+}.Build()}
+```
+
+```go
+// Плохо:
+bad := []*Type{
+ first,
+ {
+ Field: "second",
+ }\}
+```
+
+<a id="literal-repeated-type-names"></a>
+
+#### Повторяющиеся имена типов
+
+Повторяющиеся имена типов могут быть опущены в литералах срезов и карт (map).
+Это может помочь уменьшить загромождение. Приемлемый случай для явного
+повторения имен типов — это работа со сложным типом, который не является
+распространенным в вашем проекте, когда повторяющиеся имена типов находятся на
+строках, далеко отстоящих друг от друга, и могут напоминать читателю о
+контексте.
+
+```go
+// Хорошо:
+good := []*Type{
+ {A: 42},
+ {A: 43},
+}
+```
+
+```go
+// Плохо:
+repetitive := []*Type{
+ &Type{A: 42},
+ &Type{A: 43},
+}
+```
+
+```go
+// Хорошо:
+good := map[Type1]*Type2{
+ {A: 1}: {B: 2},
+ {A: 3}: {B: 4},
+}
+```
+
+```go
+// Плохо:
+repetitive := map[Type1]*Type2{
+ Type1{A: 1}: &Type2{B: 2},
+ Type1{A: 3}: &Type2{B: 4},
+}
+```
+
+**Совет:** Если вы хотите удалить повторяющиеся имена типов в литералах
+структур, вы можете запустить `gofmt -s`.
+
+<a id="literal-zero-value-fields"></a>
+
+#### Поля с нулевыми значениями
+
+[Нулевые] поля могут быть опущены в литералах структур, когда ясность не
+теряется в результате.
+
+Хорошо спроектированные API часто используют конструкцию с нулевыми значениями
+для улучшенной читаемости. Например, опускание трех полей с нулевыми значениями
+из следующей структуры привлекает внимание к единственной опции, которая
+указана.
+
+[Нулевые]: https://golang.org/ref/spec#The_zero_value
+
+```go
+// Плохо:
+import (
+ "github.com/golang/leveldb"
+ "github.com/golang/leveldb/db"
+)
+
+ldb := leveldb.Open("/my/table", &db.Options{
+ BlockSize: 1<<16,
+ ErrorIfDBExists: true,
+
+ // Все эти поля имеют свои нулевые значения.
+ BlockRestartInterval: 0,
+ Comparer: nil,
+ Compression: nil,
+ FileSystem: nil,
+ FilterPolicy: nil,
+ MaxOpenFiles: 0,
+ WriteBufferSize: 0,
+ VerifyChecksums: false,
+})
+```
+
+```go
+// Хорошо:
+import (
+ "github.com/golang/leveldb"
+ "github.com/golang/leveldb/db"
+)
+
+ldb := leveldb.Open("/my/table", &db.Options{
+ BlockSize: 1<<16,
+ ErrorIfDBExists: true,
+})
+```
+
+Структуры в табличных тестах часто выигрывают от [явных имен полей], особенно
+когда тестовая структура нетривиальна. Это позволяет автору опускать поля с
+нулевыми значениями полностью, когда рассматриваемые поля не относятся к
+тестовому случаю. Например, успешные тестовые случаи должны опускать любые поля,
+связанные с ошибками или неудачами. В случаях, когда нулевое значение
+необходимо для понимания тестового случая, таких как тестирование нулевых или
+`nil` входных данных, имена полей должны быть указаны.
+
+[явных имен полей]: #literal-field-names
+
+**Лаконично**
+
+```go
+tests := []struct {
+ input string
+ wantPieces []string
+ wantErr error
+}{
+ {
+ input: "1.2.3.4",
+ wantPieces: []string{"1", "2", "3", "4"},
+ },
+ {
+ input: "hostname",
+ wantErr: ErrBadHostname,
+ },
+}
+```
+
+**Явно**
+
+```go
+tests := []struct {
+ input string
+ wantIPv4 bool
+ wantIPv6 bool
+ wantErr bool
+}{
+ {
+ input: "1.2.3.4",
+ wantIPv4: true,
+ wantIPv6: false,
+ },
+ {
+ input: "1:2::3:4",
+ wantIPv4: false,
+ wantIPv6: true,
+ },
+ {
+ input: "hostname",
+ wantIPv4: false,
+ wantIPv6: false,
+ wantErr: true,
+ },
+}
+```
+
+<a id="nil-slices"></a>
+
+### Nil-срезы
+
+Для большинства целей нет функциональной разницы между `nil` и пустым срезом.
+Встроенные функции, такие как `len` и `cap`, ведут себя ожидаемо на `nil`
+срезах.
+
+```go
+// Хорошо:
+import "fmt"
+
+var s []int // nil
+
+fmt.Println(s) // []
+fmt.Println(len(s)) // 0
+fmt.Println(cap(s)) // 0
+for range s {...} // нет операции
+
+s = append(s, 42)
+fmt.Println(s) // [42]
+```
+
+Если вы объявляете пустой срез как локальную переменную (особенно если она может
+быть источником возвращаемого значения), предпочитайте инициализацию nil, чтобы
+снизить риск ошибок со стороны вызывающих.
+
+```go
+// Хорошо:
+var t []string
+```
+
+```go
+// Плохо:
+t := []string{}
+```
+
+Не создавайте API, которые вынуждают своих клиентов делать различие между nil и
+пустым срезом.
+
+```go
+// Хорошо:
+// Ping проверяет доступность своих целей.
+// Возвращает хосты, которые успешно ответили.
+func Ping(hosts []string) ([]string, error) { ... }
+```
+
+```go
+// Плохо:
+// Ping проверяет доступность своих целей и возвращает список хостов,
+// которые успешно ответили. Может быть пустым, если вход был пустым.
+// nil означает, что произошла системная ошибка.
+func Ping(hosts []string) []string { ... }
+```
+
+При проектировании интерфейсов избегайте различия между `nil` срезом и ненулевым
+срезом нулевой длины, так как это может привести к тонким программным ошибкам.
+Обычно это достигается с помощью `len` для проверки на пустоту, а не `== nil`.
+
+Эта реализация принимает как `nil`, так и срезы нулевой длины как "пустые":
+
+```go
+// Хорошо:
+// describeInts описывает s с заданным префиксом, если s не пуст.
+func describeInts(prefix string, s []int) {
+ if len(s) == 0 {
+ return
+ }
+ fmt.Println(prefix, s)
+}
+```
+
+Вместо того чтобы полагаться на различие как часть API:
+
+```go
+// Плохо:
+func maybeInts() []int { /* ... */ }
+
+// describeInts описывает s с заданным префиксом; передайте nil, чтобы полностью пропустить.
+func describeInts(prefix string, s []int) {
+ // Поведение этой функции непреднамеренно меняется в зависимости от того, что
+ // возвращает maybeInts() в 'пустых' случаях (nil или []int{}).
+ if s == nil {
+ return
+ }
+ fmt.Println(prefix, s)
+}
+
+describeInts("Here are some ints:", maybeInts())
+```
+
+См. [ошибки "в потоке"] для дальнейшего обсуждения.
+
+[ошибки "в потоке"]: #in-band-errors
+
+<a id="indentation-confusion"></a>
+
+### Путаница с отступами
+
+Избегайте введения разрыва строки, если это приведет к выравниванию остальной
+части строки с вложенным блоком кода. Если этого невозможно избежать, оставьте
+пробел, чтобы отделить код в блоке от перенесенной строки.
+
+```go
+// Плохо:
+if longCondition1 && longCondition2 &&
+ // Условия 3 и 4 имеют тот же отступ, что и код внутри if.
+ longCondition3 && longCondition4 {
+ log.Info("all conditions met")
+}
+```
+
+См. следующие разделы для конкретных рекомендаций и примеров:
+
+* [Форматирование функций](#func-formatting)
+* [Условные операторы и циклы](#conditional-formatting)
+* [Форматирование литералов](#literal-formatting)
+
+<a id="func-formatting"></a>
+
+### Форматирование функций
+
+Сигнатура объявления функции или метода должна оставаться на одной строке, чтобы
+избежать [путаницы с отступами](#indentation-confusion).
+
+Списки аргументов функций могут создавать одни из самых длинных строк в файле
+исходного кода Go. Однако они предшествуют изменению отступа, и поэтому трудно
+разорвать строку так, чтобы последующие строки не выглядели частью тела функции
+запутанным образом:
+
+```go
+// Плохо:
+func (r *SomeType) SomeLongFunctionName(foo1, foo2, foo3 string,
+ foo4, foo5, foo6 int) {
+ foo7 := bar(foo1)
+ // ...
+}
+```
+
+См. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#funcargs) для нескольких вариантов
+сокращения мест вызовов функций, которые в противном случае имели бы много
+аргументов.
+
+Строки часто можно сократить, вынося локальные переменные.
+
+```go
+// Хорошо:
+local := helper(some, parameters, here)
+good := foo.Call(list, of, parameters, local)
+```
+
+Аналогично, вызовы функций и методов не должны разделяться исключительно на
+основе длины строки.
+
+```go
+// Хорошо:
+good := foo.Call(long, list, of, parameters, all, on, one, line)
+```
+
+```go
+// Плохо:
+bad := foo.Call(long, list, of, parameters,
+ with, arbitrary, line, breaks)
+```
+
+По возможности избегайте добавления встроенных комментариев к конкретным
+аргументам функций. Вместо этого используйте [структуру опций (option
+struct)](https://neonxp.ru/pages/gostyleguide/google/best-practices/#option-structure) или добавьте больше деталей в
+документацию функции.
+
+```go
+// Хорошо:
+good := server.New(ctx, server.Options{Port: 42})
+```
+
+```go
+// Плохо:
+bad := server.New(
+ ctx,
+ 42, // Port
+)
+```
+
+Если API нельзя изменить или если локальный вызов необычен (независимо от того,
+слишком длинный вызов или нет), всегда допустимо добавлять разрывы строк, если
+это помогает понять вызов.
+
+```go
+// Хорошо:
+canvas.RenderHeptagon(fillColor,
+ x0, y0, vertexColor0,
+ x1, y1, vertexColor1,
+ x2, y2, vertexColor2,
+ x3, y3, vertexColor3,
+ x4, y4, vertexColor4,
+ x5, y5, vertexColor5,
+ x6, y6, vertexColor6,
+)
+```
+
+Обратите внимание, что строки в приведенном выше примере не переносятся на
+определенную границу столбца, а группируются на основе координат вершин и цвета.
+
+Длинные строковые литералы внутри функций не должны разбиваться ради длины
+строки. Для функций, включающих такие строки, разрыв строки можно добавить
+после формата строки, а аргументы могут быть предоставлены на следующей или
+последующих строках. Решение о том, где должны быть разрывы строк, лучше всего
+принимать на основе семантических групп входных данных, а не исключительно на
+основе длины строки.
+
+```go
+// Хорошо:
+log.Warningf("Database key (%q, %d, %q) incompatible in transaction started by (%q, %d, %q)",
+ currentCustomer, currentOffset, currentKey,
+ txCustomer, txOffset, txKey)
+```
+
+```go
+// Плохо:
+log.Warningf("Database key (%q, %d, %q) incompatible in"+
+ " transaction started by (%q, %d, %q)",
+ currentCustomer, currentOffset, currentKey, txCustomer,
+ txOffset, txKey)
+```
+
+<a id="conditional-formatting"></a>
+
+### Условные операторы и циклы
+
+Оператор `if` не должен разбиваться на строки; многострочные условия `if` могут
+привести к [путанице с отступами](#indentation-confusion).
+
+```go
+// Плохо:
+// Второй оператор if выровнен с кодом внутри блока if, что вызывает
+// путаницу с отступами.
+if db.CurrentStatusIs(db.InTransaction) &&
+ db.ValuesEqual(db.TransactionKey(), row.Key()) {
+ return db.Errorf(db.TransactionError, "query failed: row (%v): key does not match transaction key", row)
+}
+```
+
+Если короткое замыкание не требуется, булевы операнды могут быть извлечены
+напрямую:
+
+```go
+// Хорошо:
+inTransaction := db.CurrentStatusIs(db.InTransaction)
+keysMatch := db.ValuesEqual(db.TransactionKey(), row.Key())
+if inTransaction && keysMatch {
+ return db.Error(db.TransactionError, "query failed: row (%v): key does not match transaction key", row)
+}
+```
+
+Также могут быть другие локальные переменные, которые можно извлечь, особенно
+если условие уже повторяется:
+
+```go
+// Хорошо:
+uid := user.GetUniqueUserID()
+if db.UserIsAdmin(uid) || db.UserHasPermission(uid, perms.ViewServerConfig) || db.UserHasPermission(uid, perms.CreateGroup) {
+ // ...
+}
+```
+
+```go
+// Плохо:
+if db.UserIsAdmin(user.GetUniqueUserID()) || db.UserHasPermission(user.GetUniqueUserID(), perms.ViewServerConfig) || db.UserHasPermission(user.GetUniqueUserID(), perms.CreateGroup) {
+ // ...
+}
+```
+
+Операторы `if`, содержащие замыкания или многострочные литералы структур, должны
+обеспечить, чтобы [скобки совпадали](#literal-matching-braces), чтобы избежать
+[путаницы с отступами](#indentation-confusion).
+
+```go
+// Хорошо:
+if err := db.RunInTransaction(func(tx *db.TX) error {
+ return tx.Execute(userUpdate, x, y, z)
+}); err != nil {
+ return fmt.Errorf("user update failed: %s", err)
+}
+```
+
+```go
+// Хорошо:
+if _, err := client.Update(ctx, &upb.UserUpdateRequest{
+ ID: userID,
+ User: user,
+}); err != nil {
+ return fmt.Errorf("user update failed: %s", err)
+}
+```
+
+Аналогично, не пытайтесь вставлять искусственные разрывы строк в операторы
+`for`. Вы всегда можете позволить строке быть просто длинной, если нет
+элегантного способа ее рефакторить:
+
+```go
+// Хорошо:
+for i, max := 0, collection.Size(); i < max && !collection.HasPendingWriters(); i++ {
+ // ...
+}
+```
+
+Однако часто он есть:
+
+```go
+// Хорошо:
+for i, max := 0, collection.Size(); i < max; i++ {
+ if collection.HasPendingWriters() {
+ break
+ }
+ // ...
+}
+```
+
+Операторы `switch` и `case` также должны оставаться на одной строке.
+
+```go
+// Хорошо:
+switch good := db.TransactionStatus(); good {
+case db.TransactionStarting, db.TransactionActive, db.TransactionWaiting:
+ // ...
+case db.TransactionCommitted, db.NoTransaction:
+ // ...
+default:
+ // ...
+}
+```
+
+```go
+// Плохо:
+switch bad := db.TransactionStatus(); bad {
+case db.TransactionStarting,
+ db.TransactionActive,
+ db.TransactionWaiting:
+ // ...
+case db.TransactionCommitted,
+ db.NoTransaction:
+ // ...
+default:
+ // ...
+}
+```
+
+Если строка чрезмерно длинная, сделайте отступ для всех вариантов (`case`) и
+отделите их пустой строкой, чтобы избежать [путаницы с
+отступами](#indentation-confusion):
+
+```go
+// Хорошо:
+switch db.TransactionStatus() {
+case
+ db.TransactionStarting,
+ db.TransactionActive,
+ db.TransactionWaiting,
+ db.TransactionCommitted:
+
+ // ...
+case db.NoTransaction:
+ // ...
+default:
+ // ...
+}
+```
+
+В условных выражениях, сравнивающих переменную с константой, помещайте значение
+переменной слева от оператора равенства:
+
+```go
+// Хорошо:
+if result == "foo" {
+ // ...
+}
+```
+
+Вместо менее ясной формулировки, где константа идет первой (["условия в стиле
+Йоды"](https://en.wikipedia.org/wiki/Yoda_conditions)):
+
+```go
+// Плохо:
+if "foo" == result {
+ // ...
+}
+```
+
+<a id="copying"></a>
+
+### Копирование
+
+<a id="TOC-Copying"></a>
+
+Чтобы избежать неожиданного псевдонимного доступа (aliasing) и подобных ошибок,
+будьте осторожны при копировании структуры из другого пакета. Например, объекты
+синхронизации, такие как `sync.Mutex`, не должны копироваться.
+
+Тип `bytes.Buffer` содержит срез `[]byte` и, в качестве оптимизации для
+небольших строк, небольшой массив байтов, на который может ссылаться срез. Если
+вы скопируете `Buffer`, срез в копии может стать псевдонимом массива в
+оригинале, вызывая неожиданные эффекты при последующих вызовах методов.
+
+В целом, не копируйте значение типа `T`, если его методы ассоциированы с
+указательным типом, `*T`.
+
+```go
+// Плохо:
+b1 := bytes.Buffer{}
+b2 := b1
+```
+
+Вызов метода, принимающего получателя по значению, может скрыть копирование.
+Когда вы создаете API, вам обычно следует принимать и возвращать типы-указатели,
+если ваши структуры содержат поля, которые не должны копироваться.
+
+Эти примеры допустимы:
+
+```go
+// Хорошо:
+type Record struct {
+ buf bytes.Buffer
+ // другие поля опущены
+}
+
+func New() *Record {...}
+
+func (r *Record) Process(...) {...}
+
+func Consumer(r *Record) {...}
+```
+
+Но эти обычно ошибочны:
+
+```go
+// Плохо:
+type Record struct {
+ buf bytes.Buffer
+ // другие поля опущены
+}
+
+
+func (r Record) Process(...) {...} // Создает копию r.buf
+
+func Consumer(r Record) {...} // Создает копию r.buf
+```
+
+Эти рекомендации также применимы к копированию `sync.Mutex`.
+
+<a id="dont-panic"></a>
+
+### Не используйте panic
+
+<a id="TOC-Don-t-Panic"></a>
+
+Не используйте `panic` для обычной обработки ошибок. Вместо этого используйте
+`error` и множественные возвращаемые значения. См. [раздел Effective Go об
+ошибках].
+
+В `package main` и коде инициализации рассмотрите [`log.Exit`] для ошибок,
+которые должны завершать программу (например, неверная конфигурация), поскольку
+во многих из этих случаев трассировка стека не поможет читателю. Обратите
+внимание, что [`log.Exit`] вызывает [`os.Exit`] и отложенные (deferred) функции
+не будут выполнены.
+
+Для ошибок, указывающих на "невозможные" условия, а именно на ошибки, которые
+всегда должны быть обнаружены при проверке кода и/или тестировании, функция
+может разумно вернуть ошибку или вызвать [`log.Fatal`].
+
+Также см. [когда panic допустим](https://neonxp.ru/pages/gostyleguide/google/best-practices/.md#when-to-panic).
+
+**Примечание:** `log.Fatalf` здесь не из стандартной библиотеки log. См.
+[#logging].
+
+[раздел Effective Go об ошибках]: http://golang.org/doc/effective_go.html#errors
+[`os.Exit`]: https://pkg.go.dev/os#Exit
+
+<a id="must-functions"></a>
+
+### Функции Must
+
+Вспомогательные функции настройки, которые останавливают программу при неудаче,
+следуют соглашению об именовании `MustXYZ` (или `mustXYZ`). Как правило, их
+следует вызывать только на раннем этапе запуска программы, а не для таких вещей,
+как пользовательский ввод, где предпочтительна обычная обработка ошибок Go.
+
+Это часто возникает для функций, вызываемых для инициализации переменных уровня
+пакета исключительно во время [инициализации
+пакета](https://golang.org/ref/spec#Package_initialization) (например,
+[template.Must](https://golang.org/pkg/text/template/#Must) и
+[regexp.MustCompile](https://golang.org/pkg/regexp/#MustCompile)).
+
+```go
+// Хорошо:
+func MustParse(version string) *Version {
+ v, err := Parse(version)
+ if err != nil {
+ panic(fmt.Sprintf("MustParse(%q) = _, %v", version, err))
+ }
+ return v
+}
+
+// "Константа" уровня пакета. Если бы мы хотели использовать `Parse`, нам пришлось бы
+// устанавливать значение в `init`.
+var DefaultVersion = MustParse("1.2.3")
+```
+
+То же соглашение может использоваться во вспомогательных функциях тестирования,
+которые останавливают только текущий тест (используя `t.Fatal`). Такие помощники
+часто удобны при создании тестовых значений, например, в полях структур
+[табличных тестов](#table-driven-tests), поскольку функции, возвращающие ошибки,
+не могут быть напрямую присвоены полю структуры.
+
+```go
+// Хорошо:
+func mustMarshalAny(t *testing.T, m proto.Message) *anypb.Any {
+ t.Helper()
+ any, err := anypb.New(m)
+ if err != nil {
+ t.Fatalf("mustMarshalAny(t, m) = %v; want %v", err, nil)
+ }
+ return any
+}
+
+func TestCreateObject(t *testing.T) {
+ tests := []struct{
+ desc string
+ data *anypb.Any
+ }{
+ {
+ desc: "my test case",
+ // Создание значений непосредственно внутри тестовых случаев табличного теста.
+ data: mustMarshalAny(t, mypb.Object{}),
+ },
+ // ...
+ }
+ // ...
+}
+```
+
+В обоих этих случаях ценность этого шаблона в том, что помощники могут быть
+вызваны в "знаковом" контексте. Этих помощников не следует вызывать в местах,
+где трудно обеспечить перехват ошибки, или в контексте, где ошибка должна быть
+[проверена](#handle-errors) (например, во многих обработчиках запросов). Для
+постоянных входных данных это позволяет тестам легко убедиться, что аргументы
+`Must` корректны, а для непостоянных входных данных это позволяет тестам
+проверять, что ошибки [правильно обработаны или
+распространены](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-handling).
+
+Где функции `Must` используются в тесте, их обычно следует [помечать как
+тестовый помощник](#mark-test-helpers) и вызывать `t.Fatal` при ошибке (см.
+[обработка ошибок во вспомогательных тестовых
+функциях](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-helper-error-handling) для дополнительных
+соображений по использованию этого).
+
+Их не следует использовать, когда [обычная обработка
+ошибок](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-handling) возможна (включая некоторый рефакторинг):
+
+```go
+// Плохо:
+func Version(o *servicepb.Object) (*version.Version, error) {
+ // Вернуть ошибку вместо использования функций Must.
+ v := version.MustParse(o.GetVersionString())
+ return dealiasVersion(v)
+}
+```
+
+<a id="goroutine-lifetimes"></a>
+
+### Время жизни горутин
+
+<a id="TOC-GoroutineLifetimes"></a>
+
+Когда вы порождаете горутины, сделайте ясным, когда или завершаются ли они.
+
+Горутины могут "подтекать", блокируясь на отправке или получении по каналу.
+Сборщик мусора не завершит горутину, заблокированную на канале, даже если
+никакая другая горутина не имеет ссылки на этот канал.
+
+Даже когда горутины не подтекают, оставлять их активными, когда они больше не
+нужны, может вызвать другие тонкие и трудные для диагностики проблемы. Отправка
+в закрытый канал вызывает панику.
+
+```go
+// Плохо:
+ch := make(chan int)
+ch <- 42
+close(ch)
+ch <- 13 // panic
+```
+
+Изменение все еще используемых входных данных "после того, как результат не
+нужен", может привести к гонкам данных. Оставление горутин активными на
+произвольное время может привести к непредсказуемому использованию памяти.
+
+Параллельный код должен быть написан так, чтобы время жизни горутин было
+очевидным. Обычно это означает сохранение кода, связанного с синхронизацией, в
+пределах области видимости функции и вынесение логики в [синхронные функции].
+Если параллелизм все еще не очевиден, важно задокументировать, когда и почему
+горутины завершаются.
+
+Код, следующий лучшим практикам использования контекста, часто помогает
+прояснить это. Обычно это управляется с помощью [`context.Context`]:
+
+```go
+// Хорошо:
+func (w *Worker) Run(ctx context.Context) error {
+ var wg sync.WaitGroup
+ // ...
+ for item := range w.q {
+ // process возвращается самое позднее при отмене контекста.
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ process(ctx, item)
+ }()
+ }
+ // ...
+ wg.Wait() // Предотвращает переживание порожденными горутинами этой функции.
+}
+```
+
+Существуют другие варианты вышеизложенного, использующие простые сигнальные
+каналы, такие как `chan struct{}`, синхронизированные переменные, [условные
+переменные][rethinking-slides] и многое другое. Важная часть заключается в том,
+что конец горутины очевиден для последующих сопровождающих.
+
+В отличие от этого, следующий код небрежен в отношении того, когда его
+порожденные горутины завершаются:
+
+```go
+// Плохо:
+func (w *Worker) Run() {
+ // ...
+ for item := range w.q {
+ // process возвращается, когда завершается, если вообще завершится, возможно, не обрабатывая корректно
+ // переход состояния или завершение самой программы Go.
+ go process(item)
+ }
+ // ...
+}
+```
+
+Этот код может выглядеть нормально, но у него есть несколько скрытых проблем:
+
+* Код, вероятно, имеет неопределенное поведение в продакшене, и программа
+ может не завершиться чисто, даже если операционная система освободит
+ ресурсы.
+
+* Код трудно осмысленно протестировать из-за неопределенного жизненного цикла.
+
+* Код может подтекать ресурсами, как описано выше.
+
+См. также:
+
+* [Никогда не запускайте горутину, не зная, как она остановится][cheney-stop]
+* Переосмысление классических паттернов параллелизма:
+ [слайды][rethinking-slides], [видео][rethinking-video]
+* [Когда программы на Go завершаются]
+* [Соглашения по документации: Контексты]
+
+[синхронные функции]: #synchronous-functions
+[cheney-stop]:
+ https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop
+[rethinking-slides]:
+ https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view
+[rethinking-video]: https://www.youtube.com/watch?v=5zXAHh5tJqQ
+[Когда программы на Go завершаются]: https://changelog.com/gotime/165
+[Соглашения по документации: Контексты]:
+ best-practices.md#documentation-conventions-contexts
+
+<a id="interfaces"></a>
+
+### Интерфейсы
+
+<a id="TOC-Interfaces"></a>
+
+Интерфейсы Go обычно принадлежат пакету, который *потребляет* значения типа
+интерфейса, а не пакету, который *реализует* тип интерфейса. Реализующий пакет
+должен возвращать конкретные (обычно указатель или структура) типы. Таким
+образом, новые методы могут быть добавлены к реализациям без необходимости
+обширного рефакторинга. Подробнее см. [GoTip #49: Принимайте интерфейсы,
+возвращайте конкретные типы].
+
+Не экспортируйте [тестовый дубль (test double)][double types] реализации
+интерфейса из API, который его потребляет. Вместо этого спроектируйте API так,
+чтобы его можно было тестировать с помощью [публичного API] [реальной
+реализации]. См. [GoTip #42: Создание заглушки для тестирования] для
+подробностей. Даже когда нецелесообразно использовать реальную реализацию, может
+не потребоваться вводить интерфейс, полностью покрывающий все методы в реальном
+типе; потребитель может создать интерфейс, содержащий только нужные ему методы,
+как показано в [GoTip #78: Минимально жизнеспособные интерфейсы].
+
+Для тестирования пакетов, использующих Stubby RPC клиенты, используйте реальное
+клиентское соединение. Если реальный сервер не может быть запущен в тесте,
+внутренняя практика Google — получить реальное клиентское соединение с локальным
+[тестовым дублем] с использованием внутреннего пакета rpctest (скоро будет!).
+
+Не определяйте интерфейсы до того, как они используются (см. [TotT: Code
+Health: Eliminate YAGNI Smells][tott-438] ). Без реалистичного примера
+использования слишком сложно увидеть, нужен ли интерфейс вообще, не говоря уже о
+том, какие методы он должен содержать.
+
+Не используйте параметры типа интерфейса, если пользователям пакета не нужно
+передавать различные типы для них.
+
+Не экспортируйте интерфейсы, которые не нужны пользователям пакета.
+
+**TODO:** Написать более подробный документ об интерфейсах и дать на него ссылку
+здесь.
+
+[GoTip #42: Создание заглушки для тестирования]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[GoTip #49: Принимайте интерфейсы, возвращайте конкретные типы]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[GoTip #78: Минимально жизнеспособные интерфейсы]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[реальной реализации]: best-practices#use-real-transports
+[публичному API]:
+ https://abseil.io/resources/swe-book/html/ch12.html#test_via_public_apis
+[double types]:
+ https://abseil.io/resources/swe-book/html/ch13.html#techniques_for_using_test_doubles
+[тестовым дублем]:
+ https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
+[tott-438]:
+ https://testing.googleblog.com/2017/08/code-health-eliminate-yagni-smells.html
+
+```go
+// Хорошо:
+package consumer // consumer.go
+
+type Thinger interface { Thing() bool }
+
+func Foo(t Thinger) string { ... }
+```
+
+```go
+// Хорошо:
+package consumer // consumer_test.go
+
+type fakeThinger struct{ ... }
+func (t fakeThinger) Thing() bool { ... }
+...
+if Foo(fakeThinger{...}) == "x" { ... }
+```
+
+```go
+// Плохо:
+package producer
+
+type Thinger interface { Thing() bool }
+
+type defaultThinger struct{ ... }
+func (t defaultThinger) Thing() bool { ... }
+
+func NewThinger() Thinger { return defaultThinger{ ... } }
+```
+
+```go
+// Хорошо:
+package producer
+
+type Thinger struct{ ... }
+func (t Thinger) Thing() bool { ... }
+
+func NewThinger() Thinger { return Thinger{ ... } }
+```
+
+<a id="generics"></a>
+
+### Дженерики (Generics)
+
+Дженерики (формально называемые "[Параметрами типа]") разрешены там, где они
+удовлетворяют вашим бизнес-требованиям. Во многих приложениях обычный подход с
+использованием существующих языковых возможностей (срезы, карты, интерфейсы и
+т.д.) работает так же хорошо без добавленной сложности, поэтому будьте осторожны
+с преждевременным использованием. См. обсуждение [наименьшего механизма (least
+mechanism)](https://neonxp.ru/pages/gostyleguide/google/guide/#least-mechanism).
+
+При введении экспортируемого API, использующего дженерики, убедитесь, что он
+должным образом задокументирован. Настоятельно рекомендуется включать
+мотивирующие исполняемые [примеры].
+
+Не используйте дженерики только потому, что вы реализуете алгоритм или структуру
+данных, которой не важен тип элементов. Если на практике инстанцируется только
+один тип, начните с того, чтобы ваш код работал с этим типом без использования
+дженериков вообще. Добавить полиморфизм позже будет проще по сравнению с
+удалением абстракции, которая оказывается ненужной.
+
+Не используйте дженерики для создания предметно-ориентированных языков (DSL). В
+частности, воздержитесь от введения фреймворков обработки ошибок, которые могут
+значительно обременять читателей. Вместо этого предпочитайте установленные
+методы [обработки ошибок](#errors). Для тестирования особенно остерегайтесь
+введения [библиотек утверждений (assertion libraries)](#assert) или фреймворков,
+которые приводят к менее полезным [сообщениям об ошибках
+тестов](#useful-test-failures).
+
+В общем:
+
+* [Пишите код, не проектируйте типы]. Из выступления на GopherCon от Роберта
+ Грисемера и Яна Лэнса Тейлора.
+* Если у вас есть несколько типов, которые разделяют полезный объединяющий
+ интерфейс, рассмотрите моделирование решения с использованием этого
+ интерфейса. Дженерики могут не понадобиться.
+* В противном случае вместо того, чтобы полагаться на тип `any` и чрезмерное
+ [переключение типов (type switching)](https://tour.golang.org/methods/16),
+ рассмотрите дженерики.
+
+См. также:
+
+* [Использование дженериков в Go], выступление Яна Лэнса Тейлора
+
+* [Учебник по дженерикам] на сайте Go
+
+[Учебник по дженерикам]: https://go.dev/doc/tutorial/generics
+[Параметрами типа]: https://go.dev/design/43651-type-parameters
+[Использование дженериков в Go]: https://www.youtube.com/watch?v=nr8EpUO9jhw
+[Пишите код, не проектируйте типы]:
+ https://www.youtube.com/watch?v=Pa_e9EeCdy8&t=1250s
+
+<a id="pass-values"></a>
+
+### Передача по значению
+
+<a id="TOC-PassValues"></a>
+
+Не передавайте указатели в качестве аргументов функций только для экономии
+нескольких байтов. Если функция читает свой аргумент `x` только как `*x` на всем
+протяжении, то аргумент не должен быть указателем. Распространенные примеры
+этого включают передачу указателя на строку (`*string`) или указателя на
+значение интерфейса (`*io.Reader`). В обоих случаях само значение имеет
+фиксированный размер и может быть передано напрямую.
+
+Этот совет не применим к большим структурам или даже малым структурам, которые
+могут увеличиться в размере. В частности, сообщения protocol buffer обычно
+следует обрабатывать по указателю, а не по значению. Тип-указатель удовлетворяет
+интерфейсу `proto.Message` (принимаемому `proto.Marshal`, `protocmp.Transform`,
+и т.д.), а сообщения protocol buffer могут быть довольно большими и часто
+увеличиваются со временем.
+
+<a id="receiver-type"></a>
+
+### Тип получателя (Receiver)
+
+<a id="TOC-ReceiverType"></a>
+
+[Получатель метода] может быть передан либо по значению, либо по указателю, как
+если бы он был обычным параметром функции. Выбор между ними основан на том, в
+составе какого [набора методов] метод должен находиться.
+
+[Получатель метода]: https://golang.org/ref/spec#Method_declarations
+[набора методов]: https://golang.org/ref/spec#Method_sets
+
+**Корректность важнее скорости или простоты.** Бывают случаи, когда вы должны
+использовать значение-указатель. В других случаях выбирайте указатели для
+больших типов или в качестве будущего доказательства, если у вас нет хорошего
+понимания того, как будет расти код, и используйте значения для простых [простых
+старых данных].
+
+Список ниже подробно описывает каждый случай:
+
+* Если получатель является срезом и метод не переслаивает (reslice) и не
+ перераспределяет (reallocate) срез, используйте значение, а не указатель.
+
+ ```go
+ // Хорошо:
+ type Buffer []byte
+
+ func (b Buffer) Len() int { return len(b) }
+ ```
+
+* Если методу нужно мутировать получателя, получатель должен быть указателем.
+
+ ```go
+ // Хорошо:
+ type Counter int
+
+ func (c *Counter) Inc() { *c++ }
+
+ // См. https://pkg.go.dev/container/heap.
+ type Queue []Item
+
+ func (q *Queue) Push(x Item) { *q = append([]Item{x}, *q...) }
+ ```
+
+* Если получатель является структурой, содержащей поля, которые [не могут быть
+ безопасно скопированы](#copying), используйте получатель-указатель.
+ Распространенные примеры — [`sync.Mutex`] и другие типы синхронизации.
+
+ ```go
+ // Хорошо:
+ type Counter struct {
+ mu sync.Mutex
+ total int
+ }
+
+ func (c *Counter) Inc() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.total++
+ }
+ ```
+
+ **Совет:** Проверьте [Godoc] типа для информации о том, безопасно ли или
+ небезопасно его копировать.
+
+* Если получатель является "большой" структурой или массивом,
+ получатель-указатель может быть более эффективным. Передача структуры
+ эквивалентна передаче всех ее полей или элементов в качестве аргументов
+ методу. Если это кажется слишком большим для [передачи по
+ значению](#pass-values), указатель — хороший выбор.
+
+* Для методов, которые будут вызываться или выполняться параллельно с другими
+ функциями, которые изменяют получателя, используйте значение, если эти
+ изменения не должны быть видны вашему методу; в противном случае используйте
+ указатель.
+
+* Если получатель является структурой или массивом, любой элемент которого
+ является указателем на что-то, что может быть изменено, предпочтите
+ получателя-указателя, чтобы сделать намерение изменяемости понятным для
+ читателя.
+
+ ```go
+ // Хорошо:
+ type Counter struct {
+ m *Metric
+ }
+
+ func (c *Counter) Inc() {
+ c.m.Add(1)
+ }
+ ```
+
+* Если получатель является [встроенным типом], таким как целое число или
+ строка, который не нужно изменять, используйте значение.
+
+ ```go
+ // Хорошо:
+ type User string
+
+ func (u User) String() { return string(u) }
+ ```
+
+* Если получатель является картой (map), функцией или каналом, используйте
+ значение, а не указатель.
+
+ ```go
+ // Хорошо:
+ // См. https://pkg.go.dev/net/http#Header.
+ type Header map[string][]string
+
+ func (h Header) Add(key, value string) { /* опущено */ }
+ ```
+
+* Если получатель является "маленьким" массивом или структурой, которые
+ естественным образом являются типом значения без изменяемых полей и без
+ указателей, получатель по значению обычно является правильным выбором.
+
+ ```go
+ // Хорошо:
+ // См. https://pkg.go.dev/time#Time.
+ type Time struct { /* опущено */ }
+
+ func (t Time) Add(d Duration) Time { /* опущено */ }
+ ```
+
+* В случае сомнений используйте получатель-указатель.
+
+В качестве общего руководства предпочитайте, чтобы методы для типа были либо все
+указательными методами, либо всеми методами по значению.
+
+**Примечание:** Существует много дезинформации о том, может ли передача значения
+или указателя в функцию повлиять на производительность. Компилятор может выбрать
+передачу указателей на значения в стеке, а также копирование значений в стеке,
+но эти соображения не должны перевешивать читаемость и корректность кода в
+большинстве обстоятельств. Когда производительность действительно имеет
+значение, важно профилировать оба подхода с реалистичным бенчмарком, прежде чем
+решать, что один подход превосходит другой.
+
+[простых старых данных]: https://en.wikipedia.org/wiki/Passive_data_structure
+[`sync.Mutex`]: https://pkg.go.dev/sync#Mutex
+[встроенным типом]: https://pkg.go.dev/builtin
+
+<a id="switch-break"></a>
+
+### `switch` и `break`
+
+<a id="TOC-SwitchBreak"></a>
+
+Не используйте операторы `break` без целевых меток в конце предложений `switch`;
+они избыточны. В отличие от C и Java, предложения `switch` в Go автоматически
+завершаются, и для достижения поведения в стиле C требуется оператор
+`fallthrough`. Используйте комментарий, а не `break`, если хотите прояснить цель
+пустого предложения.
+
+```go
+// Хорошо:
+switch x {
+case "A", "B":
+ buf.WriteString(x)
+case "C":
+ // обрабатывается вне оператора switch
+default:
+ return fmt.Errorf("unknown value: %q", x)
+}
+```
+
+```go
+// Плохо:
+switch x {
+case "A", "B":
+ buf.WriteString(x)
+ break // этот break избыточен
+case "C":
+ break // этот break избыточен
+default:
+ return fmt.Errorf("unknown value: %q", x)
+}
+```
+
+> **Примечание:** Если предложение `switch` находится внутри цикла `for`,
+> использование `break` внутри `switch` не выходит из охватывающего цикла `for`.
+>
+> ```go
+> for {
+> switch x {
+> case "A":
+> break // выходит из switch, не из цикла
+> }
+> }
+> ```
+>
+> Чтобы выйти из охватывающего цикла, используйте метку на операторе `for`:
+>
+> ```go
+> loop:
+> for {
+> switch x {
+> case "A":
+> break loop // выходит из цикла
+> }
+> }
+> ```
+
+<a id="synchronous-functions"></a>
+
+### Синхронные функции
+
+<a id="TOC-SynchronousFunctions"></a>
+
+Синхронные функции возвращают свои результаты напрямую и завершают любые
+обратные вызовы или операции с каналами перед возвратом. Предпочитайте
+синхронные функции асинхронным.
+
+Синхронные функции сохраняют горутины локализованными внутри вызова. Это
+помогает рассуждать об их времени жизни и избегать утечек и гонок данных.
+Синхронные функции также легче тестировать, поскольку вызывающая сторона может
+передать входные данные и проверить выходные без необходимости опроса или
+синхронизации.
+
+При необходимости вызывающая сторона может добавить параллелизм, вызвав функцию
+в отдельной горутине. Однако довольно сложно (иногда невозможно) убрать ненужный
+параллелизм на стороне вызывающего.
+
+См. также:
+
+* "Переосмысление классических паттернов параллелизма", выступление Брайана
+ Миллса: [слайды][rethinking-slides], [видео][rethinking-video]
+
+<a id="type-aliases"></a>
+
+### Псевдонимы типов (Type aliases)
+
+<a id="TOC-TypeAliases"></a>
+
+Используйте *определение типа*, `type T1 T2`, чтобы определить новый тип.
+Используйте [*псевдоним типа*], `type T1 = T2`, чтобы ссылаться на существующий
+тип без определения нового типа. Псевдонимы типов редки; их основное
+использование — помочь в переносе пакетов в новые места исходного кода. Не
+используйте псевдонимы типов, когда это не нужно.
+
+[*псевдоним типа*]: http://golang.org/ref/spec#Type_declarations
+
+<a id="use-percent-q"></a>
+
+### Используйте %q
+
+<a id="TOC-UsePercentQ"></a>
+
+Функции форматирования Go (`fmt.Printf` и т.д.) имеют глагол `%q`, который
+печатает строки внутри двойных кавычек.
+
+```go
+// Хорошо:
+fmt.Printf("value %q looks like English text", someText)
+```
+
+Предпочитайте использование `%q` вместо эквивалентного ручного способа с
+использованием `%s`:
+
+```go
+// Плохо:
+fmt.Printf("value \"%s\" looks like English text", someText)
+// Также избегайте вручную оборачивать строки одинарными кавычками:
+fmt.Printf("value '%s' looks like English text", someText)
+```
+
+Использование `%q` рекомендуется в выводе, предназначенном для людей, где
+входное значение может быть пустым или содержать управляющие символы. Тихую
+пустую строку заметить очень сложно, но `""` явно выделяется как таковая.
+
+<a id="use-any"></a>
+
+### Используйте any
+
+Go 1.18 вводит тип `any` как [псевдоним] для `interface{}`. Поскольку это
+псевдоним, `any` эквивалентен `interface{}` во многих ситуациях, а в других
+легко взаимозаменяем через явное преобразование. Предпочитайте использовать
+`any` в новом коде.
+
+[псевдоним]:
+ https://go.googlesource.com/proposal/+/master/design/18130-type-alias.md
+
+## Общие библиотеки
+
+<a id="flags"></a>
+
+### Флаги
+
+<a id="TOC-Flags"></a>
+
+Программы Go в кодовой базе Google используют внутренний вариант [стандартного
+пакета `flag`]. Он имеет схожий интерфейс, но хорошо взаимодействует с
+внутренними системами Google. Имена флагов в бинарных файлах Go должны
+предпочитать использование подчеркиваний для разделения слов, хотя переменные,
+хранящие значение флага, должны следовать стандартному стилю именования Go
+([mixed caps]). Конкретно, имя флага должно быть в змеином регистре
+(snake_case), а имя переменной должно быть эквивалентным именем в верблюжьем
+регистре (camelCase).
+
+```go
+// Хорошо:
+var (
+ pollInterval = flag.Duration("poll_interval", time.Minute, "Interval to use for polling.")
+)
+```
+
+```go
+// Плохо:
+var (
+ poll_interval = flag.Int("pollIntervalSeconds", 60, "Interval to use for polling in seconds.")
+)
+```
+
+Флаги должны определяться только в `package main` или эквивалентном.
+
+Универсальные пакеты должны настраиваться с использованием API Go, а не путем
+пробивания до интерфейса командной строки; не позволяйте импорту библиотеки
+экспортировать новые флаги как побочный эффект. То есть предпочитайте явные
+аргументы функций или присваивание полей структуры или, что гораздо реже и под
+самым строгим контролем, экспортируемые глобальные переменные. В крайне редком
+случае, когда необходимо нарушить это правило, имя флага должно четко указывать
+пакет, который он настраивает.
+
+Если ваши флаги являются глобальными переменными, поместите их в свою
+собственную группу `var`, следующую после раздела импортов.
+
+Существуют дополнительные обсуждения о лучших практиках создания [сложных CLI] с
+подкомандами.
+
+См. также:
+
+* [Tip of the Week #45: Avoid Flags, Especially in Library Code][totw-45]
+* [Go Tip #10: Configuration Structs and
+ Flags](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
+
+[стандартного пакета `flag`]: https://golang.org/pkg/flag/
+[mixed caps]: guide#mixed-caps
+[сложных CLI]: best-practices#complex-clis
+[totw-45]: https://abseil.io/tips/45
+
+<a id="logging"></a>
+
+### Логирование
+
+Программы Go в кодовой базе Google используют вариант стандартного [`log`]
+пакета. Он имеет схожий, но более мощный интерфейс и хорошо взаимодействует с
+внутренними системами Google. Пакет с открытым исходным кодом этой библиотеки
+доступен как [пакет `glog`], и проекты Google с открытым исходным кодом могут
+использовать его, но в этом руководстве он повсюду упоминается как `log`.
+
+**Примечание:** Для аномальных завершений программы эта библиотека использует
+`log.Fatal` для аварийного завершения с трассировкой стека и `log.Exit` для
+остановки без нее. Здесь нет `log.Panic` функции, как в стандартной библиотеке.
+
+**Совет:** `log.Info(v)` эквивалентно `log.Infof("%v", v)`, и то же самое
+относится к другим уровням логирования. Предпочитайте версию без форматирования,
+когда вам нечего форматировать.
+
+См. также:
+
+* Лучшие практики по [логированию ошибок](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-logging) и
+ [пользовательским уровням детализации (verbosity
+ levels)](https://neonxp.ru/pages/gostyleguide/google/best-practices/#vlog)
+* Когда и как использовать пакет log для [остановки
+ программы](https://neonxp.ru/pages/gostyleguide/google/best-practices/#checks-and-panics)
+
+[`log`]: https://pkg.go.dev/log
+[`log/slog`]: https://pkg.go.dev/log/slog
+[пакет `glog`]: https://pkg.go.dev/github.com/golang/glog
+[`log.Exit`]: https://pkg.go.dev/github.com/golang/glog#Exit
+[`log.Fatal`]: https://pkg.go.dev/github.com/golang/glog#Fatal
+
+<a id="contexts"></a>
+
+### Контексты (Contexts)
+
+<a id="TOC-Contexts"></a>
+
+Значения типа [`context.Context`] несут учетные данные безопасности, информацию
+трассировки, сроки и сигналы отмены через границы API и процессов. В отличие от
+C++ и Java, которые в кодовой базе Google используют локальное хранилище потоков
+(thread-local storage), программы Go передают контексты явно вдоль всей цепочки
+вызовов функций от входящих RPC и HTTP запросов к исходящим запросам.
+
+[`context.Context`]: https://pkg.go.dev/context
+
+При передаче в функцию или метод [`context.Context`] всегда является первым
+параметром.
+
+```go
+func F(ctx context.Context /* другие аргументы */) {}
+```
+
+Исключения:
+
+* В HTTP обработчике, где контекст берется из
+ [`req.Context()`](https://pkg.go.dev/net/http#Request.Context).
+* В потоковых RPC методах, где контекст берется из потока.
+
+ Код, использующий потоковый gRPC, получает контекст из метода `Context()` в
+ сгенерированном типе сервера, который реализует `grpc.ServerStream`. См.
+ [документацию по сгенерированному коду
+ gRPC](https://grpc.io/docs/languages/go/generated-code/).
+
+* В точках входа (entrypoint functions) (см. ниже примеры таких функций)
+ используйте [`context.Background()`] или, для тестов,
+ [`tb.Context()`](https://pkg.go.dev/testing#TB.Context).
+
+ * В бинарных целях: `main`
+ * В общем коде и библиотеках: `init`
+ * В тестах: `TestXXX`, `BenchmarkXXX`, `FuzzXXX`
+
+> **Примечание**: Очень редко код в середине цепочки вызовов требует создания
+> базового контекста самостоятельно с помощью [`context.Background()`]. Всегда
+> предпочитайте брать контекст у вашего вызывающего, если это не неправильный
+> контекст.
+>
+> Вы можете столкнуться с серверными библиотеками (реализация Stubby, gRPC или
+> HTTP во фреймворке сервера Google для Go), которые создают новый объект
+> контекста для каждого запроса. Эти контексты сразу заполняются информацией из
+> входящего запроса, так что при передаче в обработчик запроса прикрепленные
+> значения контекста были распространены к нему через сетевую границу от
+> клиента-вызывающего. Более того, время жизни этих контекстов ограничено
+> временем запроса: когда запрос завершен, контекст отменяется.
+>
+> Если вы не реализуете серверный фреймворк, вы не должны создавать контексты с
+> помощью [`context.Background()`] в библиотечном коде. Вместо этого
+> предпочитайте использование отсоединения контекста (context detachment),
+> которое упоминается ниже, если доступен существующий контекст. Если вы
+> думаете, что вам действительно нужно [`context.Background()`] вне функций
+> точек входа, обсудите это со списком рассылки Google Go style, прежде чем
+> приступать к реализации.
+
+Соглашение о том, что [`context.Context`] идет первым в функциях, также
+применимо к тестовым помощникам.
+
+```go
+// Хорошо:
+func readTestFile(ctx context.Context, t *testing.T, path string) string {}
+```
+
+Не добавляйте поле context в тип структуры. Вместо этого добавьте параметр
+context в каждый метод типа, которому нужно передавать его дальше. Единственное
+исключение — для методов, чья сигнатура должна соответствовать интерфейсу в
+стандартной библиотеке или в сторонней библиотеке вне контроля Google. Такие
+случаи очень редки и должны быть обсуждены со списком рассылки Google Go style
+до реализации и проверки на читаемость.
+
+**Примечание:** Go 1.24 добавил метод [`(testing.TB).Context()`]. В тестах
+предпочитайте использовать [`(testing.TB).Context()`] вместо
+[`context.Background()`] для предоставления начального [`context.Context`],
+используемого тестом. Вспомогательные функции, настройка окружения или тестовых
+дублей и другие функции, вызываемые из тела тестовой функции, которые требуют
+контекст, должны иметь его явно переданным.
+
+[`(testing.TB).Context()`]: https://pkg.go.dev/testing#TB.Context
+
+Код в кодовой базе Google, который должен порождать фоновые операции, способные
+выполняться после отмены родительского контекста, может использовать внутренний
+пакет для отсоединения. Следите за [issue
+#40221](https://github.com/golang/go/issues/40221) для обсуждений об
+альтернативе с открытым исходным кодом.
+
+Поскольку контексты неизменяемы, нормально передавать один и тот же контекст в
+несколько вызовов, которые разделяют один и тот же дедлайн, сигнал отмены,
+учетные данные, родительскую трассировку и т.д.
+
+См. также:
+
+* [Контексты и структуры]
+
+[`context.Background()`]: https://pkg.go.dev/context/#Background
+[Контексты и структуры]: https://go.dev/blog/context-and-structs
+
+<a id="custom-contexts"></a>
+
+#### Пользовательские контексты
+
+Не создавайте пользовательские типы контекстов и не используйте интерфейсы,
+отличные от [`context.Context`], в сигнатурах функций. Для этого правила нет
+исключений.
+
+Представьте, если бы каждая команда имела свой пользовательский контекст. Каждый
+вызов функции из пакета `p` в пакет `q` должен был бы определять, как
+преобразовать `p.Context` в `q.Context`, для всех пар пакетов `p` и `q`. Это
+нецелесообразно и создает ошибки для людей, и это делает автоматизированные
+рефакторинги, добавляющие параметры контекста, почти невозможными.
+
+Если у вас есть данные приложения для передачи, поместите их в параметр, в
+получателе, в глобальных переменных или в значении `Context`, если они
+действительно принадлежат там. Создание вашего собственного типа контекста
+неприемлемо, поскольку оно подрывает способность команды Go заставить программы
+Go правильно работать в продакшене.
+
+<a id="crypto-rand"></a>
+
+### crypto/rand
+
+<a id="TOC-CryptoRand"></a>
+
+Не используйте пакет `math/rand` для генерации ключей, даже одноразовых. Если не
+задано начальное значение (seed), генератор полностью предсказуем. При задании
+начального значения `time.Nanoseconds()` существует всего несколько бит
+энтропии. Вместо этого используйте `Reader` из `crypto/rand`, и если вам нужен
+текст, выводите в шестнадцатеричном или base64.
+
+```go
+// Хорошо:
+import (
+ "crypto/rand"
+ // "encoding/base64"
+ // "encoding/hex"
+ "fmt"
+
+ // ...
+)
+
+func Key() string {
+ buf := make([]byte, 16)
+ if _, err := rand.Read(buf); err != nil {
+ log.Fatalf("Out of randomness, should never happen: %v", err)
+ }
+ return fmt.Sprintf("%x", buf)
+ // или hex.EncodeToString(buf)
+ // или base64.StdEncoding.EncodeToString(buf)
+}
+```
+
+**Примечание:** `log.Fatalf` здесь не из стандартной библиотеки log. См.
+[#logging].
+
+<a id="useful-test-failures"></a>
+
+## Полезные сообщения об ошибках тестов
+
+<a id="TOC-UsefulTestFailures"></a>
+
+Должна быть возможность диагностировать неудачу теста без чтения исходного кода
+теста. Тесты должны завершаться неудачей с полезными сообщениями,
+детализирующими:
+
+* Что вызвало неудачу
+* Какие входные данные привели к ошибке
+* Фактический результат
+* Что ожидалось
+
+Конкретные соглашения для достижения этой цели изложены ниже.
+
+<a id="assert"></a>
+
+### Библиотеки утверждений (Assertion libraries)
+
+<a id="TOC-Assert"></a>
+
+Не создавайте "библиотеки утверждений" в качестве помощников для тестирования.
+
+Библиотеки утверждений — это библиотеки, которые пытаются объединить проверку и
+формирование сообщений об ошибках в тесте (хотя те же ловушки могут применяться
+и к другим тестовым помощникам). Подробнее о различии между тестовыми
+помощниками и библиотеками утверждений см. [лучшие
+практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-functions).
+
+```go
+// Плохо:
+var obj BlogPost
+
+assert.IsNotNil(t, "obj", obj)
+assert.StringEq(t, "obj.Type", obj.Type, "blogPost")
+assert.IntEq(t, "obj.Comments", obj.Comments, 2)
+assert.StringNotEq(t, "obj.Body", obj.Body, "")
+```
+
+Библиотеки утверждений имеют тенденцию либо останавливать тест рано (если
+`assert` вызывает `t.Fatalf` или `panic`), либо опускать соответствующую
+информацию о том, что тест получил правильно:
+
+```go
+// Плохо:
+package assert
+
+func IsNotNil(t *testing.T, name string, val any) {
+ if val == nil {
+ t.Fatalf("Data %s = nil, want not nil", name)
+ }
+}
+
+func StringEq(t *testing.T, name, got, want string) {
+ if got != want {
+ t.Fatalf("Data %s = %q, want %q", name, got, want)
+ }
+}
+```
+
+Сложные функции утверждений часто не предоставляют [полезные сообщения об
+ошибках] и контекст, существующий внутри тестовой функции. Слишком много функций
+утверждений и библиотек приводит к фрагментированному опыту разработчика: какую
+библиотеку утверждений использовать, какой стиль формата вывода она должна
+выдавать и т.д.? Фрагментация создает ненужную путаницу, особенно для
+сопровождающих библиотек и авторов крупномасштабных изменений, которые отвечают
+за исправление потенциальных поломок вниз по течению. Вместо создания
+предметно-ориентированного языка для тестирования используйте сам Go.
+
+Библиотеки утверждений часто выносят сравнения и проверки равенства.
+Предпочитайте использование стандартных библиотек, таких как [`cmp`] и [`fmt`],
+вместо этого:
+
+```go
+// Хорошо:
+var got BlogPost
+
+want := BlogPost{
+ Comments: 2,
+ Body: "Hello, world!",
+}
+
+if !cmp.Equal(got, want) {
+ t.Errorf("Blog post = %v, want = %v", got, want)
+}
+```
+
+Для более предметно-ориентированных помощников сравнения предпочитайте
+возвращать значение или ошибку, которые можно использовать в сообщении об ошибке
+теста, вместо передачи `*testing.T` и вызова его методов сообщения об ошибках:
+
+```go
+// Хорошо:
+func postLength(p BlogPost) int { return len(p.Body) }
+
+func TestBlogPost_VeritableRant(t *testing.T) {
+ post := BlogPost{Body: "I am Gunnery Sergeant Hartman, your senior drill instructor."}
+
+ if got, want := postLength(post), 60; got != want {
+ t.Errorf("Length of post = %v, want %v", got, want)
+ }
+}
+```
+
+**Лучшая практика:** Если бы `postLength` была нетривиальной, было бы разумно
+тестировать ее напрямую, независимо от любых тестов, которые ее используют.
+
+См. также:
+
+* [Сравнение равенства и разницы (diffs)](#types-of-equality)
+* [Печать разниц (diffs)](#print-diffs)
+* Подробнее о различии между тестовыми помощниками и помощниками утверждений
+ см. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-functions)
+* Раздел [Go FAQ] о [фреймворках тестирования] и их мнение об их отсутствии
+
+[полезные сообщения об ошибках]: #useful-test-failures
+[`fmt`]: https://golang.org/pkg/fmt/
+[помеча как тестовый помощник]: #mark-test-helpers
+[Go FAQ]: https://go.dev/doc/faq
+[фреймворках тестирования]: https://go.dev/doc/faq#testing_framework
+
+<a id="identify-the-function"></a>
+
+### Идентификация функции
+
+В большинстве тестов сообщения об ошибках должны включать имя функции, которая
+завершилась неудачей, даже если это кажется очевидным из имени тестовой функции.
+Конкретно, ваше сообщение об ошибке должно быть `YourFunc(%v) = %v, want %v`
+вместо просто `got %v, want %v`.
+
+<a id="identify-the-input"></a>
+
+### Идентификация входных данных
+
+В большинстве тестов сообщения об ошибках должны включать входные данные
+функции, если они кратки. Если соответствующие свойства входных данных не
+очевидны (например, потому что входные данные большие или непрозрачные), вы
+должны называть свои тестовые случаи описанием того, что тестируется, и выводить
+это описание как часть вашего сообщения об ошибке.
+
+<a id="got-before-want"></a>
+
+### Got перед want
+
+Вывод тестов должен включать фактическое значение, которое вернула функция,
+перед печатью значения, которое ожидалось. Стандартный формат для вывода тестов
+это `YourFunc(%v) = %v, want %v`. Там, где вы бы написали "actual" и "expected",
+предпочитайте использовать слова "got" и "want" соответственно.
+
+Для разниц (diffs) направленность менее очевидна, и поэтому важно включить ключ,
+чтобы помочь в интерпретации неудачи. См. [раздел о печати разниц]. Независимо
+от порядка diff, который вы используете в своих сообщениях об ошибках, вы должны
+явно указать его как часть сообщения об ошибке, поскольку существующий код
+непоследователен в порядке.
+
+[раздел о печати разниц]: #print-diffs
+
+<a id="compare-full-structures"></a>
+
+### Полное сравнение структур
+
+Если ваша функция возвращает структуру (или любой тип данных с несколькими
+полями, такой как срезы, массивы и карты), избегайте написания тестового кода,
+который выполняет ручное поле-за-полем сравнение структуры. Вместо этого
+сконструируйте данные, которые вы ожидаете, что ваша функция вернет, и
+сравнивайте напрямую с помощью [глубокого сравнения (deep comparison)].
+
+**Примечание:** Это не применяется, если ваши данные содержат нерелевантные
+поля, которые затемняют намерение теста.
+
+Если вашу структуру нужно сравнивать на приблизительное (или эквивалентное
+семантическое) равенство или она содержит поля, которые не могут быть сравнены
+на равенство (например, если одно из полей — `io.Reader`), настройка сравнения
+[`cmp.Diff`] или [`cmp.Equal`] с опциями [`cmpopts`], такими как
+[`cmpopts.IgnoreInterfaces`], может удовлетворить ваши потребности
+([пример](https://play.golang.org/p/vrCUNVfxsvF)).
+
+Если ваша функция возвращает несколько возвращаемых значений, вам не нужно
+оборачивать их в структуру перед сравнением. Просто сравните возвращаемые
+значения по отдельности и выведите их.
+
+```go
+// Хорошо:
+val, multi, tail, err := strconv.UnquoteChar(`\"Fran & Freddie's Diner\"`, '"')
+if err != nil {
+ t.Fatalf(...)
+}
+if val != `"` {
+ t.Errorf(...)
+}
+if multi {
+ t.Errorf(...)
+}
+if tail != `Fran & Freddie's Diner"` {
+ t.Errorf(...)
+}
+```
+
+[глубокого сравнения (deep comparison)]: #types-of-equality
+[`cmpopts`]: https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts
+[`cmpopts.IgnoreInterfaces`]:
+ https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#IgnoreInterfaces
+
+<a id="compare-stable-results"></a>
+
+### Сравнение стабильных результатов
+
+Избегайте сравнения результатов, которые могут зависеть от стабильности вывода
+пакета, которым вы не владеете. Вместо этого тест должен сравнивать семантически
+релевантную информацию, которая стабильна и устойчива к изменениям в
+зависимостях. Для функциональности, возвращающей форматированную строку или
+сериализованные байты, как правило, небезопасно предполагать, что вывод
+стабилен.
+
+Например, [`json.Marshal`] может измениться (и менялся в прошлом) в конкретных
+байтах, которые он выдает. Тесты, выполняющие строковое равенство на JSON
+строке, могут сломаться, если пакет `json` изменит способ сериализации байтов.
+Вместо этого более надежный тест будет анализировать содержимое JSON строки и
+убеждаться, что оно семантически эквивалентно некоторой ожидаемой структуре
+данных.
+
+[`json.Marshal`]: https://golang.org/pkg/encoding/json/#Marshal
+
+<a id="keep-going"></a>
+
+### Продолжать выполнение
+
+Тесты должны продолжать работать как можно дольше, даже после неудачи, чтобы
+вывести все неудачные проверки за один запуск. Таким образом, разработчик,
+который исправляет падающий тест, не должен перезапускать тест после исправления
+каждой ошибки, чтобы найти следующую ошибку.
+
+Предпочитайте вызов `t.Error` вместо `t.Fatal` для сообщения о несоответствии.
+При сравнении нескольких различных свойств вывода функции используйте `t.Error`
+для каждого из этих сравнений.
+
+```go
+// Хорошо:
+gotMean, gotVariance, err := MyDistribution(input)
+if err != nil {
+ t.Fatalf("MyDistribution(%v) returned unexpected error: %v", input, err)
+}
+if diff := cmp.Diff(wantMean, gotMean); diff != "" {
+ t.Errorf("MyDistribution(%v) returned unexpected difference in mean value (-want +got):\n%s", input, diff)
+}
+if diff := cmp.Diff(wantVariance, gotVariance); diff != "" {
+ t.Errorf("MyDistribution(%v) returned unexpected difference in variance value (-want +got):\n%s", input, diff)
+}
+```
+
+Вызов `t.Fatal` в первую очередь полезен для сообщения о неожиданном условии
+(таком как ошибка или несоответствие вывода), когда последующие неудачи были бы
+бессмысленны или даже вводили бы исследователя в заблуждение. Обратите внимание,
+как код ниже вызывает `t.Fatalf` и *затем* `t.Errorf`:
+
+```go
+// Хорошо:
+gotEncoded := Encode(input)
+if gotEncoded != wantEncoded {
+ t.Fatalf("Encode(%q) = %q, want %q", input, gotEncoded, wantEncoded)
+ // Не имеет смысла декодировать из неожиданного закодированного ввода.
+}
+gotDecoded, err := Decode(gotEncoded)
+if err != nil {
+ t.Fatalf("Decode(%q) returned unexpected error: %v", gotEncoded, err)
+}
+if gotDecoded != input {
+ t.Errorf("Decode(%q) = %q, want %q", gotEncoded, gotDecoded, input)
+}
+```
+
+Для табличных тестов рассмотрите использование подтестов и используйте `t.Fatal`
+вместо `t.Error` и `continue`. См. также [GoTip #25: Subtests: Making Your Tests
+Lean](https://google.github.io/styleguide/go/index.html#gotip).
+
+**Лучшая практика:** Для дальнейшего обсуждения о том, когда следует
+использовать `t.Fatal`, см. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#t-fatal).
+
+<a id="types-of-equality"></a>
+
+### Сравнение равенства и разницы (diffs)
+
+Оператор `==` вычисляет равенство, используя [определенные языком сравнения].
+Скалярные значения (числа, булевы и т.д.) сравниваются на основе их значений, но
+только некоторые структуры и интерфейсы можно сравнивать таким образом.
+Указатели сравниваются на основе того, указывают ли они на одну и ту же
+переменную, а не на основе равенства значений, на которые они указывают.
+
+Пакет [`cmp`] может сравнивать более сложные структуры данных, не обрабатываемые
+адекватно `==`, такие как срезы. Используйте [`cmp.Equal`] для сравнения
+равенства и [`cmp.Diff`] для получения читаемой человеком разницы между
+объектами.
+
+```go
+// Хорошо:
+want := &Doc{
+ Type: "blogPost",
+ Comments: 2,
+ Body: "This is the post body.",
+ Authors: []string{"isaac", "albert", "emmy"},
+}
+if !cmp.Equal(got, want) {
+ t.Errorf("AddPost() = %+v, want %+v", got, want)
+}
+```
+
+Как универсальная библиотека сравнения, `cmp` может не знать, как сравнивать
+определенные типы. Например, она может сравнивать сообщения protobuf только если
+передана опция [`protocmp.Transform`].
+
+<!-- The order of want and got here is deliberate. See comment in #print-diffs. -->
+
+```go
+// Хорошо:
+if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
+ t.Errorf("Foo() returned unexpected difference in protobuf messages (-want +got):\n%s", diff)
+}
+```
+
+Хотя пакет `cmp` не является частью стандартной библиотеки Go, он поддерживается
+командой Go и должен обеспечивать стабильные результаты равенства с течением
+времени. Он настраивается пользователем и должен удовлетворять большинству
+потребностей в сравнении.
+
+[определенные языком сравнения]: http://golang.org/ref/spec#Comparison_operators
+[`cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
+[`cmp.Equal`]: https://pkg.go.dev/github.com/google/go-cmp/cmp#Equal
+[`cmp.Diff`]: https://pkg.go.dev/github.com/google/go-cmp/cmp#Diff
+[`protocmp.Transform`]:
+ https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp#Transform
+
+Существующий код может использовать следующие более старые библиотеки и может
+продолжать использовать их для согласованности:
+
+* [`pretty`] создает эстетически приятные отчеты о различиях. Однако он
+ довольно намеренно считает значения, имеющие одинаковое визуальное
+ представление, равными. В частности, `pretty` не улавливает различия между
+ nil срезами и пустыми, не чувствителен к разным реализациям интерфейсов с
+ идентичными полями, и возможно использовать вложенную карту в качестве
+ основы для сравнения со значением структуры. Он также сериализует все
+ значение в строку перед созданием diff, и, как таковой, не является хорошим
+ выбором для сравнения больших значений. По умолчанию он сравнивает
+ неэкспортируемые поля, что делает его чувствительным к изменениям в деталях
+ реализации в ваших зависимостях. По этой причине нецелесообразно
+ использовать `pretty` на сообщениях protobuf.
+
+[`pretty`]: https://pkg.go.dev/github.com/kylelemons/godebug/pretty
+
+Предпочитайте использовать `cmp` для нового кода, и стоит рассмотреть обновление
+старого кода для использования `cmp`, где и когда это практично.
+
+Старый код может использовать функцию стандартной библиотеки `reflect.DeepEqual`
+для сравнения сложных структур. `reflect.DeepEqual` не следует использовать для
+проверки равенства, так как она чувствительна к изменениям в неэкспортированных
+полях и другим деталям реализации. Код, использующий `reflect.DeepEqual`, должен
+быть обновлен до одной из вышеупомянутых библиотек.
+
+**Примечание:** Пакет `cmp` предназначен для тестирования, а не для
+использования в продакшене. Как таковой, он может вызывать панику, когда
+подозревает, что сравнение выполнено неправильно, чтобы дать инструкции
+пользователям, как улучшить тест, чтобы он был менее хрупким. Учитывая
+склонность cmp к панике, он непригоден для кода, который используется в
+продакшене, поскольку ложная паника может быть фатальной.
+
+<a id="level-of-detail"></a>
+
+### Уровень детализации
+
+Обычное сообщение об ошибке, подходящее для большинства тестов Go, это
+`YourFunc(%v) = %v, want %v`. Однако бывают случаи, которые могут потребовать
+больше или меньше деталей:
+
+* Тесты, выполняющие сложные взаимодействия, должны также описывать
+ взаимодействия. Например, если один и тот же `YourFunc` вызывается
+ несколько раз, идентифицируйте, какой вызов провалил тест. Если важно знать
+ любое дополнительное состояние системы, включите это в вывод ошибки (или
+ хотя бы в логи).
+* Если данные представляют собой сложную структуру со значительным шаблонным
+ кодом, допустимо описать только важные части в сообщении, но не чрезмерно
+ затемняйте данные.
+* Ошибки настройки не требуют такого же уровня детализации. Если тестовый
+ помощник заполняет таблицу Spanner, но Spanner был выключен, вам, вероятно,
+ не нужно включать, какие тестовые входные данные вы собирались сохранить в
+ базе данных. `t.Fatalf("Setup: Failed to set up test database: %s", err)`
+ обычно достаточно полезно, чтобы решить проблему.
+
+**Совет:** Заставьте ваш режим отказа срабатывать во время разработки.
+Просмотрите, как выглядит сообщение об ошибке, и может ли сопровождающий
+эффективно справиться с неудачей.
+
+Существуют некоторые методы для ясного воспроизведения тестовых входов и
+выходов:
+
+* При выводе строковых данных [`%q` часто полезен](#use-percent-q) для
+ выделения важности значения и более легкого обнаружения плохих значений.
+* При выводе (маленьких) структур `%+v` может быть полезнее, чем `%v`.
+* Когда проверка больших значений завершается неудачей, [печать разницы
+ (diff)](#print-diffs) может облегчить понимание неудачи.
+
+<a id="print-diffs"></a>
+
+### Печать разниц (diffs)
+
+Если ваша функция возвращает большой вывод, читающему может быть трудно найти
+различия, когда ваш тест проваливается. Вместо печати и возвращенного значения,
+и ожидаемого значения создайте diff.
+
+Для вычисления разниц для таких значений предпочтительна `cmp.Diff`, особенно
+для новых тестов и нового кода, но могут использоваться и другие инструменты.
+См. [типы равенства] для рекомендаций относительно сильных и слабых сторон
+каждой функции.
+
+* [`cmp.Diff`]
+
+* [`pretty.Compare`]
+
+Вы можете использовать пакет [`diff`] для сравнения многострочных строк или
+списков строк. Вы можете использовать это как строительный блок для других видов
+разниц.
+
+[типы равенства]: #types-of-equality
+[`diff`]: https://pkg.go.dev/github.com/kylelemons/godebug/diff
+[`pretty.Compare`]:
+ https://pkg.go.dev/github.com/kylelemons/godebug/pretty#Compare
+
+Добавьте некоторый текст в ваше сообщение об ошибке, объясняющий направление
+diff.
+
+<!--
+The reversed order of want and got in these examples is intentional, as this is
+the prevailing order across the Google codebase. The lack of a stance on which
+order to use is also intentional, as there is no consensus which is
+"most readable."
+
+
+-->
+
+* Что-то вроде `diff (-want +got)` хорошо, когда вы используете пакеты `cmp`,
+ `pretty` и `diff` (если вы передаете `(want, got)` в функцию), потому что
+ `-` и `+`, которые вы добавляете в строку формата, будут соответствовать `-`
+ и `+`, которые фактически появляются в начале строк diff. Если вы передаете
+ `(got, want)` в вашу функцию, правильным ключом будет `(-got +want)` вместо
+ этого.
+
+* Пакет `messagediff` использует другой формат вывода, поэтому сообщение `diff
+ (want -> got)` уместно, когда вы его используете (если вы передаете `(want,
+ got)` в функцию), потому что направление стрелки будет соответствовать
+ направлению стрелки в строках "modified".
+
+Diff будет занимать несколько строк, поэтому вы должны напечатать новую строку
+перед печатью diff.
+
+<a id="test-error-semantics"></a>
+
+### Тестирование семантики ошибок
+
+Когда модульный тест выполняет строковые сравнения или использует обычный `cmp`
+для проверки того, что возвращаются определенные типы ошибок для определенных
+входных данных, вы можете обнаружить, что ваши тесты становятся хрупкими, если
+какое-либо из этих сообщений об ошибках будет переформулировано в будущем.
+Поскольку это может превратить ваш модульный тест в детектор изменений (см.
+[TotT: Change-Detector Tests Considered Harmful][tott-350] ), не используйте
+строковое сравнение для проверки того, какой тип ошибки возвращает ваша функция.
+Однако допустимо использовать строковые сравнения для проверки того, что
+сообщения об ошибках, исходящие из тестируемого пакета, удовлетворяют
+определенным свойствам, например, что оно включает имя параметра.
+
+Значения ошибок в Go обычно имеют компонент, предназначенный для человеческого
+восприятия, и компонент, предназначенный для семантического управления потоком.
+Тесты должны стремиться тестировать только семантическую информацию, которую
+можно надежно наблюдать, а не отображаемую информацию, предназначенную для
+отладки человеком, поскольку последняя часто подвержена будущим изменениям.
+Рекомендации по созданию ошибок со смысловым значением см. [лучшие практики
+относительно ошибок](https://neonxp.ru/pages/gostyleguide/google/best-practices/#error-handling). Если ошибка с недостаточной
+семантической информацией исходит из зависимости вне вашего контроля,
+рассмотрите возможность создания отчета об ошибке (bug) владельцу, чтобы помочь
+улучшить API, а не полагаться на разбор сообщения об ошибке.
+
+В рамках модульных тестов часто важно только то, произошла ли ошибка или нет.
+Если да, то достаточно проверить только, была ли ошибка ненулевой, когда вы
+ожидали ошибки. Если вы хотите проверить, что ошибка семантически соответствует
+какой-то другой ошибке, рассмотрите использование [`errors.Is`] или `cmp` с
+[`cmpopts.EquateErrors`].
+
+> **Примечание:** Если тест использует [`cmpopts.EquateErrors`], но все его
+> значения `wantErr` либо `nil`, либо `cmpopts.AnyError`, то использование `cmp`
+> является [ненужным механизмом](https://neonxp.ru/pages/gostyleguide/google/guide/#least-mechanism). Упростите код, сделав
+> поле `want` типа `bool`. Затем вы можете использовать простое сравнение с
+> `!=`.
+>
+> ```go
+> // Хорошо:
+> err := f(test.input)
+> if gotErr := err != nil; gotErr != test.wantErr {
+> t.Errorf("f(%q) = %v, want error presence = %v", test.input, err, test.wantErr)
+> }
+> ```
+
+См. также [GoTip #13: Designing Errors for
+Checking](https://google.github.io/styleguide/go/index.html#gotip).
+
+[tott-350]:
+ https://testing.googleblog.com/2015/01/testing-on-toilet-change-detector-tests.html
+[`cmpopts.EquateErrors`]:
+ https://pkg.go.dev/github.com/google/go-cmp/cmp/cmpopts#EquateErrors
+[`errors.Is`]: https://pkg.go.dev/errors#Is
+
+<a id="test-structure"></a>
+
+## Структура тестов
+
+<a id="subtests"></a>
+
+### Подтесты (Subtests)
+
+Стандартная библиотека тестирования Go предоставляет возможность [определять
+подтесты]. Это позволяет гибкость в настройке и очистке, управлении
+параллелизмом и фильтрации тестов. Подтесты могут быть полезны (особенно для
+табличных тестов), но их использование не обязательно. См. также [пост в блоге
+Go о подтестах](https://blog.golang.org/subtests).
+
+Подтесты не должны зависеть от выполнения других случаев для успеха или
+начального состояния, поскольку ожидается, что подтесты могут быть запущены
+индивидуально с использованием флагов `go test -run` или выражений [фильтра
+тестов] Bazel.
+
+[определять подтесты]:
+ https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks
+[фильтра тестов]: https://bazel.build/docs/user-manual#test-filter
+
+<a id="subtest-names"></a>
+
+#### Имена подтестов
+
+Назовите ваш подтест так, чтобы он был читаем в выводе теста и полезен в
+командной строке для пользователей фильтрации тестов. Когда вы используете
+`t.Run` для создания подтеста, первый аргумент используется как описательное имя
+для теста. Чтобы гарантировать, что результаты тестов читаемы для людей,
+читающих логи, выбирайте имена подтестов, которые останутся полезными и
+читаемыми после экранирования. Думайте об именах подтестов скорее как об
+идентификаторе функции, чем о прозаическом описании.
+
+Тестовый раннер заменяет пробелы подчеркиваниями и экранирует непечатаемые
+символы. Чтобы обеспечить точную корреляцию между логами тестов и исходным
+кодом, рекомендуется избегать использования этих символов в именах подтестов.
+
+Если ваши тестовые данные выигрывают от более длинного описания, рассмотрите
+возможность поместить описание в отдельное поле (возможно, для печати с помощью
+`t.Log` или рядом с сообщениями об ошибках).
+
+Подтесты могут быть запущены индивидуально с использованием флагов [Go test
+runner] или Bazel [фильтра тестов], поэтому выбирайте описательные имена,
+которые также легко набирать.
+
+> **Предупреждение:** Символы слеша особенно недружелюбны в именах подтестов,
+> поскольку они имеют [особое значение для фильтров тестов].
+>
+> > ```sh
+> > # Плохо:
+> > # Предполагая TestTime и t.Run("America/New_York", ...)
+> > bazel test :mytest --test_filter="Time/New_York" # Ничего не запускает!
+> > bazel test :mytest --test_filter="Time//New_York" # Правильно, но неудобно.
+> > ```
+
+Чтобы [идентифицировать входные данные] функции, включите их в сообщения об
+ошибках теста, где они не будут экранированы тестовым раннером.
+
+```go
+// Хорошо:
+func TestTranslate(t *testing.T) {
+ data := []struct {
+ name, desc, srcLang, dstLang, srcText, wantDstText string
+ }{
+ {
+ name: "hu=en_bug-1234",
+ desc: "regression test following bug 1234. contact: cleese",
+ srcLang: "hu",
+ srcText: "cigarettát és egy öngyújtót kérek",
+ dstLang: "en",
+ wantDstText: "cigarettes and a lighter please",
+ }, // ...
+ }
+ for _, d := range data {
+ t.Run(d.name, func(t *testing.T) {
+ got := Translate(d.srcLang, d.dstLang, d.srcText)
+ if got != d.wantDstText {
+ t.Errorf("%s\nTranslate(%q, %q, %q) = %q, want %q",
+ d.desc, d.srcLang, d.dstLang, d.srcText, got, d.wantDstText)
+ }
+ })
+ }
+}
+```
+
+Вот несколько примеров того, чего следует избегать:
+
+```go
+// Плохо:
+// Слишком многословно.
+t.Run("check that there is no mention of scratched records or hovercrafts", ...)
+// Слеши вызывают проблемы в командной строке.
+t.Run("AM/PM confusion", ...)
+```
+
+См. также [Go Tip #117: Subtest
+Names](https://google.github.io/styleguide/go/index.html#gotip).
+
+[Go test runner]: https://golang.org/cmd/go/#hdr-Testing_flags
+[идентифицировать входные данные]: #identify-the-input
+[особое значение для фильтров тестов]:
+ https://blog.golang.org/subtests#:~:text=Perhaps%20a%20bit,match%20any%20tests
+
+<a id="table-driven-tests"></a>
+
+### Табличные тесты
+
+Используйте табличные тесты, когда множество различных тестовых случаев могут
+быть протестированы с помощью сходной тестовой логики.
+
+* При тестировании того, равен ли фактический вывод функции ожидаемому выводу.
+ Например, множество [тестов `fmt.Sprintf`] или минимальный фрагмент ниже.
+* При тестировании того, соответствуют ли выводы функции всегда одному и тому
+ же набору инвариантов. Например, [тесты для `net.Dial`].
+
+[тестов `fmt.Sprintf`]:
+ https://cs.opensource.google/go/go/+/master:src/fmt/fmt_test.go
+[тестов для `net.Dial`]:
+ https://cs.opensource.google/go/go/+/master:src/net/dial_test.go;l=318;drc=5b606a9d2b7649532fe25794fa6b99bd24e7697c
+
+Вот минимальная структура табличного теста. При необходимости вы можете
+использовать другие имена или добавить дополнительные средства, такие как
+подтесты или функции настройки и очистки. Всегда помните о [полезных сообщениях
+об ошибках тестов](#useful-test-failures).
+
+```go
+// Хорошо:
+func TestCompare(t *testing.T) {
+ compareTests := []struct {
+ a, b string
+ want int
+ }{
+ {"", "", 0},
+ {"a", "", 1},
+ {"", "a", -1},
+ {"abc", "abc", 0},
+ {"ab", "abc", -1},
+ {"abc", "ab", 1},
+ {"x", "ab", 1},
+ {"ab", "x", -1},
+ {"x", "a", 1},
+ {"b", "x", -1},
+ // тестирование chunked-реализации runtime·memeq
+ {"abcdefgh", "abcdefgh", 0},
+ {"abcdefghi", "abcdefghi", 0},
+ {"abcdefghi", "abcdefghj", -1},
+ }
+
+ for _, test := range compareTests {
+ got := Compare(test.a, test.b)
+ if got != test.want {
+ t.Errorf("Compare(%q, %q) = %v, want %v", test.a, test.b, got, test.want)
+ }
+ }
+}
+```
+
+**Примечание**: Сообщения об ошибках в приведенном выше примере соответствуют
+рекомендациям [идентифицировать функцию](#identify-the-function) и
+[идентифицировать входные данные](#identify-the-input). Нет необходимости
+[идентифицировать строку численно](#table-tests-identifying-the-row).
+
+Когда некоторые тестовые случаи нужно проверять с помощью логики, отличной от
+других тестовых случаев, уместно написать несколько тестовых функций, как
+объясняется в [GoTip #50: Disjoint Table Tests].
+
+Когда дополнительные тестовые случаи просты (например, базовая проверка ошибок)
+и не вводят условный поток кода в теле цикла табличного теста, допустимо
+включить этот случай в существующий тест, хотя будьте осторожны, используя такую
+логику. То, что начинается просто сегодня, может органически вырасти во что-то
+неподдерживаемое.
+
+Например:
+
+```go
+func TestDivide(t *testing.T) {
+ tests := []struct {
+ dividend, divisor int
+ want int
+ wantErr bool
+ }{
+ {
+ dividend: 4,
+ divisor: 2,
+ want: 2,
+ },
+ {
+ dividend: 10,
+ divisor: 2,
+ want: 5,
+ },
+ {
+ dividend: 1,
+ divisor: 0,
+ wantErr: true,
+ },
+ }
+
+ for _, test := range tests {
+ got, err := Divide(test.dividend, test.divisor)
+ if (err != nil) != test.wantErr {
+ t.Errorf("Divide(%d, %d) error = %v, want error presence = %t", test.dividend, test.divisor, err, test.wantErr)
+ }
+
+ // В этом примере мы тестируем значение результата только когда тестируемая функция не завершилась неудачей.
+ if err != nil {
+ continue
+ }
+
+ if got != test.want {
+ t.Errorf("Divide(%d, %d) = %d, want %d", test.dividend, test.divisor, got, test.want)
+ }
+ }
+}
+```
+
+Более сложная логика в вашем тестовом коде, например сложная проверка ошибок на
+основе условных различий в настройке теста (часто основанных на входных
+параметрах табличного теста), может быть [трудной для
+понимания](https://neonxp.ru/pages/gostyleguide/google/guide/#maintainability), когда каждая запись в таблице имеет
+специализированную логику на основе входных данных. Если тестовые случаи имеют
+разную логику, но идентичную настройку, последовательность
+[подтестов](#subtests) внутри одной тестовой функции может быть более читаемой.
+Тестовый помощник также может быть полезен для упрощения настройки теста с целью
+сохранения читаемости тела теста.
+
+Вы можете комбинировать табличные тесты с несколькими тестовыми функциями.
+Например, при тестировании того, что вывод функции точно соответствует
+ожидаемому выводу и что функция возвращает ненулевую ошибку для неверного ввода,
+лучшим подходом является написание двух отдельных табличных тестовых функций:
+одну для обычных не-ошибочных выводов, и одну для ошибочных выводов.
+
+[GoTip #50: Disjoint Table Tests]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="table-tests-data-driven"></a>
+
+#### Тестовые случаи на основе данных
+
+Строки табличных тестов иногда могут становиться сложными, причем значения строк
+диктуют условное поведение внутри тестового случая. Дополнительная ясность от
+дублирования между тестовыми случаями необходима для читаемости.
+
+```go
+// Хорошо:
+type decodeCase struct {
+ name string
+ input string
+ output string
+ err error
+}
+
+func TestDecode(t *testing.T) {
+ // setupCodex медленный, так как создает реальный Codex для теста.
+ codex := setupCodex(t)
+
+ var tests []decodeCase // строки опущены для краткости
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ output, err := Decode(test.input, codex)
+ if got, want := output, test.output; got != want {
+ t.Errorf("Decode(%q) = %v, want %v", test.input, got, want)
+ }
+ if got, want := err, test.err; !cmp.Equal(got, want) {
+ t.Errorf("Decode(%q) err %q, want %q", test.input, got, want)
+ }
+ })
+ }
+}
+
+func TestDecodeWithFake(t *testing.T) {
+ // fakeCodex — это быстрое приближение реального Codex.
+ codex := newFakeCodex()
+
+ var tests []decodeCase // строки опущены для краткости
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ output, err := Decode(test.input, codex)
+ if got, want := output, test.output; got != want {
+ t.Errorf("Decode(%q) = %v, want %v", test.input, got, want)
+ }
+ if got, want := err, test.err; !cmp.Equal(got, want) {
+ t.Errorf("Decode(%q) err %q, want %q", test.input, got, want)
+ }
+ })
+ }
+}
+```
+
+В контринте ниже обратите внимание, как сложно различить, какой тип `Codex`
+используется на тестовый случай в настройке случая. (Выделенные части нарушают
+совет из [TotT: Data Driven Traps!][tott-97] .)
+
+```go
+// Плохо:
+type decodeCase struct {
+ name string
+ input string
+ codex testCodex
+ output string
+ err error
+}
+
+type testCodex int
+
+const (
+ fake testCodex = iota
+ prod
+)
+
+func TestDecode(t *testing.T) {
+ var tests []decodeCase // строки опущены для краткости
+
+ for _, test := tests {
+ t.Run(test.name, func(t *testing.T) {
+ var codex Codex
+ switch test.codex {
+ case fake:
+ codex = newFakeCodex()
+ case prod:
+ codex = setupCodex(t)
+ default:
+ t.Fatalf("Unknown codex type: %v", codex)
+ }
+ output, err := Decode(test.input, codex)
+ if got, want := output, test.output; got != want {
+ t.Errorf("Decode(%q) = %q, want %q", test.input, got, want)
+ }
+ if got, want := err, test.err; !cmp.Equal(got, want) {
+ t.Errorf("Decode(%q) err %q, want %q", test.input, got, want)
+ }
+ })
+ }
+}
+```
+
+[tott-97]: https://testing.googleblog.com/2008/09/tott-data-driven-traps.html
+
+<a id="table-tests-identifying-the-row"></a>
+
+#### Идентификация строки
+
+Не используйте индекс теста в тестовой таблице в качестве замены именования
+ваших тестов или печати входных данных. Никто не хочет проходить через вашу
+тестовую таблицу и считать записи, чтобы выяснить, какой тестовый случай
+провалился.
+
+```go
+// Плохо:
+tests := []struct {
+ input, want string
+}{
+ {"hello", "HELLO"},
+ {"wORld", "WORLD"},
+}
+for i, d := range tests {
+ if strings.ToUpper(d.input) != d.want {
+ t.Errorf("Failed on case #%d", i)
+ }
+}
+```
+
+Добавьте описание теста в вашу тестовую структуру и печатайте его вместе с
+сообщениями об ошибках. При использовании подтестов имя вашего подтеста должно
+эффективно идентифицировать строку.
+
+**Важно:** Хотя `t.Run` ограничивает область вывода и выполнения, вы должны
+всегда [идентифицировать входные данные]. Имена строк табличных тестов должны
+следовать [руководству по именованию подтестов].
+
+[идентифицировать входные данные]: #identify-the-input
+[руководству по именованию подтестов]: #subtest-names
+
+<a id="mark-test-helpers"></a>
+
+### Тестовые помощники (Test helpers)
+
+Тестовый помощник — это функция, выполняющая задачу настройки или очистки. Все
+ошибки, возникающие в тестовых помощниках, должны быть ошибками окружения (а не
+кода под тестом) — например, когда тестовая база данных не может быть запущена
+потому что на этой машине не осталось свободных портов.
+
+Если вы передаете `*testing.T`, вызовите [`t.Helper`], чтобы приписать ошибки в
+тестовом помощнике строке, где помощник вызывается. Этот параметр должен идти
+после параметра [контекста](#contexts), если он присутствует, и перед любыми
+оставшимися параметрами.
+
+```go
+// Хорошо:
+func TestSomeFunction(t *testing.T) {
+ golden := readFile(t, "testdata/golden-result.txt")
+ // ... тесты против golden ...
+}
+
+// readFile возвращает содержимое файла данных.
+// Может вызываться только из той же горутины, которая начала тест.
+func readFile(t *testing.T, filename string) string {
+ t.Helper()
+ contents, err := runfiles.ReadFile(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return string(contents)
+}
+```
+
+Не используйте этот шаблон, когда он скрывает связь между неудачей теста и
+условиями, которые к ней привели. Конкретно, рекомендации о [библиотеках
+утверждений](#assert) все еще применяются, и [`t.Helper`] не должен
+использоваться для реализации таких библиотек.
+
+**Совет:** Подробнее о различии между тестовыми помощниками и помощниками
+утверждений см. [лучшие практики](https://neonxp.ru/pages/gostyleguide/google/best-practices/#test-functions).
+
+Хотя вышесказанное относится к `*testing.T`, большая часть рекомендаций остается
+той же для вспомогательных функций бенчмарков и фаззинга.
+
+[`t.Helper`]: https://pkg.go.dev/testing#T.Helper
+
+<a id="test-package"></a>
+
+### Тестовый пакет
+
+<a id="TOC-TestPackage"></a>
+
+<a id="test-same-package"></a>
+
+#### Тесты в том же пакете
+
+Тесты могут быть определены в том же пакете, что и тестируемый код.
+
+Чтобы написать тест в том же пакете:
+
+* Поместите тесты в файл `foo_test.go`
+* Используйте `package foo` для тестового файла
+* Не импортируйте явно тестируемый пакет
+
+```build
+# Хорошо:
+go_library(
+ name = "foo",
+ srcs = ["foo.go"],
+ deps = [
+ ...
+ ],
+)
+
+go_test(
+ name = "foo_test",
+ size = "small",
+ srcs = ["foo_test.go"],
+ library = ":foo",
+ deps = [
+ ...
+ ],
+)
+```
+
+Тест в том же пакете может обращаться к неэкспортированным идентификаторам в
+пакете. Это может обеспечить лучшее покрытие тестами и более лаконичные тесты.
+Имейте в виду, что любые [примеры], объявленные в тесте, не будут иметь имен
+пакетов, которые понадобятся пользователю в их коде.
+
+[`library`]:
+ https://github.com/bazelbuild/rules_go/blob/master/docs/go/core/rules.md#go_library
+[примеры]: #examples
+
+<a id="test-different-package"></a>
+
+#### Тесты в другом пакете
+
+Не всегда уместно или даже возможно определить тест в том же пакете, что и
+тестируемый код. В этих случаях используйте имя пакета с суффиксом `_test`. Это
+исключение из правила "без подчеркиваний" для [имен пакетов](#package-names).
+Например:
+
+* Если интеграционный тест не имеет очевидной библиотеки, к которой он
+ принадлежит
+
+ ```go
+ // Хорошо:
+ package gmailintegration_test
+
+ import "testing"
+ ```
+
+* Если определение тестов в том же пакете приводит к циклическим зависимостям
+
+ ```go
+ // Хорошо:
+ package fireworks_test
+
+ import (
+ "fireworks"
+ "fireworkstestutil" // fireworkstestutil также импортирует fireworks
+ )
+ ```
+
+<a id="use-package-testing"></a>
+
+### Использование пакета `testing`
+
+Стандартная библиотека Go предоставляет пакет [`testing`]. Это единственный
+фреймворк тестирования, разрешенный для кода Go в кодовой базе Google. В
+частности, [библиотеки утверждений](#assert) и сторонние фреймворки тестирования
+не разрешены.
+
+Пакет `testing` предоставляет минимальный, но полный набор функциональности для
+написания хороших тестов:
+
+* Тесты верхнего уровня
+* Бенчмарки
+* [Исполняемые примеры](https://blog.golang.org/examples)
+* Подтесты
+* Логирование
+* Ошибки и фатальные ошибки
+
+Они разработаны для слаженной работы с основными языковыми возможностями, такими
+как [составные литералы] и [if с инициализатором], чтобы позволить авторам
+тестов писать [понятные, читаемые и поддерживаемые тесты].
+
+[`testing` package]: https://pkg.go.dev/testing
+[составные литералы]: https://go.dev/ref/spec#Composite_literals
+[if с инициализатором]: https://go.dev/ref/spec#If_statements
+
+<a id="non-decisions"></a>
+
+## Не-решения
+
+Руководство по стилю не может перечислять позитивные предписания по всем
+вопросам, как не может оно перечислить все вопросы, по которым оно не дает
+мнения. Тем не менее, вот несколько вещей, по которым сообщество читаемости
+ранее спорило и не достигло консенсуса.
+
+* **Локальная инициализация переменных нулевым значением**. `var i int` и `i
+ := 0` эквивалентны. См. также [лучшие практики инициализации].
+* **Пустой составной литерал vs. `new` или `make`**. `&File{}` и `new(File)`
+ эквивалентны. Так же `map[string]bool{}` и `make(map[string]bool)`. См.
+ также [лучшие практики составных объявлений].
+* **Порядок аргументов got, want в вызовах cmp.Diff**. Будьте локально
+ последовательны и [включите легенду](#print-diffs) в ваше сообщение об
+ ошибке.
+* **`errors.New` vs `fmt.Errorf` на неформатированных строках**.
+ `errors.New("foo")` и `fmt.Errorf("foo")` могут использоваться
+ взаимозаменяемо.
+
+Если возникнут особые обстоятельства, наставник по читаемости может сделать
+необязательный комментарий, но в целом автор волен выбирать предпочитаемый им
+стиль в данной ситуации.
+
+Естественно, если что-то, не охваченное руководством по стилю, требует
+дополнительного обсуждения, авторы могут спросить — либо в конкретном ревью,
+либо на внутренних досках сообщений.
+
+[лучшие практики составных объявлений]:
+ https://google.github.io/styleguide/go/best-practices#vardeclcomposite
+[лучшие практики инициализации]:
+ https://google.github.io/styleguide/go/best-practices#vardeclinitialization