summaryrefslogtreecommitdiff
path: root/content/pages/gostyleguide/google/best-practices.md
diff options
context:
space:
mode:
author2026-01-31 20:38:50 +0300
committer2026-01-31 23:38:53 +0300
commit49458f5ffd5a48c465117ec27f6437683f75acc1 (patch)
treea99ee68116d10c2b2e5a70c442cdadec95ba793c /content/pages/gostyleguide/google/best-practices.md
downloadblog-49458f5ffd5a48c465117ec27f6437683f75acc1.tar.gz
blog-49458f5ffd5a48c465117ec27f6437683f75acc1.tar.bz2
blog-49458f5ffd5a48c465117ec27f6437683f75acc1.tar.xz
blog-49458f5ffd5a48c465117ec27f6437683f75acc1.zip
initial
Diffstat (limited to 'content/pages/gostyleguide/google/best-practices.md')
-rw-r--r--content/pages/gostyleguide/google/best-practices.md3727
1 files changed, 3727 insertions, 0 deletions
diff --git a/content/pages/gostyleguide/google/best-practices.md b/content/pages/gostyleguide/google/best-practices.md
new file mode 100644
index 0000000..4fc59b1
--- /dev/null
+++ b/content/pages/gostyleguide/google/best-practices.md
@@ -0,0 +1,3727 @@
+---
+order: 1
+title: Google Go Style Guide — Лучшие практики
+---
+
+# Лучшие практики стиля Go (Go Style Best Practices)
+
+Оригинал: https://google.github.io/styleguide/go/best-practices
+
+[Обзор](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 (Go
+Style)](https://neonxp.ru/pages/gostyleguide/google/) в Google. Данный документ **не является ни [нормативным
+(normative)](https://neonxp.ru/pages/gostyleguide/google/#normative), ни [каноническим (canonical)](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>
+
+## О документе (About)
+
+В этом документе представлены **рекомендации о том, как наилучшим образом
+применять Руководство по стилю Go**. Эти рекомендации предназначены для типичных
+ситуаций, возникающих часто, но могут не применяться в каждом случае. По
+возможности обсуждаются несколько альтернативных подходов, а также соображения,
+которые учитываются при решении о том, когда их применять, а когда нет.
+
+См. [обзор](https://neonxp.ru/pages/gostyleguide/google/#about) для полного набора документов руководства по стилю.
+
+<a id="naming"></a>
+
+## Именование (Naming)
+
+<a id="function-names"></a>
+
+### Имена функций и методов
+
+<a id="function-name-repetition"></a>
+
+#### Избегайте повторения (Avoid repetition)
+
+При выборе имени для функции или метода учитывайте контекст, в котором это имя
+будет прочитано. Рассмотрите следующие рекомендации, чтобы избежать избыточного
+[повторения (repetition)](https://neonxp.ru/pages/gostyleguide/google/decisions/#repetition) в месте вызова (call site):
+
+* Следующее, как правило, можно опустить в именах функций и методов:
+
+ * Типы входных и выходных данных (если нет конфликта)
+ * Тип получателя (receiver) метода
+ * Является ли входной или выходной параметр указателем (pointer)
+
+* Для функций не следует [повторять имя
+ пакета](https://neonxp.ru/pages/gostyleguide/google/decisions/#repetitive-with-package).
+
+ ```go
+ // Плохо:
+ package yamlconfig
+
+ func ParseYAMLConfig(input string) (*Config, error)
+ ```
+
+ ```go
+ // Хорошо:
+ package yamlconfig
+
+ func Parse(input string) (*Config, error)
+ ```
+
+* Для методов не следует повторять имя получателя метода.
+
+ ```go
+ // Плохо:
+ func (c *Config) WriteConfigTo(w io.Writer) (int64, error)
+ ```
+
+ ```go
+ // Хорошо:
+ func (c *Config) WriteTo(w io.Writer) (int64, error)
+ ```
+
+* Не повторяйте имена переменных, передаваемых в качестве параметров.
+
+ ```go
+ // Плохо:
+ func OverrideFirstWithSecond(dest, source *Config) error
+ ```
+
+ ```go
+ // Хорошо:
+ func Override(dest, source *Config) error
+ ```
+
+* Не повторяйте имена и типы возвращаемых значений.
+
+ ```go
+ // Плохо:
+ func TransformToJSON(input *Config) *jsonconfig.Config
+ ```
+
+ ```go
+ // Хорошо:
+ func Transform(input *Config) *jsonconfig.Config
+ ```
+
+Когда необходимо устранить неоднозначность между функциями с похожими именами,
+допустимо включить дополнительную информацию.
+
+```go
+// Хорошо:
+func (c *Config) WriteTextTo(w io.Writer) (int64, error)
+func (c *Config) WriteBinaryTo(w io.Writer) (int64, error)
+```
+
+<a id="function-name-conventions"></a>
+
+#### Соглашения об именовании (Naming conventions)
+
+Существуют и другие общие соглашения при выборе имен для функций и методов:
+
+* Функции, которые что-то возвращают, получают имена, похожие на
+ существительные.
+
+ ```go
+ // Хорошо:
+ func (c *Config) JobName(key string) (value string, ok bool)
+ ```
+
+ Следствием этого является то, что имена функций и методов должны [избегать
+ префикса `Get`](https://neonxp.ru/pages/gostyleguide/google/decisions/#getters).
+
+ ```go
+ // Плохо:
+ func (c *Config) GetJobName(key string) (value string, ok bool)
+ ```
+
+* Функции, которые что-то делают, получают имена, похожие на глаголы.
+
+ ```go
+ // Хорошо:
+ func (c *Config) WriteDetail(w io.Writer) (int64, error)
+ ```
+
+* Идентичные функции, которые отличаются только типами, включают имя типа в
+ конце имени.
+
+ ```go
+ // Хорошо:
+ func ParseInt(input string) (int, error)
+ func ParseInt64(input string) (int64, error)
+ func AppendInt(buf []byte, value int) []byte
+ func AppendInt64(buf []byte, value int64) []byte
+ ```
+
+ Если существует ясная "основная" версия, тип может быть опущен в имени для
+ этой версии:
+
+ ```go
+ // Хорошо:
+ func (c *Config) Marshal() ([]byte, error)
+ func (c *Config) MarshalText() (string, error)
+ ```
+
+<a id="naming-doubles"></a>
+
+### Тестовые дубли (Test doubles) и вспомогательные пакеты (helper packages)
+
+Существует несколько подходов, которые можно применить для [именования] пакетов
+и типов, которые предоставляют тестовые вспомогательные средства и особенно
+[тестовые дубли (test doubles)]. Тестовым дублем может быть заглушка (stub),
+фейк (fake), мок (mock) или шпион (spy).
+
+Эти примеры в основном используют заглушки. Обновите свои имена соответствующим
+образом, если ваш код использует фейки или другой вид тестового дубля.
+
+[именование]: guide#naming
+[тестовые дубли (test doubles)]:
+ https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
+
+Предположим, у вас есть хорошо сфокусированный пакет, предоставляющий
+production-код, подобный этому:
+
+```go
+package creditcard
+
+import (
+ "errors"
+
+ "path/to/money"
+)
+
+// ErrDeclined указывает, что эмитент отклонил операцию.
+var ErrDeclined = errors.New("creditcard: declined")
+
+// Card содержит информацию о кредитной карте, такую как эмитент,
+// срок действия и лимит.
+type Card struct {
+ // опущено
+}
+
+// Service позволяет выполнять операции с кредитными картами через внешние
+// процессинговые системы, такие как списание, авторизация, возврат средств и подписка.
+type Service struct {
+ // опущено
+}
+
+func (s *Service) Charge(c *Card, amount money.Money) error { /* опущено */ }
+```
+
+<a id="naming-doubles-helper-package"></a>
+
+#### Создание вспомогательных тестовых пакетов (Creating test helper packages)
+
+Предположим, вы хотите создать пакет, содержащий тестовые дубли для другого
+пакета. Воспользуемся `package creditcard` (из примера выше):
+
+Один из подходов — создать новый Go-пакет на основе production-пакета для
+тестирования. Безопасный выбор — добавить слово `test` к оригинальному имени
+пакета ("creditcard" + "test"):
+
+```go
+// Хорошо:
+package creditcardtest
+```
+
+Если явно не указано иное, все примеры в следующих разделах находятся в `package
+creditcardtest`.
+
+<a id="naming-doubles-simple"></a>
+
+#### Простой случай (Simple case)
+
+Вы хотите добавить набор тестовых дублей для `Service`. Поскольку `Card` по сути
+является простым типом данных, похожим на сообщение Protocol Buffer, он не
+требует специальной обработки в тестах, поэтому дубль не нужен. Если вы ожидаете
+только тестовые дубли для одного типа (например, `Service`), вы можете
+использовать лаконичный подход к именованию дублей:
+
+```go
+// Хорошо:
+import (
+ "path/to/creditcard"
+ "path/to/money"
+)
+
+// Stub заглушает creditcard.Service и не предоставляет собственного поведения.
+type Stub struct{}
+
+func (Stub) Charge(*creditcard.Card, money.Money) error { return nil }
+```
+
+Это строго предпочтительнее, чем выбор имен типа `StubService` или очень плохого
+`StubCreditCardService`, потому что базовое имя пакета и его доменные типы
+подразумевают, что такое `creditcardtest.Stub`.
+
+Наконец, если пакет собирается с помощью Bazel, убедитесь, что новое правило
+`go_library` для пакета помечено как `testonly`:
+
+```build
+# Хорошо:
+go_library(
+ name = "creditcardtest",
+ srcs = ["creditcardtest.go"],
+ deps = [
+ ":creditcard",
+ ":money",
+ ],
+ testonly = True,
+)
+```
+
+Приведенный выше подход является общепринятым и будет достаточно хорошо понят
+другими инженерами.
+
+См. также:
+
+* [Go Tip #42: Authoring a Stub for
+ Testing](https://google.github.io/styleguide/go/index.html#gotip)
+
+<a id="naming-doubles-multiple-behaviors"></a>
+
+#### Несколько вариантов поведения тестового дубля (Multiple test double behaviors)
+
+Когда одного вида заглушки недостаточно (например, нужна еще одна, которая
+всегда завершается ошибкой), мы рекомендуем называть заглушки в соответствии с
+поведением, которое они эмулируют. Здесь мы переименовываем `Stub` в
+`AlwaysCharges` и вводим новую заглушку `AlwaysDeclines`:
+
+```go
+// Хорошо:
+// AlwaysCharges заглушает creditcard.Service и имитирует успех.
+type AlwaysCharges struct{}
+
+func (AlwaysCharges) Charge(*creditcard.Card, money.Money) error { return nil }
+
+// AlwaysDeclines заглушает creditcard.Service и имитирует отклоненные операции.
+type AlwaysDeclines struct{}
+
+func (AlwaysDeclines) Charge(*creditcard.Card, money.Money) error {
+ return creditcard.ErrDeclined
+}
+```
+
+<a id="naming-doubles-multiple-types"></a>
+
+#### Несколько дублей для нескольких типов (Multiple doubles for multiple types)
+
+Но теперь предположим, что `package creditcard` содержит несколько типов, для
+которых стоит создавать дубли, как показано ниже с `Service` и `StoredValue`:
+
+```go
+package creditcard
+
+type Service struct {
+ // опущено
+}
+
+type Card struct {
+ // опущено
+}
+
+// StoredValue управляет кредитными балансами клиентов. Это применяется, когда
+// возвращенный товар зачисляется на локальный счет клиента, а не обрабатывается
+// эмитентом кредитной карты. По этой причине он реализован как отдельный сервис.
+type StoredValue struct {
+ // опущено
+}
+
+func (s *StoredValue) Credit(c *Card, amount money.Money) error { /* опущено */ }
+```
+
+В этом случае более явное именование тестовых дублей имеет смысл:
+
+```go
+// Хорошо:
+type StubService struct{}
+
+func (StubService) Charge(*creditcard.Card, money.Money) error { return nil }
+
+type StubStoredValue struct{}
+
+func (StubStoredValue) Credit(*creditcard.Card, money.Money) error { return nil }
+```
+
+<a id="naming-doubles-local-variables"></a>
+
+#### Локальные переменные в тестах (Local variables in tests)
+
+Когда переменные в ваших тестах ссылаются на дубли, выберите имя, которое
+наиболее четко отличает дубль от других production-типов, исходя из контекста.
+Рассмотрим некоторый production-код, который вы хотите протестировать:
+
+```go
+package payment
+
+import (
+ "path/to/creditcard"
+ "path/to/money"
+)
+
+type CreditCard interface {
+ Charge(*creditcard.Card, money.Money) error
+}
+
+type Processor struct {
+ CC CreditCard
+}
+
+var ErrBadInstrument = errors.New("payment: instrument is invalid or expired")
+
+func (p *Processor) Process(c *creditcard.Card, amount money.Money) error {
+ if c.Expired() {
+ return ErrBadInstrument
+ }
+ return p.CC.Charge(c, amount)
+}
+```
+
+В тестах тестовой дубль типа "шпион" (spy) для `CreditCard` противопоставляется
+production-типам, поэтому добавление префикса к имени может улучшить ясность.
+
+```go
+// Хорошо:
+package payment
+
+import "path/to/creditcardtest"
+
+func TestProcessor(t *testing.T) {
+ var spyCC creditcardtest.Spy
+ proc := &Processor{CC: spyCC}
+
+ // объявления опущены: card и amount
+ if err := proc.Process(card, amount); err != nil {
+ t.Errorf("proc.Process(card, amount) = %v, want nil", err)
+ }
+
+ charges := []creditcardtest.Charge{
+ {Card: card, Amount: amount},
+ }
+
+ if got, want := spyCC.Charges, charges; !cmp.Equal(got, want) {
+ t.Errorf("spyCC.Charges = %v, want %v", got, want)
+ }
+}
+```
+
+Это понятнее, чем когда имя не имеет префикса.
+
+```go
+// Плохо:
+package payment
+
+import "path/to/creditcardtest"
+
+func TestProcessor(t *testing.T) {
+ var cc creditcardtest.Spy
+
+ proc := &Processor{CC: cc}
+
+ // объявления опущены: card и amount
+ if err := proc.Process(card, amount); err != nil {
+ t.Errorf("proc.Process(card, amount) = %v, want nil", err)
+ }
+
+ charges := []creditcardtest.Charge{
+ {Card: card, Amount: amount},
+ }
+
+ if got, want := cc.Charges, charges; !cmp.Equal(got, want) {
+ t.Errorf("cc.Charges = %v, want %v", got, want)
+ }
+}
+```
+
+<a id="shadowing"></a>
+
+### Затенение (Shadowing)
+
+**Примечание:** Это объяснение использует два неформальных термина, *stomping* и
+*shadowing*. Они не являются официальными концепциями в спецификации языка Go.
+
+Как и во многих языках программирования, в Go есть изменяемые переменные:
+присваивание переменной меняет ее значение.
+
+```go
+// Хорошо:
+func abs(i int) int {
+ if i < 0 {
+ i *= -1
+ }
+ return i
+}
+```
+
+При использовании [короткого объявления переменных (short variable
+declarations)] с оператором `:=` в некоторых случаях новая переменная не
+создается. Мы можем назвать это *stomping* (затирание). Это допустимо, когда
+исходное значение больше не нужно.
+
+```go
+// Хорошо:
+// innerHandler — это вспомогательная функция для некоторого обработчика запросов, который сам
+// выполняет запросы к другим бэкендам.
+func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
+ // Безусловно ограничиваем срок действия (deadline) для этой части обработки запроса.
+ ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ ctxlog.Info(ctx, "Capped deadline in inner request")
+
+ // Код здесь больше не имеет доступа к оригинальному контексту.
+ // Это хороший стиль, если при первом написании вы предполагаете,
+ // что даже по мере роста кода ни одна операция законно не должна
+ // использовать (возможно, неограниченный) оригинальный контекст, предоставленный вызывающей стороной.
+
+ // ...
+}
+```
+
+Однако будьте осторожны с использованием короткого объявления переменных в новой
+области видимости: это вводит новую переменную. Мы можем назвать это *shadowing*
+(затенение) исходной переменной. Код после конца блока ссылается на оригинал.
+Вот ошибочная попытка условно сократить срок действия (deadline):
+
+```go
+// Плохо:
+func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
+ // Попытка условно ограничить срок действия.
+ if *shortenDeadlines {
+ ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ ctxlog.Info(ctx, "Capped deadline in inner request")
+ }
+
+ // ОШИБКА: "ctx" здесь снова означает контекст, предоставленный вызывающей стороной.
+ // Вышеуказанный ошибочный код скомпилировался, потому что и ctx, и cancel
+ // использовались внутри оператора if.
+
+ // ...
+}
+```
+
+Правильная версия кода может быть такой:
+
+```go
+// Хорошо:
+func (s *Server) innerHandler(ctx context.Context, req *pb.MyRequest) *pb.MyResponse {
+ if *shortenDeadlines {
+ var cancel func()
+ // Обратите внимание на использование простого присваивания, =, а не :=.
+ ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
+ defer cancel()
+ ctxlog.Info(ctx, "Capped deadline in inner request")
+ }
+ // ...
+}
+```
+
+В случае, который мы назвали stomping, поскольку нет новой переменной, тип
+присваиваемого значения должен совпадать с типом исходной переменной. При
+затенении вводится совершенно новая сущность, поэтому она может иметь другой
+тип. Намеренное затенение может быть полезной практикой, но вы всегда можете
+использовать новое имя, если это улучшает [ясность (clarity)](https://neonxp.ru/pages/gostyleguide/google/guide/#clarity).
+
+Не рекомендуется использовать переменные с теми же именами, что и у стандартных
+пакетов, за исключением очень маленьких областей видимости, потому что это
+делает функции и значения из этого пакета недоступными. И наоборот, при выборе
+имени для вашего пакета избегайте имен, которые, вероятно, потребуют
+[переименования импорта (import renaming)](https://neonxp.ru/pages/gostyleguide/google/decisions/#import-renaming) или
+вызовут затенение иначе хороших имен переменных на стороне клиента.
+
+```go
+// Плохо:
+func LongFunction() {
+ url := "https://example.com/"
+ // Упс, теперь мы не можем использовать net/url в коде ниже.
+}
+```
+
+[короткого объявления переменных (short variable declarations)]:
+ https://go.dev/ref/spec#Short_variable_declarations
+
+<a id="util-packages"></a>
+
+### Пакеты `util` (Util packages)
+
+Пакеты Go имеют имя, указанное в объявлении `package`, отдельное от пути
+импорта. Имя пакета имеет большее значение для читаемости, чем путь.
+
+Имена пакетов Go должны быть [связаны с тем, что предоставляет
+пакет](https://neonxp.ru/pages/gostyleguide/google/decisions/#package-names). Называть пакет просто `util`, `helper`,
+`common` или подобным обычно плохой выбор (хотя это может быть использовано как
+*часть* имени). Неинформативные имена затрудняют чтение кода, и если они
+используются слишком широко, они могут вызывать ненужные [конфликты
+импорта](https://neonxp.ru/pages/gostyleguide/google/decisions/#import-renaming).
+
+Вместо этого подумайте, как будет выглядеть место вызова (callsite).
+
+```go
+// Хорошо:
+db := spannertest.NewDatabaseFromFile(...)
+
+_, err := f.Seek(0, io.SeekStart)
+
+b := elliptic.Marshal(curve, x, y)
+```
+
+Вы можете примерно понять, что делает каждая из этих строк, даже не зная списка
+импортов (`cloud.google.com/go/spanner/spannertest`, `io` и `crypto/elliptic`).
+С менее сфокусированными именами они могли бы читаться так:
+
+```go
+// Плохо:
+db := test.NewDatabaseFromFile(...)
+
+_, err := f.Seek(0, common.SeekStart)
+
+b := helper.Marshal(curve, x, y)
+```
+
+<a id="package-size"></a>
+
+## Размер пакета (Package size)
+
+Если вы задаетесь вопросом, насколько большими должны быть ваши пакеты Go и
+следует ли помещать связанные типы в один пакет или разделять их на разные,
+хорошим началом будет [пост в блоге Go об именах пакетов][blog-pkg-names].
+Несмотря на название поста, он не только об именовании. Он содержит полезные
+подсказки и ссылается на несколько полезных статей и докладов.
+
+Вот некоторые другие соображения и примечания.
+
+Пользователи видят [godoc] для пакета на одной странице, и любые
+экспортированные методы типов, предоставляемых пакетом, группируются по их типу.
+Godoc также группирует конструкторы вместе с типами, которые они возвращают.
+Если *клиентскому коду* (client code) вероятно потребуется, чтобы два значения
+разных типов взаимодействовали друг с другом, может быть удобно для пользователя
+иметь их в одном пакете.
+
+Код внутри пакета имеет доступ к неэкспортированным идентификаторам пакета. Если
+у вас есть несколько связанных типов, *реализация* которых тесно связана,
+размещение их в одном пакете позволяет достичь этой связи без загрязнения
+публичного API этими деталями. Хороший тест для этой связи — представить
+гипотетического пользователя двух пакетов, где пакеты охватывают тесно связанные
+темы: если пользователь должен импортировать оба пакета, чтобы использовать
+любой из них хоть сколько-нибудь значимо, обычно правильным решением будет
+объединить их вместе. Стандартная библиотека в целом хорошо демонстрирует такую
+область видимости (scoping) и слоистость (layering).
+
+При всем сказанном, помещение всего вашего проекта в один пакет, вероятно,
+сделает этот пакет слишком большим. Когда что-то концептуально отличается,
+предоставление ему собственного небольшого пакета может облегчить его
+использование. Короткое имя пакета, известное клиентам, вместе с именем
+экспортированного типа работают вместе, чтобы создать значимый идентификатор:
+например, `bytes.Buffer`, `ring.New`. [Пост об именах пакетов][blog-pkg-names]
+содержит больше примеров.
+
+Стиль Go гибок относительно размера файлов, потому что сопровождающие могут
+перемещать код внутри пакета из одного файла в другой, не влияя на вызывающую
+сторону. Но в качестве общего руководства: обычно не стоит иметь один файл с
+тысячами строк или множество крошечных файлов. Нет такого соглашения, как "один
+тип — один файл", как в некоторых других языках. Эмпирическое правило: файлы
+должны быть достаточно сфокусированными, чтобы сопровождающий мог определить, в
+каком файле что-то находится, и достаточно маленькими, чтобы было легко найти
+это, когда вы там окажетесь. Стандартная библиотека часто разделяет большие
+пакеты на несколько исходных файлов, группируя связанный код по файлам. Исходный
+код [пакета `bytes`] является хорошим примером. Пакеты с длинной документацией
+могут выбрать выделение одного файла с именем `doc.go`, который содержит
+[документацию пакета](https://neonxp.ru/pages/gostyleguide/google/decisions/#package-comments), объявление пакета и больше
+ничего, но это не обязательно.
+
+Внутри кодовой базы Google и в проектах, использующих Bazel, структура каталогов
+для кода Go отличается от таковой в open source проектах на Go: вы можете иметь
+несколько целей `go_library` в одном каталоге. Хорошей причиной для выделения
+каждому пакету собственного каталога является ожидание открытия исходного кода
+вашего проекта в будущем.
+
+Несколько неканонических справочных примеров, чтобы помочь продемонстрировать
+эти идеи на практике:
+
+* маленькие пакеты, содержащие одну связную идею, которая не требует
+ добавления или удаления чего-либо еще:
+
+ * [пакет `csv`][package `csv`]: кодирование и декодирование данных CSV с
+ разделением ответственности соответственно между [reader.go] и
+ [writer.go].
+ * [пакет `expvar`][package `expvar`]: "белый ящик" (whitebox) телеметрии
+ программы, полностью содержащийся в [expvar.go].
+
+* пакеты умеренного размера, содержащие одну большую предметную область и
+ несколько связанных с ней ответственностей:
+
+ * [пакет `flag`][package `flag`]: управление флагами командной строки,
+ полностью содержащееся в [flag.go].
+
+* большие пакеты, которые разделяют несколько тесно связанных предметных
+ областей по нескольким файлам:
+
+ * [пакет `http`][package `http`]: ядро HTTP: [client.go][http-client],
+ поддержка HTTP-клиентов; [server.go][http-server], поддержка
+ HTTP-серверов; [cookie.go], управление куками.
+ * [пакет `os`][package `os`]: кроссплатформенные абстракции операционной
+ системы: [exec.go], управление подпроцессами; [file.go], управление
+ файлами; [tempfile.go], временные файлы.
+
+См. также:
+
+* [Пакеты тестовых дублей (Test double packages)](#naming-doubles)
+* [Organizing Go Code (Blog Post)]
+* [Organizing Go Code (Presentation)]
+
+[blog-pkg-names]: https://go.dev/blog/package-names
+[пакет `bytes`]: https://go.dev/src/bytes/
+[Organizing Go Code (Blog Post)]: https://go.dev/blog/organizing-go-code
+[Organizing Go Code (Presentation)]: https://go.dev/talks/2014/organizeio.slide
+[пакет `csv`]: https://pkg.go.dev/encoding/csv
+[reader.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/encoding/csv/reader.go
+[writer.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/encoding/csv/writer.go
+[пакет `expvar`]: https://pkg.go.dev/expvar
+[expvar.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/expvar/expvar.go
+[пакет `flag`]: https://pkg.go.dev/flag
+[flag.go]: https://go.googlesource.com/go/+/refs/heads/master/src/flag/flag.go
+[godoc]: https://pkg.go.dev/
+[пакет `http`]: https://pkg.go.dev/net/http
+[http-client]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/net/http/client.go
+[http-server]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/net/http/server.go
+[cookie.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/net/http/cookie.go
+[пакет `os`]: https://pkg.go.dev/os
+[exec.go]: https://go.googlesource.com/go/+/refs/heads/master/src/os/exec.go
+[file.go]: https://go.googlesource.com/go/+/refs/heads/master/src/os/file.go
+[tempfile.go]:
+ https://go.googlesource.com/go/+/refs/heads/master/src/os/tempfile.go
+
+<a id="imports"></a>
+
+## Импорт (Imports)
+
+<a id="import-protos"></a>
+
+### Сообщения Protocol Buffer и заглушки (Stubs)
+
+Импорты библиотек proto обрабатываются иначе, чем стандартные импорты Go, из-за
+их межъязыковой природы. Соглашение для переименованных импортов proto основано
+на правиле, которое сгенерировало пакет:
+
+* Суффикс `pb` обычно используется для правил `go_proto_library`.
+* Суффикс `grpc` обычно используется для правил `go_grpc_library`.
+
+Часто используется одно слово, описывающее пакет:
+
+```go
+// Хорошо:
+import (
+ foopb "path/to/package/foo_service_go_proto"
+ foogrpc "path/to/package/foo_service_go_grpc"
+)
+```
+
+Следуйте рекомендациям по стилю для [имен
+пакетов](https://google.github.io/styleguide/go/decisions#package-names).
+Предпочитайте целые слова. Короткие имена хороши, но избегайте неоднозначности.
+В случае сомнений используйте имя пакета proto до _go с суффиксом pb:
+
+```go
+// Хорошо:
+import (
+ pushqueueservicepb "path/to/package/push_queue_service_go_proto"
+)
+```
+
+**Примечание:** Предыдущие рекомендации поощряли очень короткие имена, такие как
+"xpb" или даже просто "pb". Новый код должен предпочитать более описательные
+имена. Существующий код, использующий короткие имена, не должен использоваться в
+качестве примера, но его не нужно менять.
+
+<a id="import-order"></a>
+
+### Порядок импорта (Import ordering)
+
+См. [Go Style Decisions: Группировка импортов](https://neonxp.ru/pages/gostyleguide/google/decisions/.md#import-grouping).
+
+<a id="error-handling"></a>
+
+## Обработка ошибок (Error handling)
+
+В Go [ошибки — это значения (errors are values)]; они создаются кодом и
+потребляются кодом. Ошибки могут быть:
+
+* Преобразованы в диагностическую информацию для отображения человеку
+* Использованы сопровождающим
+* Интерпретированы конечным пользователем
+
+Сообщения об ошибках также появляются на самых разных поверхностях, включая
+сообщения журнала (log messages), дампы ошибок и отрисованные пользовательские
+интерфейсы.
+
+Код, который обрабатывает (производит или потребляет) ошибки, должен делать это
+осознанно. Может возникнуть соблазн проигнорировать или слепо распространить
+возвращаемое значение ошибки. Однако всегда стоит подумать, находится ли текущая
+функция в стеке вызовов в наилучшей позиции для обработки ошибки. Это обширная
+тема, и трудно дать категоричные рекомендации. Используйте свое суждение, но
+учитывайте следующие соображения:
+
+* Создавая значение ошибки, решите, придавать ли ему какую-либо
+ [структуру](#error-structure).
+* Обрабатывая ошибку, рассмотрите возможность [добавления
+ информации](#error-extra-info), которая есть у вас, но которой может не быть
+ у вызывающей и/или вызываемой стороны.
+* См. также рекомендации по [логированию ошибок](#error-logging).
+
+Хотя обычно нецелесообразно игнорировать ошибку, разумным исключением из этого
+является оркестрация связанных операций, где часто только первая ошибка полезна.
+Пакет [`errgroup`] предоставляет удобную абстракцию для группы операций, которые
+могут завершиться ошибкой или быть отменены как группа.
+
+[ошибки — это значения (errors are values)]:
+ https://go.dev/blog/errors-are-values
+[`errgroup`]: https://pkg.go.dev/golang.org/x/sync/errgroup
+
+См. также:
+
+* [Effective Go об ошибках](https://go.dev/doc/effective_go#errors)
+* [Пост в блоге Go об ошибках](https://go.dev/blog/go1.13-errors)
+* [Пакет `errors`](https://pkg.go.dev/errors)
+* [Пакет
+ `upspin.io/errors`](https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html)
+* [GoTip #89: When to Use Canonical Status Codes as
+ Errors](https://google.github.io/styleguide/go/index.html#gotip)
+* [GoTip #48: Error Sentinel
+ Values](https://google.github.io/styleguide/go/index.html#gotip)
+* [GoTip #13: Designing Errors for
+ Checking](https://google.github.io/styleguide/go/index.html#gotip)
+
+<a id="error-structure"></a>
+
+### Структура ошибок (Error structure)
+
+Если вызывающим сторонам необходимо анализировать ошибку (например, различать
+различные условия ошибки), придайте значению ошибки структуру, чтобы это можно
+было сделать программно, а не заставлять вызывающую сторону выполнять
+сопоставление строк. Этот совет применим как к production-коду, так и к тестам,
+которые заботятся о разных условиях ошибок.
+
+Простейшие структурированные ошибки — это непараметризованные глобальные
+значения.
+
+```go
+type Animal string
+
+var (
+ // ErrDuplicate возникает, если это животное уже было замечено.
+ ErrDuplicate = errors.New("duplicate")
+
+ // ErrMarsupial возникает, потому что у нас аллергия на сумчатых за пределами Австралии.
+ // Извините.
+ ErrMarsupial = errors.New("marsupials are not supported")
+)
+
+func process(animal Animal) error {
+ switch {
+ case seen[animal]:
+ return ErrDuplicate
+ case marsupial(animal):
+ return ErrMarsupial
+ }
+ seen[animal] = true
+ // ...
+ return nil
+}
+```
+
+Вызывающая сторона может просто сравнить возвращенное значение ошибки функции с
+одним из известных значений ошибок:
+
+```go
+// Хорошо:
+func handlePet(...) {
+ switch err := process(an); err {
+ case ErrDuplicate:
+ return fmt.Errorf("feed %q: %v", an, err)
+ case ErrMarsupial:
+ // Попробуем восстановиться с помощью друга.
+ alternate = an.BackupAnimal()
+ return handlePet(..., alternate, ...)
+ }
+}
+```
+
+Выше используются сторожевые (sentinel) значения, где ошибка должна быть равна
+(в смысле `==`) ожидаемому значению. Во многих случаях это вполне адекватно.
+Если `process` возвращает обернутые ошибки (wrapped errors) (обсуждается ниже),
+вы можете использовать [`errors.Is`].
+
+```go
+// Хорошо:
+func handlePet(...) {
+ switch err := process(an); {
+ case errors.Is(err, ErrDuplicate):
+ return fmt.Errorf("feed %q: %v", an, err)
+ case errors.Is(err, ErrMarsupial):
+ // ...
+ }
+}
+```
+
+Не пытайтесь различать ошибки на основе их строковой формы. (См. [GoTip #13:
+Designing Errors for
+Checking](https://google.github.io/styleguide/go/index.html#gotip) для получения
+дополнительной информации.)
+
+```go
+// Плохо:
+func handlePet(...) {
+ err := process(an)
+ if regexp.MatchString(`duplicate`, err.Error()) {...}
+ if regexp.MatchString(`marsupial`, err.Error()) {...}
+}
+```
+
+Если в ошибке есть дополнительная информация, которая нужна вызывающей стороне
+программно, ее, в идеале, следует представить структурно. Например, тип
+[`os.PathError`] документирован так, что помещает путь к операции, завершившейся
+неудачей, в поле структуры, к которому вызывающая сторона может легко получить
+доступ.
+
+Могут использоваться и другие структуры ошибок, например, структура проекта,
+содержащая код ошибки и строку с деталями. [Пакет `status`][status] —
+распространенная инкапсуляция; если вы выбираете этот подход (вы не обязаны это
+делать), используйте [канонические коды (canonical codes)]. См. [GoTip #89:
+When to Use Canonical Status Codes as
+Errors](https://google.github.io/styleguide/go/index.html#gotip) чтобы понять,
+является ли использование кодов статуса правильным выбором.
+
+[`os.PathError`]: https://pkg.go.dev/os#PathError
+[`errors.Is`]: https://pkg.go.dev/errors#Is
+[`errors.As`]: https://pkg.go.dev/errors#As
+[`package cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
+[status]: https://pkg.go.dev/google.golang.org/grpc/status
+[канонические коды (canonical codes)]:
+ https://pkg.go.dev/google.golang.org/grpc/codes
+
+<a id="error-extra-info"></a>
+
+### Добавление информации к ошибкам (Adding information to errors)
+
+Добавляя информацию к ошибкам, избегайте избыточной информации, которую уже
+предоставляет лежащая в основе ошибка. Пакет `os`, например, уже включает
+информацию о пути в своих ошибках.
+
+```go
+// Хорошо:
+if err := os.Open("settings.txt"); err != nil {
+ return fmt.Errorf("launch codes unavailable: %v", err)
+}
+
+// Вывод:
+//
+// launch codes unavailable: open settings.txt: no such file or directory
+```
+
+Здесь "launch codes unavailable" добавляет конкретный смысл ошибке `os.Open`,
+релевантный для контекста текущей функции, без дублирования информации о пути к
+файлу.
+
+```go
+// Плохо:
+if err := os.Open("settings.txt"); err != nil {
+ return fmt.Errorf("could not open settings.txt: %v", err)
+}
+
+// Вывод:
+//
+// could not open settings.txt: open settings.txt: no such file or directory
+```
+
+Не добавляйте аннотацию, если ее единственная цель — указать на сбой без
+добавления новой информации. Наличие ошибки достаточно передает сбой вызывающей
+стороне.
+
+```go
+// Плохо:
+return fmt.Errorf("failed: %v", err) // просто верните err вместо этого
+```
+
+[Выбор между `%v` и `%w` при оборачивании ошибок (wrapping
+errors)](https://go.dev/blog/go1.13-errors#whether-to-wrap) с помощью
+`fmt.Errorf` — это тонкое решение, которое значительно влияет на то, как ошибки
+распространяются, обрабатываются, проверяются и документируются в вашем
+приложении. Основной принцип — сделать значения ошибок полезными для их
+наблюдателей, будь то люди или код.
+
+1. **`%v` для простой аннотации или новой ошибки**
+
+ Глагол `%v` — это ваш универсальный инструмент для строкового форматирования
+ любого значения Go, включая ошибки. При использовании с `fmt.Errorf` он
+ встраивает строковое представление ошибки (то, что возвращает ее метод
+ `Error()`) в новое значение ошибки, отбрасывая любую структурированную
+ информацию из исходной ошибки. Примеры использования `%v`:
+
+ * Добавление интересного, не избыточного контекста: как в примере выше.
+
+ * Логирование или отображение ошибок: Когда основная цель — представить
+ удобочитаемое сообщение об ошибке в журналах или пользователю, и вы не
+ планируете, чтобы вызывающая сторона программно проверяла ошибку с
+ помощью `errors.Is` или `errors.As` (Примечание: `errors.Unwrap` здесь,
+ как правило, не рекомендуется, так как он не обрабатывает множественные
+ ошибки (multi-errors)).
+
+ * Создание новых, независимых ошибок: Иногда необходимо преобразовать
+ ошибку в новое сообщение об ошибке, тем самым скрывая специфику исходной
+ ошибки. Эта практика особенно полезна на границах систем, включая,
+ помимо прочего, RPC, IPC и хранилища, где мы переводим
+ доменно-специфичные ошибки в каноническое пространство ошибок.
+
+ ```go
+ // Хорошо:
+ func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
+ // ...
+ if err != nil {
+ return nil, fmt.Errorf("couldn't find fortune database: %v", err)
+ }
+ }
+ ```
+
+ Мы также могли бы явно аннотировать RPC код `Internal` в примере выше.
+
+ ```go
+ // Хорошо:
+ import (
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+ )
+
+ func (*FortuneTeller) SuggestFortune(context.Context, *pb.SuggestionRequest) (*pb.SuggestionResponse, error) {
+ // ...
+ if err != nil {
+ // Или используйте fmt.Errorf с глаголом %w, если намеренно оборачиваете ошибку,
+ // которую вызывающая сторона должна развернуть (unwrap).
+ return nil, status.Errorf(codes.Internal, "couldn't find fortune database", status.ErrInternal)
+ }
+ }
+ ```
+
+1. **`%w` (wrap) для программной проверки и цепочки ошибок (error chaining)**
+
+ Глагол `%w` специально предназначен для оборачивания ошибок (error
+ wrapping). Он создает новую ошибку, которая предоставляет метод `Unwrap()`,
+ позволяя вызывающим сторонам программно проверять цепочку ошибок с помощью
+ `errors.Is` и `errors.As`. Примеры использования `%w`:
+
+ * Добавление контекста с сохранением исходной ошибки для программной
+ проверки: Это основной случай использования во вспомогательных функциях
+ (helpers) вашего приложения. Вы хотите обогатить ошибку дополнительным
+ контекстом (например, какая операция выполнялась, когда она завершилась
+ неудачей), но при этом позволить вызывающей стороне проверить, является
+ ли лежащая в основе ошибка конкретной сторожевой ошибкой или типом.
+
+ ```go
+ // Хорошо:
+ func (s *Server) internalFunction(ctx context.Context) error {
+ // ...
+ if err != nil {
+ return fmt.Errorf("couldn't find remote file: %w", err)
+ }
+ }
+ ```
+
+ Это позволяет функции более высокого уровня выполнить `errors.Is(err,
+ fs.ErrNotExist)`, даже если исходная ошибка была обернута.
+
+ В точках, где ваша система взаимодействует с внешними системами, такими
+ как RPC, IPC или хранилище, часто лучше переводить доменно-специфичные
+ ошибки в стандартизированное пространство ошибок (например, коды статуса
+ gRPC), а не просто оборачивать исходную ошибку с помощью `%w`. Клиента
+ обычно не волнует точная внутренняя ошибка файловой системы; их волнует
+ канонический результат (например, `Internal`, `NotFound`,
+ `PermissionDenied`).
+
+ * Когда вы явно документируете и тестируете лежащие в основе ошибки,
+ которые вы раскрываете: Если API вашего пакета гарантирует, что
+ определенные лежащие в основе ошибки могут быть развернуты и проверены
+ вызывающими сторонами (например, "эта функция может вернуть
+ `ErrInvalidConfig`, обернутый в более общую ошибку"), то `%w` уместен.
+ Это становится частью контракта вашего пакета.
+
+См. также:
+
+* [Соглашения по документации ошибок (Error Documentation
+ Conventions)](#documentation-conventions-errors)
+* [Пост в блоге об оборачивании ошибок](https://blog.golang.org/go1.13-errors)
+
+<a id="error-percent-w"></a>
+
+### Размещение `%w` в ошибках (Placement of %w in errors)
+
+Предпочитайте размещать `%w` в конце строки ошибки *если* вы используете
+[оборачивание ошибок (error wrapping)](https://go.dev/blog/go1.13-errors) с
+глаголом форматирования `%w`.
+
+Ошибки могут быть обернуты с помощью глагола `%w` или путем помещения их в
+[структурированную
+ошибку](https://google.github.io/styleguide/go/index.html#gotip), которая
+реализует `Unwrap() error` (например,
+[`fs.PathError`](https://pkg.go.dev/io/fs#PathError)).
+
+Обернутые ошибки образуют цепочки ошибок (error chains): каждый новый слой
+обертывания добавляет новую запись в начало цепочки ошибок. Цепочку ошибок можно
+обойти с помощью метода `Unwrap() error`. Например:
+
+```go
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("err2: %w", err1)
+err3 := fmt.Errorf("err3: %w", err2)
+```
+
+Это формирует цепочку ошибок следующего вида:
+
+```mermaid
+flowchart LR
+ err3 == err3 wraps err2 ==> err2;
+ err2 == err2 wraps err1 ==> err1;
+```
+
+Независимо от того, где размещен глагол `%w`, возвращаемая ошибка всегда
+представляет начало цепочки ошибок, а `%w` — это следующий дочерний элемент.
+Аналогично, `Unwrap() error` всегда обходит цепочку ошибок от самой новой к
+самой старой ошибке.
+
+Однако размещение глагола `%w` влияет на то, печатается ли цепочка ошибок от
+самой новой к самой старой, от самой старой к самой новой или ни то, ни другое:
+
+```go
+// Хорошо:
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("err2: %w", err1)
+err3 := fmt.Errorf("err3: %w", err2)
+fmt.Println(err3) // err3: err2: err1
+// err3 — это цепочка ошибок от самой новой к самой старой, которая печатается от самой новой к самой старой.
+```
+
+```go
+// Плохо:
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("%w: err2", err1)
+err3 := fmt.Errorf("%w: err3", err2)
+fmt.Println(err3) // err1: err2: err3
+// err3 — это цепочка ошибок от самой новой к самой старой, которая печатается от самой старой к самой новой.
+```
+
+```go
+// Плохо:
+err1 := fmt.Errorf("err1")
+err2 := fmt.Errorf("err2-1 %w err2-2", err1)
+err3 := fmt.Errorf("err3-1 %w err3-2", err2)
+fmt.Println(err3) // err3-1 err2-1 err1 err2-2 err3-2
+// err3 — это цепочка ошибок от самой новой к самой старой, которая печатается ни от самой новой к самой старой,
+// ни от самой старой к самой новой.
+```
+
+Поэтому, чтобы текст ошибки отражал структуру цепочки ошибок, предпочитайте
+размещать глагол `%w` в конце в форме `[...]: %w`.
+
+<a id="error-logging"></a>
+
+### Логирование ошибок (Logging errors)
+
+Иногда функциям необходимо сообщить внешней системе об ошибке, не передавая ее
+своим вызывающим сторонам. Логирование — очевидный выбор здесь; но будьте
+внимательны к тому, что и как вы логируете.
+
+* Как и [хорошие сообщения о неудачных тестах (good test failure messages)],
+ сообщения журнала должны четко выражать, что пошло не так, и помогать
+ сопровождающему, включая соответствующую информацию для диагностики
+ проблемы.
+
+* Избегайте дублирования. Если вы возвращаете ошибку, обычно лучше не
+ логировать ее самостоятельно, а позволить вызывающей стороне обработать ее.
+ Вызывающая сторона может выбрать логирование ошибки или, возможно,
+ ограничить частоту логирования с помощью [`rate.Sometimes`]. Другие варианты
+ включают попытку восстановления или даже [остановку программы]. В любом
+ случае, предоставление контроля вызывающей стороне помогает избежать спама в
+ журналах.
+
+ Однако обратной стороной этого подхода является то, что любое логирование
+ записывается с использованием координат строк вызывающей стороны.
+
+* Будьте осторожны с [PII]. Многие приемники журналов (log sinks) не являются
+ подходящими местами назначения для конфиденциальной информации конечных
+ пользователей.
+
+* Используйте `log.Error` скупо. Логирование уровня ERROR вызывает сброс
+ (flush) и является более дорогостоящим, чем более низкие уровни логирования.
+ Это может серьезно повлиять на производительность вашего кода. Принимая
+ решение между уровнями error и warning, учитывайте лучшую практику:
+ сообщения на уровне error должны быть actionable (то есть требовать
+ действий), а не просто "более серьезными", чем warning.
+
+* Внутри Google у нас есть системы мониторинга, которые можно настроить для
+ более эффективного оповещения, чем просто запись в файл журнала в надежде,
+ что кто-то его заметит. Это похоже, но не идентично стандартной библиотеке
+ [пакету `expvar`].
+
+[хорошие сообщения о неудачных тестах (good test failure messages)]:
+ https://google.github.io/styleguide/go/decisions#useful-test-failures
+[остановку программы]: #checks-and-panics
+[`rate.Sometimes`]: https://pkg.go.dev/golang.org/x/time/rate#Sometimes
+[PII]: https://en.wikipedia.org/wiki/Personal_data
+[пакет `expvar`]: https://pkg.go.dev/expvar
+
+<a id="vlog"></a>
+
+#### Пользовательские уровни детализации (Custom verbosity levels)
+
+Используйте детальное логирование ([`log.V`]) с пользой. Детальное логирование
+может быть полезно для разработки и трассировки. Установление соглашения об
+уровнях детализации может быть полезным. Например:
+
+* Записывайте небольшое количество дополнительной информации на `V(1)`
+* Трассируйте больше информации на `V(2)`
+* Выводите большие внутренние состояния на `V(3)`
+
+Чтобы минимизировать стоимость детального логирования, вы должны убедиться, что
+случайно не вызываете дорогие функции, даже когда `log.V` выключен. `log.V`
+предлагает два API. Более удобный из них несет риск этих случайных затрат. В
+случае сомнений используйте немного более многословный стиль.
+
+```go
+// Хорошо:
+for _, sql := range queries {
+ log.V(1).Infof("Handling %v", sql)
+ if log.V(2) {
+ log.Infof("Handling %v", sql.Explain())
+ }
+ sql.Run(...)
+}
+```
+
+```go
+// Плохо:
+// sql.Explain вызывается даже когда это сообщение не печатается.
+log.V(2).Infof("Handling %v", sql.Explain())
+```
+
+[`log.V`]: https://pkg.go.dev/github.com/golang/glog#V
+
+<a id="program-init"></a>
+
+### Инициализация программы (Program initialization)
+
+Ошибки инициализации программы (например, неправильные флаги и конфигурация)
+должны передаваться вверх в `main`, который должен вызвать `log.Exit` с ошибкой,
+объясняющей, как исправить ошибку. В этих случаях `log.Fatal` обычно не следует
+использовать, потому что трассировка стека, указывающая на проверку, вряд ли
+будет так полезна, как сгенерированное человеком, actionable сообщение.
+
+<a id="checks-and-panics"></a>
+
+### Проверки программы и паники (Program checks and panics)
+
+Как указано в [решении против паник (decision against panics)], стандартная
+обработка ошибок должна быть структурирована вокруг возвращаемых значений
+ошибок. Библиотеки должны предпочитать возвращать ошибку вызывающей стороне, а
+не завершать программу, особенно для временных ошибок.
+
+Иногда необходимо выполнить проверку согласованности (consistency check)
+инварианта и завершить программу, если он нарушен. Как правило, это делается
+только в том случае, если сбой проверки инварианта означает, что внутреннее
+состояние стало невосстановимым. Наиболее надежный способ сделать это в кодовой
+базе Google — вызвать `log.Fatal`. Использование `panic` в этих случаях
+ненадежно, потому что возможно, что отложенные (deferred) функции заблокируют
+или еще больше повредят внутреннее или внешнее состояние.
+
+Аналогично, сопротивляйтесь искушению восстановить паники (recover panics),
+чтобы избежать сбоев, так как это может привести к распространению поврежденного
+состояния. Чем дальше вы от паники, тем меньше вы знаете о состоянии программы,
+которая может удерживать блокировки или другие ресурсы. Затем программа может
+развить другие неожиданные режимы сбоев, которые могут еще больше затруднить
+диагностику проблемы. Вместо того чтобы пытаться обрабатывать неожиданные паники
+в коде, используйте инструменты мониторинга для выявления неожиданных сбоев и
+исправляйте связанные ошибки с высоким приоритетом.
+
+**Примечание:** Стандартный [`net/http` server] нарушает этот совет и
+восстанавливает паники из обработчиков запросов. Консенсус среди опытных
+инженеров Go заключается в том, что это была историческая ошибка. Если вы
+исследуете журналы серверов приложений на других языках, часто можно найти
+большие трассировки стека, которые остаются необработанными. Избегайте этой
+ловушки в своих серверах.
+
+[решении против паник (decision against panics)]:
+ https://google.github.io/styleguide/go/decisions#dont-panic
+[`net/http` server]: https://pkg.go.dev/net/http#Server
+
+<a id="when-to-panic"></a>
+
+### Когда использовать panic (When to panic)
+
+Стандартная библиотека вызывает panic при неправильном использовании API.
+Например, [`reflect`] вызывает panic во многих случаях, когда значение доступно
+таким образом, что предполагает его неправильную интерпретацию. Это аналогично
+паникам на ошибки ядра языка, такие как доступ к элементу среза вне его границ.
+Проверка кода и тесты должны обнаруживать такие ошибки, которые не ожидаются в
+production-коде. Эти паники действуют как проверки инвариантов, которые не
+зависят от библиотеки, поскольку стандартная библиотека не имеет доступа к
+[уровневому пакету `log`], который используется в кодовой базе Google.
+
+[`reflect`]: https://pkg.go.dev/reflect
+[уровневому пакету `log`]: decisions#logging
+
+Другой случай, когда паники могут быть полезны, хотя и нечасто, — это внутренняя
+деталь реализации пакета, которая всегда имеет соответствующее восстановление
+(recover) в цепочке вызовов. Парсеры и подобные глубоко вложенные, тесно
+связанные внутренние группы функций могут выиграть от такого дизайна, где
+проталкивание возвратов ошибок добавляет сложность без ценности.
+
+Ключевой атрибут этого дизайна заключается в том, что эти **паники никогда не
+должны выходить за границы пакета** и не должны быть частью API пакета. Обычно
+это достигается с помощью функции верхнего уровня с отложенным вызовом
+(deferred), которая использует `recover` для преобразования распространенной
+паники в возвращаемую ошибку на публичной границе API. Это требует, чтобы код,
+который вызывает panic и восстанавливается, отличал паники, которые код вызывает
+сам, от тех, которые он не вызывает:
+
+```go
+// Хорошо:
+type syntaxError struct {
+ msg string
+}
+
+func parseInt(in string) int {
+ n, err := strconv.Atoi(in)
+ if err != nil {
+ panic(&syntaxError{"not a valid integer"})
+ }
+}
+
+func Parse(in string) (_ *Node, err error) {
+ defer func() {
+ if p := recover(); p != nil {
+ sErr, ok := p.(*syntaxError)
+ if !ok {
+ panic(p) // Распространяем panic, поскольку он находится вне области нашего кода.
+ }
+ err = fmt.Errorf("syntax error: %v", sErr.msg)
+ }
+ }()
+ ... // Парсим входные данные, вызывая parseInt внутри для парсинга целых чисел
+}
+```
+
+> **Предупреждение:** Код, использующий этот шаблон, должен позаботиться об
+> управлении любыми ресурсами, связанными с кодом, запущенным в таких разделах,
+> управляемых defer (например, закрыть, освободить или разблокировать).
+>
+> См.: [Go Tip #81: Avoiding Resource Leaks in API Design]
+
+Паника также используется, когда компилятор не может идентифицировать
+недостижимый код, например, при использовании функции типа `log.Fatal`, которая
+не вернется:
+
+```go
+// Хорошо:
+func answer(i int) string {
+ switch i {
+ case 42:
+ return "yup"
+ case 54:
+ return "base 13, huh"
+ default:
+ log.Fatalf("Sorry, %d is not the answer.", i)
+ panic("unreachable")
+ }
+}
+```
+
+[Не вызывайте функции `log` до того, как флаги будут
+распарсены.](https://pkg.go.dev/github.com/golang/glog#pkg-overview) Если вы
+должны завершиться в функции инициализации пакета (в `init` или
+["must"-функции](https://neonxp.ru/pages/gostyleguide/google/decisions/#must-functions)), panic допустима вместо вызова
+фатального логирования.
+
+См. также:
+
+* [Handling panics](https://go.dev/ref/spec#Handling_panics) и [Run-time
+ Panics](https://go.dev/ref/spec#Run_time_panics) в спецификации языка
+* [Defer, Panic, and Recover](https://go.dev/blog/defer-panic-and-recover)
+* [On the uses and misuses of panics in
+ Go](https://eli.thegreenplace.net/2018/on-the-uses-and-misuses-of-panics-in-go/)
+
+[Go Tip #81: Avoiding Resource Leaks in API Design]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="documentation"></a>
+
+## Документация (Documentation)
+
+<a id="documentation-conventions"></a>
+
+### Соглашения (Conventions)
+
+Этот раздел дополняет раздел [комментариев (commentary)] в документе решений.
+
+Код на Go, который документирован в знакомом стиле, легче читать и менее
+вероятно, что его будут использовать неправильно, по сравнению с тем, что плохо
+задокументировано или не задокументировано вовсе. Запускаемые [примеры
+(examples)] появляются в Godoc и Code Search и являются отличным способом
+объяснить, как использовать ваш код.
+
+[комментариев (commentary)]: decisions#commentary
+[примеры (examples)]: decisions#examples
+
+<a id="documentation-conventions-params"></a>
+
+#### Параметры и конфигурация (Parameters and configuration)
+
+Не каждый параметр должен быть перечислен в документации. Это относится к:
+
+* параметрам функций и методов
+* полям структур (struct fields)
+* API для опций (options)
+
+Документируйте подверженные ошибкам или неочевидные поля и параметры, объясняя,
+почему они интересны.
+
+В следующем фрагменте выделенный комментарий добавляет мало полезной информации
+для читателя:
+
+```go
+// Плохо:
+// Sprintf форматирует в соответствии со спецификатором формата и возвращает результирующую строку.
+//
+// format — это формат, а data — данные для интерполяции.
+func Sprintf(format string, data ...any) string
+```
+
+Однако этот фрагмент демонстрирует сценарий кода, похожий на предыдущий, где
+комментарий вместо этого говорит что-то неочевидное или существенно полезное для
+читателя:
+
+```go
+// Хорошо:
+// Sprintf форматирует в соответствии со спецификатором формата и возвращает результирующую строку.
+//
+// Предоставленные данные используются для интерполяции строки формата. Если данные не соответствуют
+// ожидаемым глаголам формата или количество данных не удовлетворяет спецификации формата,
+// функция будет встраивать предупреждения об ошибках форматирования в выходную строку, как описано
+// в разделе "Format errors" выше.
+func Sprintf(format string, data ...any) string
+```
+
+Учитывайте вашу вероятную аудиторию при выборе того, что документировать и на
+какую глубину. Сопровождающие, новички в команде, внешние пользователи и даже вы
+сами через шесть месяцев могут оценить немного другую информацию, отличную от
+той, что у вас на уме, когда вы впервые начинаете писать документацию.
+
+См. также:
+
+* [GoTip #41: Identify Function Call Parameters]
+* [GoTip #51: Patterns for Configuration]
+
+[GoTip #41: Identify Function Call Parameters]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[GoTip #51: Patterns for Configuration]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="documentation-conventions-contexts"></a>
+
+#### Контексты (Contexts)
+
+Подразумевается, что отмена (cancellation) аргумента контекста прерывает
+функцию, которой он предоставлен. Если функция может возвращать ошибку, по
+соглашению это `ctx.Err()`.
+
+Этот факт не нужно повторять:
+
+```go
+// Плохо:
+// Run выполняет рабочий цикл (run loop) воркера.
+//
+// Метод будет обрабатывать работу до отмены контекста и соответственно возвращает ошибку.
+func (Worker) Run(ctx context.Context) error
+```
+
+Поскольку это подразумевается, следующее лучше:
+
+```go
+// Хорошо:
+// Run выполняет рабочий цикл воркера.
+func (Worker) Run(ctx context.Context) error
+```
+
+Когда поведение контекста отличается или неочевидно, его следует прямо
+задокументировать, если верно любое из следующего.
+
+* Функция возвращает ошибку, отличную от `ctx.Err()`, когда контекст отменен:
+
+ ```go
+ // Хорошо:
+ // Run выполняет рабочий цикл воркера.
+ //
+ // Если контекст отменен, Run возвращает nil ошибку.
+ func (Worker) Run(ctx context.Context) error
+ ```
+
+* Функция имеет другие механизмы, которые могут ее прервать или повлиять на
+ время жизни:
+
+ ```go
+ // Хорошо:
+ // Run выполняет рабочий цикл воркера.
+ //
+ // Run обрабатывает работу до отмены контекста или вызова Stop.
+ // Отмена контекста обрабатывается асинхронно внутри: run может вернуться до того,
+ // как вся работа остановится. Метод Stop является синхронным и ожидает завершения
+ // всех операций из рабочего цикла. Используйте Stop для плавного завершения работы.
+ func (Worker) Run(ctx context.Context) error
+
+ func (Worker) Stop()
+ ```
+
+* Функция имеет особые ожидания относительно времени жизни контекста, его
+ происхождения (lineage) или прикрепленных значений (attached values):
+
+ ```go
+ // Хорошо:
+ // NewReceiver начинает получать сообщения, отправленные в указанную очередь.
+ // Контекст не должен иметь дедлайна (deadline).
+ func NewReceiver(ctx context.Context) *Receiver
+
+ // Principal возвращает человеко-читаемое имя стороны, совершившей вызов.
+ // Контекст должен иметь прикрепленное к нему значение из security.NewContext.
+ func Principal(ctx context.Context) (name string, ok bool)
+ ```
+
+ **Предупреждение:** Избегайте разработки API, которые предъявляют такие
+ требования (например, отсутствие дедлайнов у контекстов) от своих вызывающих
+ сторон. Вышеприведенное — лишь пример того, как это задокументировать, если
+ этого нельзя избежать, а не одобрение такого шаблона.
+
+<a id="documentation-conventions-concurrency"></a>
+
+#### Параллелизм (Concurrency)
+
+Пользователи Go предполагают, что концептуально доступные только для чтения
+операции безопасны для параллельного использования и не требуют дополнительной
+синхронизации.
+
+Дополнительное замечание о параллелизме можно безопасно удалить в этой Godoc:
+
+```go
+// Хорошо:
+// Len возвращает количество байт непрочитанной части буфера;
+// b.Len() == len(b.Bytes()).
+//
+// Безопасно для вызова несколькими горутинами одновременно.
+func (*Buffer) Len() int
+```
+
+Однако мутирующие операции не считаются безопасными для параллельного
+использования и требуют, чтобы пользователь учитывал синхронизацию.
+
+Аналогично, дополнительное замечание о параллелизме можно безопасно удалить
+здесь:
+
+```go
+// Хорошо:
+// Grow увеличивает емкость буфера.
+//
+// Не безопасно для вызова несколькими горутинами одновременно.
+func (*Buffer) Grow(n int)
+```
+
+Настоятельно рекомендуется документировать, если верно любое из следующего.
+
+* Непонятно, является ли операция доступной только для чтения или мутирующей:
+
+ ```go
+ // Хорошо:
+ package lrucache
+
+ // Lookup возвращает данные, связанные с ключом, из кэша.
+ //
+ // Эта операция не безопасна для параллельного использования.
+ func (*Cache) Lookup(key string) (data []byte, ok bool)
+ ```
+
+ Почему? При попадании в кэш (cache hit) при поиске ключа внутреннее
+ состояние LRU-кэша мутирует. Как это реализовано, может быть неочевидно для
+ всех читателей.
+
+* Синхронизация предоставляется API:
+
+ ```go
+ // Хорошо:
+ package fortune_go_proto
+
+ // NewFortuneTellerClient возвращает *rpc.Client для сервиса FortuneTeller.
+ // Безопасно для одновременного использования несколькими горутинами.
+ func NewFortuneTellerClient(cc *rpc.ClientConn) *FortuneTellerClient
+ ```
+
+ Почему? Stubby предоставляет синхронизацию.
+
+ **Примечание:** Если API является типом и API предоставляет синхронизацию в
+ целом, по соглашению только определение типа документирует семантику.
+
+* API потребляет пользовательские реализации типов или интерфейсов, и
+ потребитель интерфейса имеет особые требования к параллелизму:
+
+ ```go
+ // Хорошо:
+ package health
+
+ // Watcher сообщает о состоянии здоровья некоторой сущности (обычно серверной службы).
+ //
+ // Методы Watcher безопасны для одновременного использования несколькими горутинами.
+ type Watcher interface {
+ // Watch отправляет true на переданный канал, когда статус Watcher изменился.
+ Watch(changed chan<- bool) (unwatch func())
+
+ // Health возвращает nil, если за которой следят сущность здорова, или
+ // ненулевую ошибку, объясняющую, почему сущность нездорова.
+ Health() error
+ }
+ ```
+
+ Почему? Является ли API безопасным для использования несколькими горутинами
+ — это часть его контракта.
+
+<a id="documentation-conventions-cleanup"></a>
+
+#### Очистка (Cleanup)
+
+Документируйте любые явные требования к очистке, которые есть у API. В противном
+случае вызывающие стороны будут использовать API неправильно, что приведет к
+утечкам ресурсов и другим возможным ошибкам.
+
+Указывайте очистки, которые зависят от вызывающей стороны:
+
+```go
+// Хорошо:
+// NewTicker возвращает новый Ticker, содержащий канал, который будет отправлять
+// текущее время на канал после каждого тика.
+//
+// Вызовите Stop, чтобы освободить ресурсы, связанные с Ticker, когда закончите.
+func NewTicker(d Duration) *Ticker
+
+func (*Ticker) Stop()
+```
+
+Если может быть неясно, как очистить ресурсы, объясните, как:
+
+```go
+// Хорошо:
+// Get выполняет GET к указанному URL.
+//
+// Когда err равен nil, resp всегда содержит ненулевой resp.Body.
+// Вызывающая сторона должна закрыть resp.Body, когда закончит читать из него.
+//
+// resp, err := http.Get("http://example.com/")
+// if err != nil {
+// // обработать ошибку
+// }
+// defer resp.Body.Close()
+// body, err := io.ReadAll(resp.Body)
+func (c *Client) Get(url string) (resp *Response, err error)
+```
+
+См. также:
+
+* [GoTip #110: Don’t Mix Exit With Defer]
+
+[GoTip #110: Don’t Mix Exit With Defer]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="documentation-conventions-errors"></a>
+
+#### Ошибки (Errors)
+
+Документируйте значимые сторожевые значения ошибок (error sentinel values) или
+типы ошибок, которые ваши функции возвращают вызывающим сторонам, чтобы
+вызывающие стороны могли предвидеть, какие типы условий они могут обработать в
+своем коде.
+
+```go
+// Хорошо:
+package os
+
+// Read читает до len(b) байт из File и сохраняет их в b. Он возвращает
+// количество прочитанных байт и любую встреченную ошибку.
+//
+// При достижении конца файла Read возвращает 0, io.EOF.
+func (*File) Read(b []byte) (n int, err error) {
+```
+
+Когда функция возвращает определенный тип ошибки, правильно укажите, является ли
+ошибка указателем (pointer receiver) или нет:
+
+```go
+// Хорошо:
+package os
+
+type PathError struct {
+ Op string
+ Path string
+ Err error
+}
+
+// Chdir меняет текущий рабочий каталог на указанный каталог.
+//
+// Если есть ошибка, она будет типа *PathError.
+func Chdir(dir string) error {
+```
+
+Документирование того, являются ли возвращаемые значения указателями, позволяет
+вызывающим сторонам правильно сравнивать ошибки с помощью [`errors.Is`],
+[`errors.As`] и [`package cmp`]. Это связано с тем, что не указатель
+(non-pointer value) не эквивалентен указателю (pointer value).
+
+**Примечание:** В примере `Chdir` тип возвращаемого значения записан как
+`error`, а не `*PathError`, из-за [как работают нулевые значения интерфейса (nil
+interface values)](https://go.dev/doc/faq#nil_error).
+
+Документируйте общие соглашения об ошибках в [документации
+пакета](https://neonxp.ru/pages/gostyleguide/google/decisions/#package-comments), когда поведение применимо к большинству
+ошибок, встречающихся в пакете:
+
+```go
+// Хорошо:
+// Пакет os предоставляет независимый от платформы интерфейс к функциям операционной системы.
+//
+// Часто доступно больше информации внутри ошибки. Например, если вызов, принимающий имя файла,
+// завершается неудачей, такой как Open или Stat, ошибка будет включать имя файла, которое
+// не удалось, когда она печатается, и будет иметь тип *PathError, который может быть распакован
+// для получения дополнительной информации.
+package os
+```
+
+Вдумчивое применение этих подходов может добавить [дополнительную информацию к
+ошибкам](#error-extra-info) без особых усилий и помочь вызывающим сторонам
+избежать добавления избыточных аннотаций.
+
+См. также:
+
+* [Go Tip #106: Error Naming
+ Conventions](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #89: When to Use Canonical Status Codes as
+ Errors](https://google.github.io/styleguide/go/index.html#gotip)
+
+<a id="documentation-preview"></a>
+
+### Предварительный просмотр (Preview)
+
+Go имеет [сервер
+документации](https://pkg.go.dev/golang.org/x/pkgsite/cmd/pkgsite).
+Рекомендуется предварительно просматривать документацию, которую производит ваш
+код, как до, так и во время процесса ревью кода. Это помогает проверить, что
+[форматирование godoc] отображается правильно.
+
+[форматирование godoc]: #godoc-formatting
+
+<a id="godoc-formatting"></a>
+
+### Форматирование Godoc (Godoc formatting)
+
+[Godoc] предоставляет специальный синтаксис для [форматирования документации].
+
+* Требуется пустая строка для разделения абзацев:
+
+ ```go
+ // Хорошо:
+ // LoadConfig читает конфигурацию из указанного файла.
+ //
+ // См. some/shortlink для подробностей о формате файла конфигурации.
+ ```
+
+* Файлы тестов могут содержать [запускаемые примеры (runnable examples)],
+ которые появляются прикрепленными к соответствующей документации в godoc:
+
+ ```go
+ // Хорошо:
+ func ExampleConfig_WriteTo() {
+ cfg := &Config{
+ Name: "example",
+ }
+ if err := cfg.WriteTo(os.Stdout); err != nil {
+ log.Exitf("Failed to write config: %s", err)
+ }
+ // Output:
+ // {
+ // "name": "example"
+ // }
+ }
+ ```
+
+* Отступ строк на два дополнительных пробела форматирует их буквально
+ (verbatim):
+
+ ```go
+ // Хорошо:
+ // Update выполняет функцию в атомарной транзакции.
+ //
+ // Обычно это используется с анонимной TransactionFunc:
+ //
+ // if err := db.Update(func(state *State) { state.Foo = bar }); err != nil {
+ // //...
+ // }
+ ```
+
+ Однако обратите внимание, что часто может быть более уместно поместить код в
+ запускаемый пример, а не включать его в комментарий.
+
+ Это буквальное форматирование может быть использовано для форматирования, не
+ родного для godoc, такого как списки и таблицы:
+
+ ```go
+ // Хорошо:
+ // LoadConfig читает конфигурацию из указанного файла.
+ //
+ // LoadConfig обрабатывает следующие ключи особым образом:
+ // "import" заставит эту конфигурацию наследовать из указанного файла.
+ // "env" если присутствует, будет заполнен системным окружением.
+ ```
+
+* Одна строка, которая начинается с заглавной буквы, не содержит знаков
+ препинания, кроме скобок и запятых, и за которой следует другой абзац,
+ форматируется как заголовок:
+
+ ```go
+ // Хорошо:
+ // Следующая строка форматируется как заголовок.
+ //
+ // Использование заголовков
+ //
+ // Заголовки поставляются с автоматически сгенерированными якорными тегами для удобного связывания.
+ ```
+
+[Godoc]: https://pkg.go.dev/
+[форматирования документации]: https://go.dev/doc/comment
+[запускаемые примеры (runnable examples)]: decisions#examples
+
+<a id="signal-boost"></a>
+
+### Усиление сигнала (Signal boosting)
+
+Иногда строка кода выглядит как нечто обычное, но на самом деле это не так. Один
+из лучших примеров этого — проверка `err == nil` (поскольку `err != nil`
+встречается гораздо чаще). Следующие две условные проверки трудно различить:
+
+```go
+// Хорошо:
+if err := doSomething(); err != nil {
+ // ...
+}
+```
+
+```go
+// Плохо:
+if err := doSomething(); err == nil {
+ // ...
+}
+```
+
+Вы можете вместо этого "усилить" сигнал условного оператора, добавив
+комментарий:
+
+```go
+// Хорошо:
+if err := doSomething(); err == nil { // если ошибки НЕТ
+ // ...
+}
+```
+
+Комментарий привлекает внимание к различию в условном операторе.
+
+<a id="vardecls"></a>
+
+## Объявление переменных (Variable declarations)
+
+<a id="vardeclinitialization"></a>
+
+### Инициализация (Initialization)
+
+Для единообразия предпочитайте `:=` вместо `var` при инициализации новой
+переменной ненулевым значением.
+
+```go
+// Хорошо:
+i := 42
+```
+
+```go
+// Плохо:
+var i = 42
+```
+
+<a id="vardeclzero"></a>
+
+### Объявление переменных с нулевыми значениями (Declaring variables with zero values)
+
+Следующие объявления используют [нулевое значение (zero value)]:
+
+```go
+// Хорошо:
+var (
+ coords Point
+ magic [4]byte
+ primes []int
+)
+```
+
+[нулевое значение (zero value)]: https://golang.org/ref/spec#The_zero_value
+
+Вы должны объявлять значения, используя нулевое значение, когда хотите передать
+пустое значение, которое **готово к использованию позже**. Использование
+составных литералов (composite literals) с явной инициализацией может быть
+громоздким:
+
+```go
+// Плохо:
+var (
+ coords = Point{X: 0, Y: 0}
+ magic = [4]byte{0, 0, 0, 0}
+ primes = []int(nil)
+)
+```
+
+Распространенное применение объявления с нулевым значением — когда переменная
+используется как выход при демаршалинге (unmarshalling):
+
+```go
+// Хорошо:
+var coords Point
+if err := json.Unmarshal(data, &coords); err != nil {
+```
+
+Также допустимо использовать нулевое значение в следующей форме, когда вам нужна
+переменная типа указателя:
+
+```go
+// Хорошо:
+msg := new(pb.Bar) // или "&pb.Bar{}"
+if err := proto.Unmarshal(data, msg); err != nil {
+```
+
+Если в вашей структуре нужна блокировка (lock) или другое поле, которое [не
+должно копироваться](https://neonxp.ru/pages/gostyleguide/google/decisions/#copying), вы можете сделать его типом значения
+(value type), чтобы воспользоваться преимуществами инициализации нулевым
+значением. Это означает, что содержащий тип теперь должен передаваться по
+указателю, а не по значению. Методы этого типа должны принимать
+получатели-указатели (pointer receivers).
+
+```go
+// Хорошо:
+type Counter struct {
+ // Это поле не обязательно должно быть "*sync.Mutex". Однако
+ // пользователи теперь должны передавать *Counter объекты между собой, а не Counter.
+ mu sync.Mutex
+ data map[string]int64
+}
+
+// Обратите внимание, что это должен быть получатель-указатель, чтобы предотвратить копирование.
+func (c *Counter) IncrementBy(name string, n int64)
+```
+
+Допустимо использовать типы значений для локальных переменных составных типов
+(таких как структуры и массивы), даже если они содержат такие некопируемые поля.
+Однако, если составной тип возвращается функцией, или если все обращения к нему
+в конечном итоге требуют взятия адреса, предпочтительнее объявить переменную как
+тип указателя с самого начала. Аналогично, сообщения protobuf должны объявляться
+как типы указателей.
+
+```go
+// Хорошо:
+func NewCounter(name string) *Counter {
+ c := new(Counter) // "&Counter{}" тоже подходит.
+ registerCounter(name, c)
+ return c
+}
+
+var msg = new(pb.Bar) // или "&pb.Bar{}".
+```
+
+Это потому, что `*pb.Something` удовлетворяет [`proto.Message`], а
+`pb.Something` — нет.
+
+```go
+// Плохо:
+func NewCounter(name string) *Counter {
+ var c Counter
+ registerCounter(name, &c)
+ return &c
+}
+
+var msg = pb.Bar{}
+```
+
+[`proto.Message`]: https://pkg.go.dev/google.golang.org/protobuf/proto#Message
+
+> **Важно:** Типы map должны быть явно инициализированы перед тем, как их можно
+> будет изменять. Однако чтение из map с нулевым значением вполне допустимо.
+>
+> Для типов map и slice, если код особенно чувствителен к производительности и
+> если вы заранее знаете размеры, см. раздел [подсказки по размеру (size
+> hints)](#vardeclsize).
+
+<a id="vardeclcomposite"></a>
+
+### Составные литералы (Composite literals)
+
+Следующие объявления являются [составными литералами (composite literal)]:
+
+```go
+// Хорошо:
+var (
+ coords = Point{X: x, Y: y}
+ magic = [4]byte{'I', 'W', 'A', 'D'}
+ primes = []int{2, 3, 5, 7, 11}
+ captains = map[string]string{"Kirk": "James Tiberius", "Picard": "Jean-Luc"}
+)
+```
+
+Вы должны объявлять значение с помощью составного литерала, когда знаете
+начальные элементы или члены.
+
+В отличие от этого, использование составных литералов для объявления пустых
+значений или значений без членов может быть визуально шумным по сравнению с
+[инициализацией нулевым значением](#vardeclzero).
+
+Когда вам нужен указатель на нулевое значение, у вас есть два варианта: пустые
+составные литералы и `new`. Оба варианта допустимы, но ключевое слово `new`
+может служить напоминанием читателю, что если бы потребовалось ненулевое
+значение, составной литерал не сработал бы:
+
+```go
+// Хорошо:
+var (
+ buf = new(bytes.Buffer) // непустые Buffers инициализируются конструкторами.
+ msg = new(pb.Message) // непустые proto сообщения инициализируются билдерами или установкой полей по одному.
+)
+```
+
+[составные литералы (composite literal)]:
+ https://golang.org/ref/spec#Composite_literals
+
+<a id="vardeclsize"></a>
+
+### Подсказки по размеру (Size hints)
+
+Следующие объявления используют подсказки по размеру, чтобы предварительно
+выделить емкость:
+
+```go
+// Хорошо:
+var (
+ // Предпочтительный размер буфера для целевой файловой системы: st_blksize.
+ buf = make([]byte, 131072)
+ // Обычно обрабатывается до 8-10 элементов за запуск (16 — безопасное предположение).
+ q = make([]Node, 0, 16)
+ // Каждый шард обрабатывает shardSize (обычно 32000+) элементов.
+ seen = make(map[string]bool, shardSize)
+)
+```
+
+Подсказки по размеру и предварительное выделение — важные шаги **в сочетании с
+эмпирическим анализом кода и его интеграций**, для создания производительного и
+ресурсоэффективного кода.
+
+Большинству кода не нужны подсказки по размеру или предварительное выделение, и
+он может позволить среде выполнения увеличивать срез или карту по мере
+необходимости. Допустимо предварительно выделять память, когда окончательный
+размер известен (например, при преобразовании между map и срезом), но это не
+является требованием читаемости и может не стоить загромождения в простых
+случаях.
+
+**Предупреждение:** Предварительное выделение больше памяти, чем нужно, может
+тратить память в парке (fleet) или даже вредить производительности. В случае
+сомнений см. [GoTip #3: Benchmarking Go Code] и по умолчанию используйте
+[инициализацию нулевым значением](#vardeclzero) или [объявление составным
+литералом](#vardeclcomposite).
+
+[GoTip #3: Benchmarking Go Code]:
+ https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="decl-chan"></a>
+
+### Направление каналов (Channel direction)
+
+Указывайте [направление канала (channel direction)] там, где это возможно.
+
+```go
+// Хорошо:
+// sum вычисляет сумму всех значений. Она читает из канала до тех пор,
+// пока канал не закроется.
+func sum(values <-chan int) int {
+ // ...
+}
+```
+
+Это предотвращает случайные ошибки программирования, которые возможны без
+спецификации:
+
+```go
+// Плохо:
+func sum(values chan int) (out int) {
+ for v := range values {
+ out += v
+ }
+ // values уже должен быть закрыт для достижения этого кода, что означает,
+ // что второе закрытие вызовет панику.
+ close(values)
+}
+```
+
+Когда направление указано, компилятор перехватывает простые ошибки, подобные
+этой. Это также помогает передать меру владения (ownership) типу.
+
+См. также доклад Брайана Миллса "Rethinking Classical Concurrency Patterns":
+[слайды][rethinking-concurrency-slides] [видео][rethinking-concurrency-video].
+
+[rethinking-concurrency-slides]:
+ https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view?usp=sharing
+[rethinking-concurrency-video]: https://www.youtube.com/watch?v=5zXAHh5tJqQ
+[направление канала (channel direction)]: https://go.dev/ref/spec#Channel_types
+
+<a id="funcargs"></a>
+
+## Списки аргументов функций (Function argument lists)
+
+Не позволяйте сигнатуре функции становиться слишком длинной. По мере добавления
+большего количества параметров в функцию роль отдельных параметров становится
+менее ясной, а соседние параметры одного типа становится легче спутать. Функции
+с большим количеством аргументов менее запоминаемы и их труднее читать в месте
+вызова.
+
+При проектировании API рассмотрите возможность разделения высоконастраиваемой
+функции, сигнатура которой становится сложной, на несколько более простых. Они
+могут использовать общую (неэкспортируемую) реализацию, если это необходимо.
+
+Если функции требуется много входных данных, рассмотрите возможность введения
+[структуры опций (option struct)] для некоторых аргументов или использование
+более продвинутой техники [вариативных опций (variadic options)]. Основным
+критерием выбора стратегии должно быть то, как выглядит вызов функции во всех
+ожидаемых случаях использования.
+
+Приведенные ниже рекомендации в первую очередь применяются к экспортированным
+API, к которым предъявляются более высокие стандарты, чем к неэкспортированным.
+Эти методы могут быть не нужны для вашего случая использования. Используйте свое
+суждение и балансируйте между принципами [ясности (clarity)] и [наименьшей
+механизации (least mechanism)].
+
+См. также: [Go Tip #24: Use Case-Specific
+Constructions](https://google.github.io/styleguide/go/index.html#gotip)
+
+[структуры опций (option struct)]: #option-structure
+[вариативных опций (variadic options)]: #variadic-options
+[ясности (clarity)]: guide#clarity
+[наименьшей механизации (least mechanism)]: guide#least-mechanism
+
+<a id="option-structure"></a>
+
+### Структура опций (Option structure)
+
+Структура опций (option structure) — это тип struct, который собирает некоторые
+или все аргументы функции или метода, а затем передается в качестве последнего
+аргумента функции или методу. (Структура должна быть экспортирована только если
+она используется в экспортированной функции.)
+
+Использование структуры опций имеет ряд преимуществ:
+
+* Литерал структуры включает как поля, так и значения для каждого аргумента,
+ что делает их самодокументированными и затрудняет их перестановку.
+* Несущественные или "значения по умолчанию" поля могут быть опущены.
+* Вызывающие стороны могут совместно использовать структуру опций и писать
+ вспомогательные функции для работы с ней.
+* Структуры обеспечивают более чистую документацию для каждого поля, чем
+ аргументы функций.
+* Структуры опций могут расти со временем без влияния на места вызова.
+
+Вот пример функции, которую можно улучшить:
+
+```go
+// Плохо:
+func EnableReplication(ctx context.Context, config *replicator.Config, primaryRegions, readonlyRegions []string, replicateExisting, overwritePolicies bool, replicationInterval time.Duration, copyWorkers int, healthWatcher health.Watcher) {
+ // ...
+}
+```
+
+Функция выше может быть переписана со структурой опций следующим образом:
+
+```go
+// Хорошо:
+type ReplicationOptions struct {
+ Config *replicator.Config
+ PrimaryRegions []string
+ ReadonlyRegions []string
+ ReplicateExisting bool
+ OverwritePolicies bool
+ ReplicationInterval time.Duration
+ CopyWorkers int
+ HealthWatcher health.Watcher
+}
+
+func EnableReplication(ctx context.Context, opts ReplicationOptions) {
+ // ...
+}
+```
+
+Затем функцию можно вызвать в другом пакете:
+
+```go
+// Хорошо:
+func foo(ctx context.Context) {
+ // Сложный вызов:
+ storage.EnableReplication(ctx, storage.ReplicationOptions{
+ Config: config,
+ PrimaryRegions: []string{"us-east1", "us-central2", "us-west3"},
+ ReadonlyRegions: []string{"us-east5", "us-central6"},
+ OverwritePolicies: true,
+ ReplicationInterval: 1 * time.Hour,
+ CopyWorkers: 100,
+ HealthWatcher: watcher,
+ })
+
+ // Простой вызов:
+ storage.EnableReplication(ctx, storage.ReplicationOptions{
+ Config: config,
+ PrimaryRegions: []string{"us-east1", "us-central2", "us-west3"},
+ })
+}
+```
+
+**Примечание:** [Контексты никогда не включаются в структуры
+опций](https://neonxp.ru/pages/gostyleguide/google/decisions/#contexts).
+
+Этот вариант часто предпочтителен, когда применимо одно из следующих условий:
+
+* Все вызывающие стороны должны указать одну или несколько опций.
+* Большому количеству вызывающих сторон необходимо предоставить множество
+ опций.
+* Опции используются совместно несколькими функциями, которые будет вызывать
+ пользователь.
+
+<a id="variadic-options"></a>
+
+### Вариативные опции (Variadic options)
+
+Используя вариативные опции, создаются экспортированные функции, которые
+возвращают замыкания (closures), которые могут быть переданы в [вариативный
+(`...`) параметр] функции. Функция принимает в качестве параметров значения
+опции (если есть), а возвращаемое замыкание принимает изменяемую ссылку (обычно
+указатель на тип struct), которая будет обновлена на основе входных данных.
+
+[вариативный (`...`) параметр]:
+ https://golang.org/ref/spec#Passing_arguments_to_..._parameters
+
+Использование вариативных опций может предоставить ряд преимуществ:
+
+* Опции не занимают места в месте вызова, когда конфигурация не нужна.
+* Опции все еще являются значениями, поэтому вызывающие стороны могут делиться
+ ими, писать вспомогательные функции и накапливать их.
+* Опции могут принимать несколько параметров (например,
+ `cartesian.Translate(dx, dy int) TransformOption`).
+* Функции опций могут возвращать именованный тип, чтобы группировать опции
+ вместе в godoc.
+* Пакеты могут разрешать (или запрещать) сторонним пакетам определять (или
+ запрещать определение) свои собственные опции.
+
+**Примечание:** Использование вариативных опций требует значительного количества
+дополнительного кода (см. следующий пример), поэтому их следует использовать
+только тогда, когда преимущества перевешивают накладные расходы.
+
+Вот пример функции, которую можно улучшить:
+
+```go
+// Плохо:
+func EnableReplication(ctx context.Context, config *placer.Config, primaryCells, readonlyCells []string, replicateExisting, overwritePolicies bool, replicationInterval time.Duration, copyWorkers int, healthWatcher health.Watcher) {
+ ...
+}
+```
+
+Пример выше может быть переписан с вариативными опциями следующим образом:
+
+```go
+// Хорошо:
+type replicationOptions struct {
+ readonlyCells []string
+ replicateExisting bool
+ overwritePolicies bool
+ replicationInterval time.Duration
+ copyWorkers int
+ healthWatcher health.Watcher
+}
+
+// ReplicationOption настраивает EnableReplication.
+type ReplicationOption func(*replicationOptions)
+
+// ReadonlyCells добавляет дополнительные ячейки, которые дополнительно
+// должны содержать реплики только для чтения данных.
+//
+// Передача этой опции несколько раз добавит дополнительные
+// ячейки только для чтения.
+//
+// По умолчанию: нет
+func ReadonlyCells(cells ...string) ReplicationOption {
+ return func(opts *replicationOptions) {
+ opts.readonlyCells = append(opts.readonlyCells, cells...)
+ }
+}
+
+// ReplicateExisting контролирует, будут ли файлы, уже существующие в
+// первичных ячейках, реплицированы. В противном случае только недавно добавленные
+// файлы будут кандидатами на репликацию.
+//
+// Повторная передача этой опции перезапишет предыдущие значения.
+//
+// По умолчанию: false
+func ReplicateExisting(enabled bool) ReplicationOption {
+ return func(opts *replicationOptions) {
+ opts.replicateExisting = enabled
+ }
+}
+
+// ... другие опции ...
+
+// DefaultReplicationOptions управляют значениями по умолчанию перед
+// применением опций, переданных в EnableReplication.
+var DefaultReplicationOptions = []ReplicationOption{
+ OverwritePolicies(true),
+ ReplicationInterval(12 * time.Hour),
+ CopyWorkers(10),
+}
+
+func EnableReplication(ctx context.Context, config *placer.Config, primaryCells []string, opts ...ReplicationOption) {
+ var options replicationOptions
+ for _, opt := range DefaultReplicationOptions {
+ opt(&options)
+ }
+ for _, opt := range opts {
+ opt(&options)
+ }
+}
+```
+
+Затем функцию можно вызвать в другом пакете:
+
+```go
+// Хорошо:
+func foo(ctx context.Context) {
+ // Сложный вызов:
+ storage.EnableReplication(ctx, config, []string{"po", "is", "ea"},
+ storage.ReadonlyCells("ix", "gg"),
+ storage.OverwritePolicies(true),
+ storage.ReplicationInterval(1*time.Hour),
+ storage.CopyWorkers(100),
+ storage.HealthWatcher(watcher),
+ )
+
+ // Простой вызов:
+ storage.EnableReplication(ctx, config, []string{"po", "is", "ea"})
+}
+```
+
+Предпочитайте этот вариант, когда применимо большинство из следующего:
+
+* Большинству вызывающих сторон не нужно указывать никакие опции.
+* Большинство опций используется редко.
+* Существует большое количество опций.
+* Опции требуют аргументов.
+* Опции могут завершиться неудачей или быть установлены неправильно (в этом
+ случае функция опции возвращает `error`).
+* Опции требуют большого количества документации, которую трудно уместить в
+ структуре.
+* Пользователи или другие пакеты могут предоставлять пользовательские опции.
+
+Опции в этом стиле должны принимать параметры, а не использовать наличие
+(presence) для сигнализации своего значения; последнее может значительно
+усложнить динамическое составление аргументов. Например, двоичные настройки
+должны принимать логическое значение (например, `rpc.FailFast(enable bool)`
+предпочтительнее, чем `rpc.EnableFailFast()`). Перечисляемая опция должна
+принимать перечисляемую константу (например, `log.Format(log.Capacitor)`
+предпочтительнее, чем `log.CapacitorFormat()`). Альтернатива значительно
+усложняет жизнь пользователям, которые должны программно выбирать, какие опции
+передавать; такие пользователи вынуждены изменять фактический состав параметров,
+а не просто изменять аргументы опций. Не предполагайте, что все пользователи
+будут статически знать полный набор опций.
+
+Как правило, опции должны обрабатываться по порядку. Если возникает конфликт или
+если некумулятивная опция передается несколько раз, должен побеждать последний
+аргумент.
+
+Параметр функции опции в этом шаблоне обычно не экспортируется, чтобы ограничить
+определение опций только самим пакетом. Это хороший вариант по умолчанию, хотя
+могут быть случаи, когда уместно позволить другим пакетам определять опции.
+
+См. [оригинальный пост в блоге Роба Пайка] и [доклад Дейва Ченея] для более
+глубокого изучения того, как эти опции могут быть использованы.
+
+[оригинальный пост в блоге Роба Пайка]:
+ http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
+[доклад Дейва Ченея]:
+ https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
+
+<a id="complex-clis"></a>
+
+## Сложные интерфейсы командной строки (Complex command-line interfaces)
+
+Некоторые программы хотят предоставить пользователям богатый интерфейс командной
+строки, включающий подкоманды. Например, `kubectl create`, `kubectl run` и
+многие другие подкоманды предоставляются программой `kubectl`. Существует по
+крайней мере следующие общеупотребительные библиотеки для достижения этого.
+
+Если у вас нет предпочтений или другие соображения равны, рекомендуется
+[subcommands], поскольку она самая простая и с ней легко работать правильно.
+Однако, если вам нужны другие функции, которые она не предоставляет, выберите
+один из других вариантов.
+
+* **[cobra]**
+
+ * Соглашение о флагах: getopt
+ * Распространена за пределами кодовой базы Google.
+ * Много дополнительных функций.
+ * Подводные камни в использовании (см. ниже).
+
+* **[subcommands]**
+
+ * Соглашение о флагах: Go
+ * Проста и с ней легко работать правильно.
+ * Рекомендуется, если вам не нужны дополнительные функции.
+
+**Предупреждение**: функции команд cobra должны использовать `cmd.Context()` для
+получения контекста, а не создавать свой собственный корневой контекст с помощью
+`context.Background`. Код, использующий пакет subcommands, уже получает
+правильный контекст как параметр функции.
+
+Вы не обязаны помещать каждую подкоманду в отдельный пакет, и часто в этом нет
+необходимости. Применяйте те же соображения о границах пакетов, что и в любой
+кодовой базе Go. Если ваш код может использоваться как библиотека и как бинарный
+файл, обычно полезно отделить CLI-код от библиотеки, делая CLI просто еще одним
+из ее клиентов. (Это не специфично для CLI с подкомандами, но упоминается здесь,
+потому что это частое место, где это возникает.)
+
+[subcommands]: https://pkg.go.dev/github.com/google/subcommands
+[cobra]: https://pkg.go.dev/github.com/spf13/cobra
+
+<a id="tests"></a>
+
+## Тесты (Tests)
+
+<a id="test-functions"></a>
+
+### Оставляйте тестирование функции `Test`
+
+<!-- Примечание для сопровождающих: Этот раздел пересекается с decisions#assert и
+decisions#mark-test-helpers. Цель не в том, чтобы повторять информацию, а
+в том, чтобы иметь одно место, которое суммирует различие, о котором часто
+задумываются новички в языке. -->
+
+Go различает "тестовые помощники (test helpers)" и "помощники утверждений
+(assertion helpers)":
+
+* **Тестовые помощники** — это функции, которые выполняют задачи настройки или
+ очистки. Все сбои, которые происходят в тестовых помощниках, ожидаемо
+ являются сбоями окружения (а не тестируемого кода) — например, когда
+ тестовая база данных не может быть запущена, потому что на этой машине
+ больше нет свободных портов. Для таких функций часто уместно вызывать
+ `t.Helper`, чтобы [пометить их как тестовый помощник]. См. [обработку ошибок
+ в тестовых помощниках] для более подробной информации.
+
+* **Помощники утверждений** — это функции, которые проверяют правильность
+ системы и завершают тест с ошибкой, если ожидание не выполняется. Помощники
+ утверждений [не считаются идиоматичными] в Go.
+
+Цель теста — сообщить о условиях прохождения/непрохождения тестируемого кода.
+Идеальное место для завершения теста с ошибкой — внутри самой функции `Test`,
+так как это обеспечивает ясность [сообщений об ошибках] и логики теста.
+
+[пометить их как тестовый помощник]: decisions#mark-test-helpers
+[обработку ошибок в тестовых помощниках]: #test-helper-error-handling
+[не считаются идиоматичными]: decisions#assert
+[сообщений об ошибках]: decisions#useful-test-failures
+
+По мере роста вашего тестового кода может стать необходимым вынести некоторую
+функциональность в отдельные функции. Стандартные соображения программной
+инженерии все еще применяются, поскольку *тестовый код — это все еще код*. Если
+функциональность не взаимодействует с тестовым фреймворком, то применяются все
+обычные правила. Однако, когда общий код взаимодействует с фреймворком,
+необходимо соблюдать осторожность, чтобы избежать распространенных подводных
+камней, которые могут привести к неинформативным сообщениям об ошибках и
+неудобным в поддержке тестам.
+
+Если многим отдельным тестовым случаям требуется одна и та же логика валидации,
+организуйте тест одним из следующих способов вместо использования помощников
+утверждений или сложных функций валидации:
+
+* Встройте логику (и валидацию, и завершение с ошибкой) в функцию `Test`, даже
+ если это повторяется. Это лучше всего работает в простых случаях.
+* Если входные данные похожи, рассмотрите возможность объединения их в
+ [табличный тест (table-driven test)], сохраняя логику встроенной в цикл. Это
+ помогает избежать повторения, сохраняя валидацию и завершение с ошибкой в
+ `Test`.
+* Если есть несколько вызывающих сторон, которым нужна одна и та же функция
+ валидации, но табличные тесты не подходят (обычно потому, что входные данные
+ недостаточно просты или валидация требуется как часть последовательности
+ операций), организуйте функцию валидации так, чтобы она возвращала значение
+ (обычно `error`), а не принимала параметр `testing.T` и использовала его для
+ завершения теста с ошибкой. Используйте логику внутри `Test`, чтобы решить,
+ завершать ли тест с ошибкой, и предоставить [полезные сообщения об ошибках
+ теста]. Вы также можете создать тестовые помощники для выноса общего
+ шаблонного кода настройки.
+
+Дизайн, описанный в последнем пункте, сохраняет ортогональность. Например,
+[пакет `cmp`] не предназначен для завершения тестов с ошибкой, а для сравнения
+(и вычисления различий) значений. Поэтому ему не нужно знать о контексте, в
+котором было сделано сравнение, поскольку вызывающая сторона может предоставить
+его. Если ваш общий тестовый код предоставляет `cmp.Transformer` для вашего типа
+данных, это часто может быть самым простым дизайном. Для других проверок
+рассмотрите возможность возврата значения `error`.
+
+```go
+// Хорошо:
+// polygonCmp возвращает cmp.Option, которое приравнивает объекты геометрии s2
+// с некоторой небольшой ошибкой с плавающей точкой.
+func polygonCmp() cmp.Option {
+ return cmp.Options{
+ cmp.Transformer("polygon", func(p *s2.Polygon) []*s2.Loop { return p.Loops() }),
+ cmp.Transformer("loop", func(l *s2.Loop) []s2.Point { return l.Vertices() }),
+ cmpopts.EquateApprox(0.00000001, 0),
+ cmpopts.EquateEmpty(),
+ }
+}
+
+func TestFenceposts(t *testing.T) {
+ // Это тест для вымышленной функции Fenceposts, которая рисует забор
+ // вокруг некоторого объекта Place. Детали не важны, за исключением того,
+ // что результат — это некоторый объект, имеющий геометрию s2 (github.com/golang/geo/s2)
+ got := Fencepost(tomsDiner, 1*meter)
+ if diff := cmp.Diff(want, got, polygonCmp()); diff != "" {
+ t.Errorf("Fencepost(tomsDiner, 1m) returned unexpected diff (-want+got):\n%v", diff)
+ }
+}
+
+func FuzzFencepost(f *testing.F) {
+ // Фаззинг-тест (https://go.dev/doc/fuzz) для того же.
+
+ f.Add(tomsDiner, 1*meter)
+ f.Add(school, 3*meter)
+
+ f.Fuzz(func(t *testing.T, geo Place, padding Length) {
+ got := Fencepost(geo, padding)
+ // Простая эталонная реализация: не используется в prod, но проста для
+ // понимания и поэтому полезна для проверки в случайных тестах.
+ reference := slowFencepost(geo, padding)
+
+ // Во фаззинг-тесте входные и выходные данные могут быть большими, поэтому
+ // не беспокойтесь о печати diff. cmp.Equal достаточно.
+ if !cmp.Equal(got, reference, polygonCmp()) {
+ t.Errorf("Fencepost returned wrong placement")
+ }
+ })
+}
+```
+
+Функция `polygonCmp` агностична относительно того, как ее вызывают; она не
+принимает конкретный тип входных данных и не контролирует, что делать, если два
+объекта не совпадают. Поэтому больше вызывающих сторон могут использовать ее.
+
+**Примечание:** Существует аналогия между тестовыми помощниками и обычным
+библиотечным кодом. Код в библиотеках обычно [не должен вызывать panic] за
+редкими исключениями; код, вызываемый из теста, не должен останавливать тест,
+если нет [смысла продолжать].
+
+[табличный тест (table-driven test)]: decisions#table-driven-tests
+[полезные сообщения об ошибках теста]: decisions#useful-test-failures
+[пакет `cmp`]: https://pkg.go.dev/github.com/google/go-cmp/cmp
+[не должен вызывать panic]: decisions#dont-panic
+[смысла продолжать]: #t-fatal
+
+<a id="test-validation-apis"></a>
+
+### Проектирование расширяемых API валидации (Designing extensible validation APIs)
+
+Большая часть советов о тестировании в руководстве по стилю касается
+тестирования вашего собственного кода. Этот раздел о том, как предоставить
+средства для других людей тестировать код, который они пишут, чтобы убедиться,
+что он соответствует требованиям вашей библиотеки.
+
+<a id="test-validation-apis-what"></a>
+
+#### Приемочное тестирование (Acceptance testing)
+
+Такое тестирование называется [приемочным тестированием (acceptance testing)].
+Предпосылка такого тестирования заключается в том, что человек, использующий
+тест, не знает всех деталей того, что происходит в тесте; он просто передает
+входные данные в тестовое средство, чтобы оно выполнило работу. Это можно
+рассматривать как форму [инверсии управления (inversion of control)].
+
+В типичном тесте Go тестовая функция контролирует поток программы, и
+рекомендации [без утверждений (no assert)](https://neonxp.ru/pages/gostyleguide/google/decisions/#assert) и [тестовые
+функции](#test-functions) побуждают вас сохранять это так. Этот раздел
+объясняет, как создавать поддержку для таких тестов способом, согласующимся со
+стилем Go.
+
+Прежде чем углубляться в "как", рассмотрим пример из [`io/fs`], приведенный
+ниже:
+
+```go
+type FS interface {
+ Open(name string) (File, error)
+}
+```
+
+Хотя существуют хорошо известные реализации `fs.FS`, от разработчика Go может
+потребоваться создать свою. Чтобы помочь проверить правильность пользовательской
+реализации `fs.FS`, была предоставлена универсальная библиотека в
+[`testing/fstest`] под названием [`fstest.TestFS`]. Этот API рассматривает
+реализацию как черный ящик (blackbox), чтобы убедиться, что она соблюдает самые
+основные части контракта `io/fs`.
+
+[приемочным тестированием (acceptance testing)]:
+ https://en.wikipedia.org/wiki/Acceptance_testing
+[инверсии управления (inversion of control)]:
+ https://en.wikipedia.org/wiki/Inversion_of_control
+[`io/fs`]: https://pkg.go.dev/io/fs
+[`testing/fstest`]: https://pkg.go.dev/testing/fstest
+[`fstest.TestFS`]: https://pkg.go.dev/testing/fstest#TestFS
+
+<a id="test-validation-apis-writing"></a>
+
+#### Написание приемочного теста (Writing an acceptance test)
+
+Теперь, когда мы знаем, что такое приемочный тест и почему вы можете его
+использовать, давайте рассмотрим создание приемочного теста для `package chess`,
+пакета, используемого для симуляции шахматных игр. Пользователи `chess` должны
+реализовать интерфейс `chess.Player`. Эти реализации — основное, что мы будем
+проверять. Наш приемочный тест касается того, делает ли реализация игрока
+легальные ходы, а не того, являются ли ходы умными.
+
+1. Создайте новый пакет для поведения валидации, [обычно
+ именуемый](#naming-doubles-helper-package) добавлением слова `test` к имени
+ пакета (например, `chesstest`).
+
+1. Создайте функцию, которая выполняет валидацию, принимая тестируемую
+ реализацию в качестве аргумента и проверяя ее:
+
+ ```go
+ // ExercisePlayer тестирует реализацию Player за один ход на доске.
+ // Сама доска выборочно проверяется на разумность и правильность.
+ //
+ // Возвращает nil ошибку, если игрок делает правильный ход в контексте
+ // предоставленной доски. В противном случае ExercisePlayer возвращает одну из
+ // ошибок этого пакета, чтобы указать, как и почему игрок не прошел валидацию.
+ func ExercisePlayer(b *chess.Board, p chess.Player) error
+ ```
+
+ Тест должен отмечать, какие инварианты нарушены и как. Ваш дизайн может
+ выбрать одну из двух дисциплин для сообщения о сбоях:
+
+ * **Завершение при первой ошибке (Fail fast)**: возвращать ошибку, как
+ только реализация нарушает инвариант.
+
+ Это самый простой подход, и он хорошо работает, если ожидается, что
+ приемочный тест будет выполняться быстро. Простые [сторожевые ошибки
+ (sentinels)] и [пользовательские типы] могут быть легко использованы
+ здесь, что, в свою очередь, облегчает тестирование самого приемочного
+ теста.
+
+ ```go
+ for color, army := range b.Armies {
+ // Король никогда не должен покидать доску, потому что игра заканчивается
+ // матом.
+ if army.King == nil {
+ return &MissingPieceError{Color: color, Piece: chess.King}
+ }
+ }
+ ```
+
+ * **Агрегация всех сбоев (Aggregate all failures)**: собирать все сбои и
+ сообщать о них всех.
+
+ Этот подход напоминает рекомендацию [продолжать выполнение (keep going)]
+ и может быть предпочтительнее, если ожидается, что приемочный тест будет
+ выполняться медленно.
+
+ То, как вы агрегируете сбои, должно определяться тем, хотите ли вы дать
+ пользователям или себе возможность исследовать отдельные сбои (например,
+ для тестирования вашего приемочного теста). Ниже демонстрируется
+ использование [пользовательского типа ошибки][пользовательские типы],
+ который [агрегирует ошибки]:
+
+ ```go
+ var badMoves []error
+
+ move := p.Move()
+ if putsOwnKingIntoCheck(b, move) {
+ badMoves = append(badMoves, PutsSelfIntoCheckError{Move: move})
+ }
+
+ if len(badMoves) > 0 {
+ return SimulationError{BadMoves: badMoves}
+ }
+ return nil
+ ```
+
+Приемочный тест должен соблюдать рекомендацию [продолжать выполнение (keep
+going)], не вызывая `t.Fatal`, если тест не обнаруживает нарушение инварианта в
+системе, которая тестируется.
+
+Например, `t.Fatal` должен быть зарезервирован для исключительных случаев, таких
+как [сбой настройки](#test-helper-error-handling), как обычно:
+
+```go
+func ExerciseGame(t *testing.T, cfg *Config, p chess.Player) error {
+ t.Helper()
+
+ if cfg.Simulation == Modem {
+ conn, err := modempool.Allocate()
+ if err != nil {
+ t.Fatalf("No modem for the opponent could be provisioned: %v", err)
+ }
+ t.Cleanup(func() { modempool.Return(conn) })
+ }
+ // Запустить приемочный тест (целую игру).
+}
+```
+
+Эта техника может помочь вам создавать лаконичные, каноничные проверки. Но не
+пытайтесь использовать ее, чтобы обойти [рекомендации об
+утверждениях](https://neonxp.ru/pages/gostyleguide/google/decisions/#assert).
+
+Конечный продукт должен быть похож на этот для конечных пользователей:
+
+```go
+// Хорошо:
+package deepblue_test
+
+import (
+ "chesstest"
+ "deepblue"
+)
+
+func TestAcceptance(t *testing.T) {
+ player := deepblue.New()
+ err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player)
+ if err != nil {
+ t.Errorf("Deep Blue player failed acceptance test: %v", err)
+ }
+}
+```
+
+[сторожевые ошибки (sentinels)]:
+ https://google.github.io/styleguide/go/index.html#gotip
+[пользовательские типы]: https://google.github.io/styleguide/go/index.html#gotip
+[агрегирует ошибки]: https://google.github.io/styleguide/go/index.html#gotip
+
+<a id="use-real-transports"></a>
+
+### Используйте реальные транспорты (Use real transports)
+
+При тестировании интеграции компонентов, особенно когда HTTP или RPC
+используются в качестве базового транспорта между компонентами, предпочитайте
+использовать реальный базовый транспорт для подключения к тестовой версии
+бэкенда.
+
+Например, предположим, что код, который вы хотите протестировать (иногда
+называемый "системой под тестом" или SUT), взаимодействует с бэкендом,
+реализующим API [долго выполняющихся операций (long running operations)]. Чтобы
+протестировать ваш SUT, используйте реальный [OperationsClient], подключенный к
+[тестовому двойнику (test
+double)](https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts)
+(например, моку, заглушке или фейку) [OperationsServer].
+
+[тестовому двойнику (test double)]:
+ https://abseil.io/resources/swe-book/html/ch13.html#basic_concepts
+[долго выполняющихся операций (long running operations)]:
+ https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning
+[OperationsClient]:
+ https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning#OperationsClient
+[OperationsServer]:
+ https://pkg.go.dev/google.golang.org/genproto/googleapis/longrunning#OperationsServer
+
+Это рекомендуется вместо ручной реализации клиента из-за сложности правильной
+имитации поведения клиента. Используя production-клиент с тестовым сервером, вы
+гарантируете, что ваш тест использует как можно больше реального кода.
+
+**Совет:** По возможности используйте тестовую библиотеку, предоставленную
+авторами тестируемого сервиса.
+
+<a id="t-fatal"></a>
+
+### `t.Error` против `t.Fatal`
+
+Как обсуждалось в [решениях](https://neonxp.ru/pages/gostyleguide/google/decisions/#keep-going), тесты, как правило, не
+должны прерываться при первой встреченной проблеме.
+
+Однако некоторые ситуации требуют, чтобы тест не продолжался. Вызов `t.Fatal`
+уместен, когда какая-то часть настройки теста завершается неудачей, особенно во
+[вспомогательных функциях настройки теста], без которых вы не можете запустить
+остальную часть теста. В табличном тесте `t.Fatal` уместен для сбоев, которые
+настраивают всю тестовую функцию до начала цикла теста. Сбои, которые
+затрагивают одну запись в таблице теста и делают невозможным продолжение работы
+с этой записью, должны сообщаться следующим образом:
+
+* Если вы не используете подтесты `t.Run`, используйте `t.Error`, за которым
+ следует оператор `continue` для перехода к следующей записи таблицы.
+* Если вы используете подтесты (и вы внутри вызова `t.Run`), используйте
+ `t.Fatal`, который завершает текущий подтест и позволяет вашему тестовому
+ случаю перейти к следующему подтесту.
+
+**Предупреждение:** Не всегда безопасно вызывать `t.Fatal` и подобные функции.
+[Подробнее здесь](#t-fatal-goroutine).
+
+[вспомогательных функциях настройки теста]: #test-helper-error-handling
+
+<a id="test-helper-error-handling"></a>
+
+### Обработка ошибок во вспомогательных тестовых функциях (Error handling in test helpers)
+
+**Примечание:** В этом разделе обсуждаются [тестовые помощники (test helpers)] в
+том смысле, в котором Go использует этот термин: функции, которые выполняют
+настройку и очистку теста, а не общие средства утверждений. См. раздел [тестовые
+функции](#test-functions) для более подробного обсуждения.
+
+[тестовые помощники (test helpers)]: decisions#mark-test-helpers
+
+Операции, выполняемые тестовым помощником, иногда завершаются неудачей.
+Например, настройка каталога с файлами включает ввод-вывод, который может
+завершиться неудачей. Когда тестовые помощники завершаются неудачей, их сбой
+часто означает, что тест не может продолжиться, поскольку не выполнилось
+предварительное условие настройки. Когда это происходит, предпочтительнее
+вызвать одну из функций `Fatal` в помощнике:
+
+```go
+// Хорошо:
+func mustAddGameAssets(t *testing.T, dir string) {
+ t.Helper()
+ if err := os.WriteFile(path.Join(dir, "pak0.pak"), pak0, 0644); err != nil {
+ t.Fatalf("Setup failed: could not write pak0 asset: %v", err)
+ }
+ if err := os.WriteFile(path.Join(dir, "pak1.pak"), pak1, 0644); err != nil {
+ t.Fatalf("Setup failed: could not write pak1 asset: %v", err)
+ }
+}
+```
+
+Это делает вызывающую сторону чище, чем если бы помощник возвращал ошибку самому
+тесту:
+
+```go
+// Плохо:
+func addGameAssets(t *testing.T, dir string) error {
+ t.Helper()
+ if err := os.WriteFile(path.Join(d, "pak0.pak"), pak0, 0644); err != nil {
+ return err
+ }
+ if err := os.WriteFile(path.Join(d, "pak1.pak"), pak1, 0644); err != nil {
+ return err
+ }
+ return nil
+}
+```
+
+**Предупреждение:** Не всегда безопасно вызывать `t.Fatal` и подобные функции.
+[Подробнее](#t-fatal-goroutine) здесь.
+
+Сообщение об ошибке должно включать описание того, что произошло. Это важно, так
+как вы можете предоставлять тестовый API многим пользователям, особенно с
+увеличением количества шагов, производящих ошибки, в помощнике. Когда тест
+завершается неудачей, пользователь должен знать, где и почему.
+
+**Совет:** Go 1.14 представила функцию [`t.Cleanup`], которую можно использовать
+для регистрации функций очистки, которые запускаются при завершении вашего
+теста. Функция также работает с тестовыми помощниками. См. [GoTip #4: Cleaning
+Up Your Tests](https://google.github.io/styleguide/go/index.html#gotip) для
+рекомендаций по упрощению тестовых помощников.
+
+Сниппет ниже в вымышленном файле `paint_test.go` демонстрирует, как
+`(*testing.T).Helper` влияет на сообщение об ошибке в тесте Go:
+
+```go
+package paint_test
+
+import (
+ "fmt"
+ "testing"
+)
+
+func paint(color string) error {
+ return fmt.Errorf("no %q paint today", color)
+}
+
+func badSetup(t *testing.T) {
+ // Здесь должен быть вызов t.Helper, но его нет.
+ if err := paint("taupe"); err != nil {
+ t.Fatalf("Could not paint the house under test: %v", err) // строка 15
+ }
+}
+
+func goodSetup(t *testing.T) {
+ t.Helper()
+ if err := paint("lilac"); err != nil {
+ t.Fatalf("Could not paint the house under test: %v", err)
+ }
+}
+
+func TestBad(t *testing.T) {
+ badSetup(t)
+ // ...
+}
+
+func TestGood(t *testing.T) {
+ goodSetup(t) // строка 32
+ // ...
+}
+```
+
+Вот пример вывода при запуске. Обратите внимание на выделенный текст и на то,
+как он отличается:
+
+```text
+=== RUN TestBad
+ paint_test.go:15: Could not paint the house under test: no "taupe" paint today
+--- FAIL: TestBad (0.00s)
+=== RUN TestGood
+ paint_test.go:32: Could not paint the house under test: no "lilac" paint today
+--- FAIL: TestGood (0.00s)
+FAIL
+```
+
+Ошибка с `paint_test.go:15` относится к строке функции настройки, которая
+завершилась неудачей в `badSetup`:
+
+`t.Fatalf("Could not paint the house under test: %v", err)`
+
+Тогда как `paint_test.go:32` относится к строке теста, которая завершилась
+неудачей в `TestGood`:
+
+`goodSetup(t)`
+
+Правильное использование `(*testing.T).Helper` гораздо лучше определяет
+местоположение сбоя, когда:
+
+* вспомогательные функции растут
+* вспомогательные функции вызывают другие вспомогательные функции
+* количество использований вспомогательных функций в тестовых функциях растет
+
+**Совет:** Если вспомогательная функция вызывает `(*testing.T).Error` или
+`(*testing.T).Fatal`, предоставьте некоторый контекст в строке формата, чтобы
+помочь определить, что пошло не так и почему.
+
+**Совет:** Если ничто из того, что делает помощник, не может привести к неудаче
+теста, ему не нужно вызывать `t.Helper`. Упростите его сигнатуру, удалив `t` из
+списка параметров функции.
+
+[`t.Cleanup`]: https://pkg.go.dev/testing#T.Cleanup
+
+<a id="t-fatal-goroutine"></a>
+
+### Не вызывайте `t.Fatal` из отдельных горутин (Don't call `t.Fatal` from separate goroutines)
+
+Как [документировано в пакете testing](https://pkg.go.dev/testing#T),
+неправильно вызывать `t.FailNow`, `t.Fatal` и т.д. из любой горутины, кроме той,
+которая запускает функцию Test (или подтест). Если ваш тест запускает новые
+горутины, они не должны вызывать эти функции внутри этих горутин.
+
+[Тестовые помощники](#test-functions) обычно не сигнализируют о сбое из новых
+горутин, поэтому для них допустимо использовать `t.Fatal`. В случае сомнений
+вызовите `t.Error` и вернитесь.
+
+```go
+// Хорошо:
+func TestRevEngine(t *testing.T) {
+ engine, err := Start()
+ if err != nil {
+ t.Fatalf("Engine failed to start: %v", err)
+ }
+
+ num := 11
+ var wg sync.WaitGroup
+ wg.Add(num)
+ for i := 0; i < num; i++ {
+ go func() {
+ defer wg.Done()
+ if err := engine.Vroom(); err != nil {
+ // Здесь нельзя использовать t.Fatalf.
+ t.Errorf("No vroom left on engine: %v", err)
+ return
+ }
+ if rpm := engine.Tachometer(); rpm > 1e6 {
+ t.Errorf("Inconceivable engine rate: %d", rpm)
+ }
+ }()
+ }
+ wg.Wait()
+
+ if seen := engine.NumVrooms(); seen != num {
+ t.Errorf("engine.NumVrooms() = %d, want %d", seen, num)
+ }
+}
+```
+
+Добавление `t.Parallel` к тесту или подтесту не делает небезопасным вызов
+`t.Fatal`.
+
+Когда все вызовы API `testing` находятся в [тестовой функции](#test-functions),
+обычно легко заметить неправильное использование, потому что ключевое слово `go`
+легко увидеть. Передача аргументов `testing.T` усложняет отслеживание такого
+использования. Обычно причина передачи этих аргументов — введение тестового
+помощника, и они не должны зависеть от тестируемой системы. Поэтому, если
+тестовый помощник [регистрирует фатальную ошибку
+теста](#test-helper-error-handling), он может и должен делать это из горутины
+теста.
+
+<a id="t-field-names"></a>
+
+### Используйте имена полей в литералах структур (Use field names in struct literals)
+
+<a id="t-field-labels"></a>
+
+В табличных тестах предпочитайте указывать имена полей при инициализации
+литералов структур тестовых случаев. Это полезно, когда тестовые случаи
+охватывают большое вертикальное пространство (например, более 20-30 строк),
+когда есть соседние поля с одинаковым типом, а также когда вы хотите опустить
+поля, имеющие нулевое значение. Например:
+
+```go
+// Хорошо:
+func TestStrJoin(t *testing.T) {
+ tests := []struct {
+ slice []string
+ separator string
+ skipEmpty bool
+ want string
+ }{
+ {
+ slice: []string{"a", "b", ""},
+ separator: ",",
+ want: "a,b,",
+ },
+ {
+ slice: []string{"a", "b", ""},
+ separator: ",",
+ skipEmpty: true,
+ want: "a,b",
+ },
+ // ...
+ }
+ // ...
+}
+```
+
+<a id="t-common-setup-scope"></a>
+
+### Ограничивайте код настройки конкретными тестами (Keep setup code scoped to specific tests)
+
+По возможности настройка ресурсов и зависимостей должна быть максимально
+ограничена конкретными тестовыми случаями. Например, учитывая функцию настройки:
+
+```go
+// mustLoadDataSet загружает набор данных для тестов.
+//
+// Этот пример очень прост и легко читается. Часто реалистичная настройка более
+// сложная, подверженная ошибкам и потенциально медленная.
+func mustLoadDataset(t *testing.T) []byte {
+ t.Helper()
+ data, err := os.ReadFile("path/to/your/project/testdata/dataset")
+
+ if err != nil {
+ t.Fatalf("Could not load dataset: %v", err)
+ }
+ return data
+}
+```
+
+Вызовите `mustLoadDataset` явно в тестовых функциях, которые в этом нуждаются:
+
+```go
+// Хорошо:
+func TestParseData(t *testing.T) {
+ data := mustLoadDataset(t)
+ parsed, err := ParseData(data)
+ if err != nil {
+ t.Fatalf("Unexpected error parsing data: %v", err)
+ }
+ want := &DataTable{ /* ... */ }
+ if got := parsed; !cmp.Equal(got, want) {
+ t.Errorf("ParseData(data) = %v, want %v", got, want)
+ }
+}
+
+func TestListContents(t *testing.T) {
+ data := mustLoadDataset(t)
+ contents, err := ListContents(data)
+ if err != nil {
+ t.Fatalf("Unexpected error listing contents: %v", err)
+ }
+ want := []string{ /* ... */ }
+ if got := contents; !cmp.Equal(got, want) {
+ t.Errorf("ListContents(data) = %v, want %v", got, want)
+ }
+}
+
+func TestRegression682831(t *testing.T) {
+ if got, want := guessOS("zpc79.example.com"), "grhat"; got != want {
+ t.Errorf(`guessOS("zpc79.example.com") = %q, want %q`, got, want)
+ }
+}
+```
+
+Тестовая функция `TestRegression682831` не использует набор данных и поэтому не
+вызывает `mustLoadDataset`, которая может быть медленной и подверженной сбоям:
+
+```go
+// Плохо:
+var dataset []byte
+
+func TestParseData(t *testing.T) {
+ // Как описано выше без вызова mustLoadDataset напрямую.
+}
+
+func TestListContents(t *testing.T) {
+ // Как описано выше без вызова mustLoadDataset напрямую.
+}
+
+func TestRegression682831(t *testing.T) {
+ if got, want := guessOS("zpc79.example.com"), "grhat"; got != want {
+ t.Errorf(`guessOS("zpc79.example.com") = %q, want %q`, got, want)
+ }
+}
+
+func init() {
+ dataset = mustLoadDataset()
+}
+```
+
+Пользователь может захотеть запустить функцию изолированно от других и не должен
+быть наказан этими факторами:
+
+```shell
+# Нет причин для выполнения дорогой инициализации.
+$ go test -run TestRegression682831
+```
+
+<a id="t-custom-main"></a>
+
+#### Когда использовать пользовательскую точку входа `TestMain` (When to use a custom `TestMain` entrypoint)
+
+Если **все тесты в пакете** требуют общей настройки и **настройка требует
+очистки (teardown)**, вы можете использовать [пользовательскую точку входа
+testmain]. Это может произойти, если ресурс, требующийся тестовым случаям,
+особенно дорог в настройке, и стоимость должна быть амортизирована. Обычно к
+этому моменту вы уже убрали несвязанные тесты из набора тестов. Обычно это
+используется только для [функциональных тестов (functional tests)].
+
+Использование пользовательского `TestMain` **не должно быть вашим первым
+выбором** из-за количества осторожности, которое требуется для правильного
+использования. Сначала рассмотрите, достаточно ли решения в разделе
+[*амортизация общей настройки теста*] или обычного [тестового помощника] для
+ваших нужд.
+
+[пользовательскую точку входа testmain]:
+ https://golang.org/pkg/testing/#hdr-Main
+[функциональных тестов (functional tests)]:
+ https://en.wikipedia.org/wiki/Functional_testing
+[*амортизация общей настройки теста*]: #t-setup-amortization
+[тестового помощника]: #t-common-setup-scope
+
+```go
+// Хорошо:
+var db *sql.DB
+
+func TestInsert(t *testing.T) { /* omitted */ }
+
+func TestSelect(t *testing.T) { /* omitted */ }
+
+func TestUpdate(t *testing.T) { /* omitted */ }
+
+func TestDelete(t *testing.T) { /* omitted */ }
+
+// runMain устанавливает зависимости теста и в конечном итоге выполняет тесты.
+// Она определена как отдельная функция, чтобы этапы настройки могли четко
+// откладывать (defer) свои шаги очистки.
+func runMain(ctx context.Context, m *testing.M) (code int, err error) {
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ d, err := setupDatabase(ctx)
+ if err != nil {
+ return 0, err
+ }
+ defer d.Close() // Явно очищаем базу данных.
+ db = d // db определена как переменная на уровне пакета.
+
+ // m.Run() выполняет обычные, определенные пользователем тестовые функции.
+ // Любые операторы defer, которые были сделаны, будут выполнены после завершения m.Run().
+ return m.Run(), nil
+}
+
+func TestMain(m *testing.M) {
+ code, err := runMain(context.Background(), m)
+ if err != nil {
+ // Сообщения о сбоях должны записываться в STDERR, что и использует log.Fatal.
+ log.Fatal(err)
+ }
+ // ПРИМЕЧАНИЕ: операторы defer не выполняются после здесь из-за os.Exit
+ // завершающего процесс.
+ os.Exit(code)
+}
+```
+
+В идеале тестовый случай является герметичным (hermetic) между вызовами самого
+себя и между другими тестовыми случаями.
+
+По крайней мере, убедитесь, что отдельные тестовые случаи сбрасывают любое
+глобальное состояние, которое они изменили, если они это сделали (например, если
+тесты работают с внешней базой данных).
+
+<a id="t-setup-amortization"></a>
+
+#### Амортизация общей настройки теста (Amortizing common test setup)
+
+Использование `sync.Once` может быть уместным, хотя и не обязательно, если все
+из следующего верно для общей настройки:
+
+* Она дорогая.
+* Она применяется только к некоторым тестам.
+* Она не требует очистки.
+
+```go
+// Хорошо:
+var dataset struct {
+ once sync.Once
+ data []byte
+ err error
+}
+
+func mustLoadDataset(t *testing.T) []byte {
+ t.Helper()
+ dataset.once.Do(func() {
+ data, err := os.ReadFile("path/to/your/project/testdata/dataset")
+ // dataset определена как переменная на уровне пакета.
+ dataset.data = data
+ dataset.err = err
+ })
+ if err := dataset.err; err != nil {
+ t.Fatalf("Could not load dataset: %v", err)
+ }
+ return dataset.data
+}
+```
+
+Когда `mustLoadDataset` используется в нескольких тестовых функциях, ее
+стоимость амортизируется:
+
+```go
+// Хорошо:
+func TestParseData(t *testing.T) {
+ data := mustLoadDataset(t)
+
+ // Как описано выше.
+}
+
+func TestListContents(t *testing.T) {
+ data := mustLoadDataset(t)
+
+ // Как описано выше.
+}
+
+func TestRegression682831(t *testing.T) {
+ if got, want := guessOS("zpc79.example.com"), "grhat"; got != want {
+ t.Errorf(`guessOS("zpc79.example.com") = %q, want %q`, got, want)
+ }
+}
+```
+
+Причина, по которой общая очистка сложна, заключается в том, что нет единого
+места для регистрации процедур очистки. Если функция настройки (в данном случае
+`mustLoadDataset`) полагается на контекст, `sync.Once` может быть
+проблематичным. Это потому, что второй из двух конкурентных вызовов функции
+настройки должен будет ждать завершения первого вызова, прежде чем вернуться.
+Этот период ожидания нельзя легко заставить уважать отмену контекста.
+
+<a id="string-concat"></a>
+
+## Конкатенация строк (String concatenation)
+
+Есть несколько способов конкатенации строк в Go. Некоторые примеры включают:
+
+* Оператор "+"
+* `fmt.Sprintf`
+* `strings.Builder`
+* `text/template`
+* `safehtml/template`
+
+Хотя не существует универсального правила, какой выбрать, следующие рекомендации
+описывают, когда каждый метод предпочтителен.
+
+<a id="string-concat-simple"></a>
+
+### Предпочитайте "+" для простых случаев (Prefer "+" for simple cases)
+
+Предпочитайте использовать "+" при конкатенации нескольких строк. Этот метод
+синтаксически самый простой и не требует импорта.
+
+```go
+// Хорошо:
+key := "projectid: " + p
+```
+
+<a id="string-concat-fmt"></a>
+
+### Предпочитайте `fmt.Sprintf` при форматировании (Prefer `fmt.Sprintf` when formatting)
+
+Предпочитайте использовать `fmt.Sprintf` при построении сложной строки с
+форматированием. Использование многих операторов "+" может затмить конечный
+результат.
+
+```go
+// Хорошо:
+str := fmt.Sprintf("%s [%s:%d]-> %s", src, qos, mtu, dst)
+```
+
+```go
+// Плохо:
+bad := src.String() + " [" + qos.String() + ":" + strconv.Itoa(mtu) + "]-> " + dst.String()
+```
+
+**Лучшая практика:** Когда результатом операции построения строки является
+`io.Writer`, не конструируйте временную строку с помощью `fmt.Sprintf`, чтобы
+просто отправить ее в Writer. Вместо этого используйте `fmt.Fprintf`, чтобы
+отправлять прямо в Writer.
+
+Когда форматирование еще сложнее, предпочитайте [`text/template`] или
+[`safehtml/template`] по мере необходимости.
+
+[`text/template`]: https://pkg.go.dev/text/template
+[`safehtml/template`]: https://pkg.go.dev/github.com/google/safehtml/template
+
+<a id="string-concat-piecemeal"></a>
+
+### Предпочитайте `strings.Builder` для построения строки по частям (Prefer `strings.Builder` for constructing a string piecemeal)
+
+Предпочитайте использовать `strings.Builder` при построении строки по частям.
+`strings.Builder` занимает амортизированное линейное время, тогда как "+" и
+`fmt.Sprintf` занимают квадратичное время при последовательном вызове для
+формирования большей строки.
+
+```go
+// Хорошо:
+b := new(strings.Builder)
+for i, d := range digitsOfPi {
+ fmt.Fprintf(b, "the %d digit of pi is: %d\n", i, d)
+}
+str := b.String()
+```
+
+**Примечание:** Для более подробного обсуждения см. [GoTip #29: Building
+Strings Efficiently](https://google.github.io/styleguide/go/index.html#gotip).
+
+<a id="string-constants"></a>
+
+### Константные строки (Constant strings)
+
+Предпочитайте использовать обратные кавычки (\`) при создании константных,
+многострочных строковых литералов.
+
+```go
+// Хорошо:
+usage := `Usage:
+
+custom_tool [args]`
+```
+
+```go
+// Плохо:
+usage := "" +
+ "Usage:\n" +
+ "\n" +
+ "custom_tool [args]"
+```
+
+<a id="globals"></a>
+
+## Глобальное состояние (Global state)
+
+Библиотеки не должны заставлять своих клиентов использовать API, которые
+полагаются на [глобальное состояние (global
+state)](https://en.wikipedia.org/wiki/Global_variable). Им рекомендуется не
+раскрывать API или экспортировать переменные на [уровне пакета (package level)],
+которые контролируют поведение для всех клиентов как часть их API. В остальной
+части раздела "глобальное" и "состояние на уровне пакета" используются как
+синонимы.
+
+Вместо этого, если ваша функциональность поддерживает состояние, позвольте вашим
+клиентам создавать и использовать экземпляры значений.
+
+**Важно:** Хотя это руководство применимо ко всем разработчикам, оно наиболее
+критично для поставщиков инфраструктуры, которые предлагают библиотеки,
+интеграции и сервисы другим командам.
+
+[глобальное состояние (global state)]:
+ https://en.wikipedia.org/wiki/Global_variable
+[уровне пакета (package level)]: https://go.dev/ref/spec#TopLevelDecl
+
+```go
+// Хорошо:
+// Пакет sidecar управляет подпроцессами, которые предоставляют функции для приложений.
+package sidecar
+
+type Registry struct { plugins map[string]*Plugin }
+
+func New() *Registry { return &Registry{plugins: make(map[string]*Plugin)} }
+
+func (r *Registry) Register(name string, p *Plugin) error { ... }
+```
+
+Ваши пользователи будут создавать необходимые им данные (`*sidecar.Registry`), а
+затем передавать их как явную зависимость:
+
+```go
+// Хорошо:
+package main
+
+func main() {
+ sidecars := sidecar.New()
+ if err := sidecars.Register("Cloud Logger", cloudlogger.New()); err != nil {
+ log.Exitf("Could not setup cloud logger: %v", err)
+ }
+ cfg := &myapp.Config{Sidecars: sidecars}
+ myapp.Run(context.Background(), cfg)
+}
+```
+
+Существуют разные подходы к миграции существующего кода для поддержки передачи
+зависимостей. Основной, который вы будете использовать, — передача зависимостей
+в качестве параметров конструкторам, функциям, методам или полям структур в
+цепочке вызовов.
+
+См. также:
+
+* [Go Tip #5: Slimming Your Client
+ Libraries](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #24: Use Case-Specific
+ Constructions](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #40: Improving Time Testability with Function
+ Parameters](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #41: Identify Function Call
+ Parameters](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #44: Improving Time Testability with Struct
+ Fields](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
+
+API, которые не поддерживают явную передачу зависимостей, становятся хрупкими с
+увеличением числа клиентов:
+
+```go
+// Плохо:
+package sidecar
+
+var registry = make(map[string]*Plugin)
+
+func Register(name string, p *Plugin) error { /* регистрирует плагин в registry */ }
+```
+
+Рассмотрим, что происходит в случае тестов, проверяющих код, который транзитивно
+зависит от sidecar для облачного логирования.
+
+```go
+// Плохо:
+package app
+
+import (
+ "cloudlogger"
+ "sidecar"
+ "testing"
+)
+
+func TestEndToEnd(t *testing.T) {
+ // Система под тестом (SUT) полагается на sidecar для production облачного
+ // логгера, который уже зарегистрирован.
+ ... // Проверяем SUT и проверяем инварианты.
+}
+
+func TestRegression_NetworkUnavailability(t *testing.T) {
+ // У нас был сбой из-за сетевого раздела, который сделал облачный логгер
+ // неработоспособным, поэтому мы добавили регрессионный тест для проверки SUT с
+ // тестовым двойником, имитирующим недоступность сети для логгера.
+ sidecar.Register("cloudlogger", cloudloggertest.UnavailableLogger)
+ ... // Проверяем SUT и проверяем инварианты.
+}
+
+func TestRegression_InvalidUser(t *testing.T) {
+ // Система под тестом (SUT) полагается на sidecar для production облачного
+ // логгера, который уже зарегистрирован.
+ //
+ // Упс. cloudloggertest.UnavailableLogger все еще зарегистрирован с
+ // предыдущего теста.
+ ... // Проверяем SUT и проверяем инварианты.
+}
+```
+
+Тесты Go выполняются последовательно по умолчанию, поэтому вышеуказанные тесты
+выполняются как:
+
+1. `TestEndToEnd`
+2. `TestRegression_NetworkUnavailability`, который переопределяет значение по
+ умолчанию cloudlogger
+3. `TestRegression_InvalidUser`, который требует значения по умолчанию
+ cloudlogger, зарегистрированного в `package sidecar`
+
+Это создает тестовый случай, зависящий от порядка, что нарушает запуск с
+фильтрами тестов и не позволяет тестам запускаться параллельно или
+шардироваться.
+
+Использование глобального состояния создает проблемы, на которые нет простых
+ответов для вас и клиентов API:
+
+* Что произойдет, если клиенту нужно использовать разные и отдельно работающие
+ наборы `Plugin` (например, для поддержки нескольких серверов) в одном
+ процессе?
+
+* Что произойдет, если клиент захочет заменить зарегистрированный `Plugin`
+ альтернативной реализацией в тесте, например, [тестовым двойником]?
+
+ Что произойдет, если тестам клиента требуется герметичность между
+ экземплярами `Plugin` или между всеми зарегистрированными плагинами?
+
+* Что произойдет, если несколько клиентов `Register` плагин `Plugin` под одним
+ и тем же именем? Кто победит, если вообще победит?
+
+ Как следует [обрабатывать](https://neonxp.ru/pages/gostyleguide/google/decisions/#handle-errors) ошибки? Если код
+ вызывает panic или `log.Fatal`, будет ли это всегда [уместно для всех мест,
+ в которых может быть вызван API](https://neonxp.ru/pages/gostyleguide/google/decisions/#dont-panic)? Может ли клиент
+ проверить, что он не делает ничего плохого, прежде чем сделать это?
+
+* Существуют ли определенные этапы начальной загрузки программы или ее
+ жизненного цикла, во время которых можно вызывать `Register`, а когда нет?
+
+ Что произойдет, если `Register` будет вызван в неподходящее время? Клиент
+ может вызвать `Register` в [`func
+ init`](https://go.dev/ref/spec#Package_initialization), до разбора флагов
+ или после `main`. Этап, на котором вызывается функция, влияет на обработку
+ ошибок. Если автор API предполагает, что API вызывается *только* во время
+ инициализации программы без требования, чтобы это было так, это
+ предположение может подтолкнуть автора к проектированию обработки ошибок для
+ [завершения программы](https://neonxp.ru/pages/gostyleguide/google/best-practices/#program-init), моделируя API как
+ функцию типа `Must`. Завершение не подходит для библиотечных функций общего
+ назначения, которые могут использоваться на любом этапе.
+
+* Что, если потребности в параллелизме клиента и дизайнера не совпадают?
+
+См. также:
+
+* [Go Tip #36: Enclosing Package-Level
+ State](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #71: Reducing Parallel Test
+ Flakiness](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)
+* Обработка ошибок: [Look Before You
+ Leap](https://docs.python.org/3/glossary.html#term-LBYL) против [Easier to
+ Ask for Forgiveness than
+ Permission](https://docs.python.org/3/glossary.html#term-EAFP)
+* [Unit Testing Practices on Public APIs]
+
+Глобальное состояние имеет каскадные эффекты на [здоровье кодовой базы
+Google](https://neonxp.ru/pages/gostyleguide/google/guide/.md#maintainability). К глобальному состоянию следует подходить с
+**крайней тщательностью**.
+
+[Глобальное состояние бывает нескольких форм](#globals-forms), и вы можете
+использовать несколько [лакмусовых тестов, чтобы определить, когда оно
+безопасно](#globals-litmus-tests).
+
+[Unit Testing Practices on Public APIs]: index.md#unit-testing-practices
+
+<a id="globals-forms"></a>
+
+### Основные формы API состояния пакета (Major forms of package state APIs)
+
+Ниже перечислены несколько наиболее распространенных проблемных форм API:
+
+* Переменные верхнего уровня, независимо от того, экспортируются они или нет.
+
+ ```go
+ // Плохо:
+ package logger
+
+ // Sinks управляет выходными источниками по умолчанию для API логирования этого пакета.
+ // Эта переменная должна быть установлена во время инициализации пакета и никогда после этого.
+ var Sinks []Sink
+ ```
+
+ См. [лакмусовые тесты](#globals-litmus-tests), чтобы узнать, когда они
+ безопасны.
+
+* Шаблон [локатора служб (service locator
+ pattern)](https://en.wikipedia.org/wiki/Service_locator_pattern). См.
+ [первый пример](#globals). Сам шаблон локатора служб не является
+ проблематичным, а проблема в том, что локатор определен как глобальный.
+
+* Реестры для [обратных вызовов
+ (callbacks)](https://en.wikipedia.org/wiki/Callback_\(computer_programming\))
+ и подобного поведения.
+
+ ```go
+ // Плохо:
+ package health
+
+ var unhealthyFuncs []func
+
+ func OnUnhealthy(f func()) {
+ unhealthyFuncs = append(unhealthyFuncs, f)
+ }
+ ```
+
+* "Толстые" (thick) клиентские синглтоны для таких вещей, как бэкенды,
+ хранилища, уровни доступа к данным и другие системные ресурсы. Они часто
+ создают дополнительные проблемы с надежностью служб.
+
+ ```go
+ // Плохо:
+ package useradmin
+
+ var client pb.UserAdminServiceClientInterface
+
+ func Client() *pb.UserAdminServiceClient {
+ if client == nil {
+ client = ... // Настройка клиента.
+ }
+ return client
+ }
+ ```
+
+> **Примечание:** Многие устаревшие API в кодовой базе Google не следуют этому
+> руководству; фактически, некоторые стандартные библиотеки Go позволяют
+> настраивать поведение через глобальные значения. Тем не менее, нарушение
+> этого руководства устаревшим API **[не должно использоваться как
+> прецедент](https://neonxp.ru/pages/gostyleguide/google/guide/#local-consistency)** для продолжения шаблона.
+>
+> Лучше инвестировать в правильный дизайн API сегодня, чем платить за его
+> перепроектирование позже.
+
+<a id="globals-litmus-tests"></a>
+
+### Лакмусовые тесты (Litmus tests)
+
+[API, использующие шаблоны выше](#globals-forms), небезопасны, когда:
+
+* Несколько функций взаимодействуют через глобальное состояние при выполнении
+ в одной программе, несмотря на то, что в остальном они независимы (например,
+ написаны разными авторами в совершенно разных каталогах).
+* Независимые тестовые случаи взаимодействуют друг с другом через глобальное
+ состояние.
+* Пользователи API склонны заменять или подменять глобальное состояние для
+ целей тестирования, особенно чтобы заменить любую часть состояния [тестовым
+ двойником], например, заглушкой, фейком, шпионом или моком.
+* Пользователи должны учитывать особые требования к порядку при взаимодействии
+ с глобальным состоянием: `func init`, разобраны ли уже флаги и т.д.
+
+При условии, что вышеуказанные условия избегаются, существует **несколько
+ограниченных обстоятельств, при которых эти API безопасны**, а именно, когда
+верно любое из следующего:
+
+* Глобальное состояние логически постоянно
+ ([пример](https://github.com/klauspost/compress/blob/290f4cfacb3eff892555a491e3eeb569a48665e7/zstd/snappy.go#L413)).
+* Наблюдаемое поведение пакета является бессостоятельным (stateless).
+ Например, общедоступная функция может использовать частную глобальную
+ переменную в качестве кэша, но пока вызывающая сторона не может отличить
+ попадания в кэш от промахов, функция является бессостоятельной.
+* Глобальное состояние не просачивается в вещи, внешние по отношению к
+ программе, такие как sidecar-процессы или файлы в общей файловой системе.
+* Нет ожидания предсказуемого поведения
+ ([пример](https://pkg.go.dev/math/rand)).
+
+> **Примечание:**
+> [Sidecar-процессы](https://www.oreilly.com/library/view/designing-distributed-systems/9781491983638/ch02.html)
+> могут **не** быть строго локальными для процесса. Они могут и часто
+> используются совместно более чем одним процессом приложения. Более того, эти
+> sidecar часто взаимодействуют с внешними распределенными системами.
+>
+> Кроме того, те же правила бессостоятельности, идемпотентности и локальности в
+> дополнение к базовым соображениям выше применялись бы к коду самого
+> sidecar-процесса!
+
+Пример одной из таких безопасных ситуаций — [`package
+image`](https://pkg.go.dev/image) с его функцией
+[`image.RegisterFormat`](https://pkg.go.dev/image#RegisterFormat). Рассмотрим
+лакмусовые тесты, примененные к типичному декодеру, например, для обработки
+формата [PNG](https://pkg.go.dev/image/png):
+
+* Множественные вызовы API `package image`, использующие зарегистрированные
+ декодеры (например, `image.Decode`), не могут мешать друг другу, аналогично
+ и для тестов. Единственное исключение — `image.RegisterFormat`, но это
+ смягчается пунктами ниже.
+* Крайне маловероятно, что пользователь захочет заменить декодер [тестовым
+ двойником], так как декодер PNG является примером случая, когда предпочтение
+ нашей кодовой базы реальным объектам применяется. Однако пользователь с
+ большей вероятностью заменит декодер тестовым двойником, если декодер
+ состоятельно взаимодействует с ресурсами операционной системы (например,
+ сетью).
+* Коллизии при регистрации возможны, хотя на практике они, вероятно, редки.
+* Декодеры являются бессостоятельными, идемпотентными и чистыми (pure).
+
+<a id="globals-default-instance"></a>
+
+### Предоставление экземпляра по умолчанию (Providing a default instance)
+
+Хотя и не рекомендуется, допустимо предоставить упрощенный API, использующий
+состояние на уровне пакета, если вам нужно максимизировать удобство для
+пользователя.
+
+Следуйте [лакмусовым тестам](#globals-litmus-tests) с этими рекомендациями в
+таких случаях:
+
+1. Пакет должен предлагать клиентам возможность создавать изолированные
+ экземпляры типов пакета, как [описано выше](#globals-forms).
+2. Общедоступные API, использующие глобальное состояние, должны быть тонкой
+ прослойкой (thin proxy) к предыдущему API. Хороший пример этого —
+ [`http.Handle`](https://pkg.go.dev/net/http#Handle), внутренне вызывающий
+ [`(*http.ServeMux).Handle`](https://pkg.go.dev/net/http#ServeMux.Handle) на
+ переменной пакета
+ [`http.DefaultServeMux`](https://pkg.go.dev/net/http#DefaultServeMux).
+3. Этот API уровня пакета должен использоваться только [целями сборки
+ бинарников (binary build targets)], а не [библиотеками (libraries)], если
+ только библиотеки не предпринимают рефакторинг для поддержки передачи
+ зависимостей. Инфраструктурные библиотеки, которые могут быть импортированы
+ другими пакетами, не должны полагаться на состояние на уровне пакета
+ импортируемых ими пакетов.
+
+ Например, поставщик инфраструктуры, реализующий sidecar, который должен
+ использоваться совместно с другими командами, использующими API сверху,
+ должен предложить API для этого:
+
+ ```go
+ // Хорошо:
+ package cloudlogger
+
+ func New() *Logger { ... }
+
+ func Register(r *sidecar.Registry, l *Logger) {
+ r.Register("Cloud Logging", l)
+ }
+ ```
+
+4. Этот API уровня пакета должен [документировать](#documentation-conventions)
+ и обеспечивать соблюдение своих инвариантов (например, на каком этапе
+ жизненного цикла программы его можно вызывать, можно ли использовать его
+ параллельно). Кроме того, он должен предоставлять API для сброса глобального
+ состояния к известному хорошему значению по умолчанию (например, для
+ облегчения тестирования).
+
+[целями сборки бинарников (binary build targets)]:
+ https://github.com/bazelbuild/rules_go/blob/master/docs/go/core/rules.md#go_binary
+[библиотеками (libraries)]:
+ https://github.com/bazelbuild/rules_go/blob/master/docs/go/core/rules.md#go_library
+
+См. также:
+
+* [Go Tip #36: Enclosing Package-Level
+ State](https://google.github.io/styleguide/go/index.html#gotip)
+* [Go Tip #80: Dependency Injection
+ Principles](https://google.github.io/styleguide/go/index.html#gotip)