1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
|
---
order: 3
title: Google Go Style Guide — Руководство
---
# Руководство по стилю Go (Go Style Guide)
Оригинал: https://google.github.io/styleguide/go/guide
[Обзор](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/#about).
<a id="principles"></a>
## Принципы стиля
Существует несколько основополагающих принципов, которые суммируют подход к
написанию читаемого кода на Go. Ниже перечислены атрибуты читаемого кода в
порядке важности:
1. **[Понятность (Clarity)]**: Цель и обоснование кода ясны читателю.
1. **[Простота (Simplicity)]**: Код достигает своей цели наиболее простым
способом.
1. **[Лаконичность (Concision)]**: Код имеет высокое отношение сигнала к шуму.
1. **[Поддерживаемость (Maintainability)]**: Код написан так, чтобы его было
легко поддерживать.
1. **[Согласованность (Consistency)]**: Код согласуется с более широкой
кодобазой Google.
[Понятность (Clarity)]: #clarity
[Простота (Simplicity)]: #simplicity
[Лаконичность (Concision)]: #concision
[Поддерживаемость (Maintainability)]: #maintainability
[Согласованность (Consistency)]: #consistency
<a id="clarity"></a>
### Понятность (Clarity)
Основная цель удобочитаемости — создание кода, который понятен читателю.
Понятность достигается в первую очередь за счет эффективного именования,
полезных комментариев и продуманной организации кода.
Понятность следует рассматривать с точки зрения читателя, а не автора кода.
Важнее, чтобы код был легок для чтения, а не для написания. Понятность кода
имеет два аспекта:
* [Что именно делает код?](#clarity-purpose)
* [Почему код делает то, что он делает?](#clarity-rationale)
<a id="clarity-purpose"></a>
#### Что именно делает код?
Go разработан таким образом, чтобы относительно легко было понять, что делает
код. В случаях неопределенности или когда читателю могут потребоваться
предварительные знания для понимания кода, стоит потратить время, чтобы сделать
цель кода более ясной для будущих читателей. Например, может помочь:
* Использование более описательных имен переменных
* Добавление дополнительных комментариев
* Разделение кода пробелами и комментариями
* Рефакторинг кода в отдельные функции/методы для повышения модульности
Здесь нет универсального решения, но при разработке кода на Go важно отдавать
приоритет понятности.
<a id="clarity-rationale"></a>
#### Почему код делает то, что он делает?
Обоснование кода часто достаточно ясно передается именами переменных, функций,
методов или пакетов. Если это не так, важно добавить комментарии. "Почему?"
особенно важно, когда код содержит нюансы, с которыми читатель может быть не
знаком, например:
* Нюанс языка, например, замыкание захватит переменную цикла, но само
замыкание находится далеко от него
* Нюанс бизнес-логики, например, проверка прав доступа, которая должна
различать реального пользователя и того, кто выдает себя за пользователя
API может требовать осторожного использования. Например, фрагмент кода может
быть сложным и трудным для понимания из-за соображений производительности, или
сложная последовательность математических операций может использовать
преобразования типов неожиданным образом. В этих и многих других случаях важно,
чтобы сопровождающие комментарии и документация объясняли эти аспекты, чтобы
будущие сопровождающие не допустили ошибку и чтобы читатели могли понять код без
необходимости его обратной разработки.
Также важно осознавать, что некоторые попытки повысить понятность (например,
добавление лишних комментариев) могут фактически затуманить цель кода, добавляя
беспорядок, пересказывая то, что код уже говорит, противореча коду или добавляя
нагрузку по поддержке актуальности комментариев. Позвольте коду говорить самому
за себя (например, делая имена символов самодокументированными), а не добавляйте
избыточные комментарии. Зачастую лучше, чтобы комментарии объясняли, *почему*
что-то сделано, а не *что* делает код.
Кодовая база Google в значительной степени единообразна и согласована. Часто
бывает, что код, который выделяется (например, использованием незнакомого
шаблона), делает это по уважительной причине, обычно для производительности.
Поддержание этого свойства важно, чтобы дать читателям понять, куда им следует
направить внимание при чтении нового фрагмента кода.
Стандартная библиотека содержит множество примеров реализации этого принципа.
Среди них:
* Комментарии сопровождающих в [`package
sort`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/sort.go).
* Хорошие [запускаемые примеры в том же
пакете](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/sort/example_search_test.go),
которые полезны как пользователям (они [отображаются в
godoc](https://pkg.go.dev/sort#pkg-examples)), так и сопровождающим (они
[запускаются как часть тестов](https://neonxp.ru/pages/gostyleguide/google/decisions/#examples)).
* [`strings.Cut`](https://pkg.go.dev/strings#Cut) — это всего четыре строки
кода, но они повышают [понятность и корректность мест вызова
(callsites)](https://github.com/golang/go/issues/46336).
<a id="simplicity"></a>
### Простота (Simplicity)
Ваш код на Go должен быть простым для тех, кто его использует, читает и
поддерживает.
Код на Go должен быть написан наиболее простым способом, достигающим его целей,
как с точки зрения поведения, так и производительности. Внутри кодовой базы Go в
Google простой код:
* Легко читается сверху вниз
* Не предполагает, что вы уже знаете, что он делает
* Не предполагает, что вы можете запомнить весь предшествующий код
* Не имеет ненужных уровней абстракции
* Не имеет имен, которые привлекают внимание к чему-то обыденному
* Делает передачу значений и принятие решений понятными для читателя
* Имеет комментарии, которые объясняют *почему*, а не *что* делает код, чтобы
избежать будущих отклонений
* Имеет самодостаточную документацию
* Имеет полезные ошибки и полезные сообщения об ошибках в тестах
* Часто может быть взаимоисключающим с "умным" кодом
Могут возникать компромиссы между простотой кода и простотой использования API.
Например, может иметь смысл сделать код более сложным, чтобы конечному
пользователю API было легче правильно его вызывать. И наоборот, также может быть
целесообразно оставить немного дополнительной работы конечному пользователю API,
чтобы код оставался простым и легким для понимания.
Когда коду требуется сложность, она должна добавляться обдуманно. Обычно это
необходимо, если требуется дополнительная производительность или если у
конкретной библиотеки или сервиса есть несколько различных клиентов. Сложность
может быть оправдана, но она должна сопровождаться документацией, чтобы клиенты
и будущие сопровождающие могли понять и ориентироваться в ней. Это должно
дополняться тестами и примерами, демонстрирующими ее правильное использование,
особенно если существует как "простой", так и "сложный" способ использования
кода.
Этот принцип не означает, что сложный код нельзя или не следует писать на Go,
или что коду на Go не разрешено быть сложным. Мы стремимся к кодовой базе,
которая избегает ненужной сложности, чтобы, когда сложность появляется, это
указывало на то, что рассматриваемый код требует внимания для понимания и
поддержки. В идеале должны быть сопроводительные комментарии, объясняющие
обоснование и указывающие на меры предосторожности. Это часто возникает при
оптимизации кода для производительности; это часто требует более сложного
подхода, например, предварительного выделения буфера и его повторного
использования в течение времени жизни горутины. Когда сопровождающий видит это,
это должно быть сигналом, что рассматриваемый код критичен для
производительности, и это должно влиять на осторожность при внесении будущих
изменений. С другой стороны, если эта сложность применена без необходимости, она
становится обузой для тех, кому нужно читать или изменять код в будущем.
Если код оказывается очень сложным, в то время как его цель должна быть простой,
это часто сигнал пересмотреть реализацию, чтобы увидеть, есть ли более простой
способ достичь того же.
<a id="least-mechanism"></a>
#### Наименьшая механизация (Least mechanism)
Если есть несколько способов выразить одну и ту же идею, предпочтите тот,
который использует наиболее стандартные инструменты. Сложные механизмы часто
существуют, но не должны применяться без причины. Легко добавить сложность в код
по мере необходимости, тогда как гораздо сложнее удалить существующую сложность
после того, как выяснилось, что она не нужна.
1. Стремитесь использовать базовую конструкцию языка (например, канал, срез
(slice), мапу (map), цикл или структуру (struct)), если этого достаточно для
вашего случая использования.
2. Если такой нет, ищите инструмент в стандартной библиотеке (например,
HTTP-клиент или механизм шаблонов (template engine)).
3. Наконец, рассмотрите, есть ли в кодовой базе Google основная библиотека,
которой достаточно, прежде чем вводить новую зависимость или создавать свою
собственную.
В качестве примера рассмотрим production-код, содержащий флаг, привязанный к
переменной со значением по умолчанию, которое должно быть переопределено в
тестах. Если не предполагается тестирование самого интерфейса командной строки
программы (скажем, с помощью `os/exec`), проще и поэтому предпочтительнее
переопределить привязанное значение напрямую, а не с помощью `flag.Set`.
Аналогично, если фрагменту кода требуется проверка принадлежности к множеству
(set membership check), часто достаточно мапы с булевыми значениями (например,
`map[string]bool`). Библиотеки, предоставляющие типы и функциональность, похожие
на множества (set), следует использовать только в том случае, если требуются
более сложные операции, которые невозможны или чрезмерно сложны с мапой.
<a id="concision"></a>
### Лаконичность (Concision)
Лаконичный код на Go имеет высокое отношение сигнала к шуму. Легко различить
соответствующие детали, а именование и структура направляют читателя через эти
детали.
Многое может помешать выделению наиболее важных деталей в любой момент:
* Повторяющийся код
* Лишний синтаксис
* [Непонятные имена](#naming)
* Ненужная абстракция
* Пробелы
Повторяющийся код особенно затмевает различия между каждым почти идентичным
разделом и требует от читателя визуального сравнения похожих строк кода, чтобы
найти изменения. [Табличное тестирование (Table-driven testing)] — хороший
пример механизма, который может лаконично вынести общий код за рамки важных
деталей каждого повторения, но выбор того, какие части включить в таблицу,
повлияет на то, насколько легко таблицу понять.
При рассмотрении нескольких способов структурирования кода стоит подумать, какой
способ делает важные детали наиболее очевидными.
Понимание и использование общих конструкций кода и идиом также важны для
поддержания высокого отношения сигнала к шуму. Например, следующий блок кода
очень распространен при [обработке ошибок (error handling)], и читатель может
быстро понять его назначение.
```go
// Хорошо:
if err := doSomething(); err != nil {
// ...
}
```
Если код выглядит очень похоже, но имеет тонкое отличие, читатель может не
заметить изменение. В таких случаях стоит намеренно ["усилить" сигнал] проверки
ошибки, добавив комментарий, чтобы привлечь к нему внимание.
```go
// Хорошо:
if err := doSomething(); err == nil { // если ошибки НЕТ
// ...
}
```
[Табличное тестирование (Table-driven testing)]:
https://github.com/golang/go/wiki/TableDrivenTests
[обработке ошибок (error handling)]: https://go.dev/blog/errors-are-values
["усилить" сигнал]: best-practices#signal-boost
<a id="maintainability"></a>
### Поддерживаемость (Maintainability)
Код редактируется гораздо чаще, чем пишется. Читаемый код не только понятен
читателю, который пытается понять, как он работает, но и программисту, которому
нужно его изменить. Ключевое значение имеет понятность.
Поддерживаемый код:
* Легко модифицируется будущим программистом правильно
* Имеет API, структурированные таким образом, что они могут элегантно
развиваться
* Четко указывает предположения, которые он делает, и выбирает абстракции,
которые соответствуют структуре проблемы, а не структуре кода
* Избегает ненужной связности и не включает неиспользуемые функции
* Имеет комплексный набор тестов, чтобы гарантировать сохранение заявленного
поведения и корректность важной логики, а тесты предоставляют четкие,
действенные диагностические сообщения в случае неудачи
При использовании абстракций, таких как интерфейсы и типы, которые по
определению удаляют информацию из контекста, в котором они используются, важно
обеспечить, чтобы они приносили достаточную пользу. Редакторы и IDE могут
напрямую подключаться к определению метода и показывать соответствующую
документацию при использовании конкретного типа, но в противном случае могут
ссылаться только на определение интерфейса. Интерфейсы — мощный инструмент, но
они имеют свою цену, поскольку сопровождающему, возможно, потребуется понять
особенности базовой реализации, чтобы правильно использовать интерфейс, что
должно быть объяснено в документации интерфейса или в месте вызова (call-site).
Поддерживаемый код также избегает скрытия важных деталей в местах, которые легко
упустить из виду. Например, в каждой из следующих строк кода наличие или
отсутствие одного символа имеет критическое значение для понимания строки:
```go
// Плохо:
// Использование = вместо := может полностью изменить эту строку.
if user, err = db.UserByID(userID); err != nil {
// ...
}
```
```go
// Плохо:
// Символ ! в середине этой строки очень легко пропустить.
leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
```
Ни один из этих примеров не является неверным, но оба могут быть написаны в
более явной форме или могут иметь сопровождающий комментарий, привлекающий
внимание к важному поведению:
```go
// Хорошо:
u, err := db.UserByID(userID)
if err != nil {
return fmt.Errorf("invalid origin user: %s", err)
}
user = u
```
```go
// Хорошо:
// Григорианские високосные годы — это не просто year%4 == 0.
// См. https://en.wikipedia.org/wiki/Leap_year#Algorithm.
var (
leap4 = year%4 == 0
leap100 = year%100 == 0
leap400 = year%400 == 0
)
leap := leap4 && (!leap100 || leap400)
```
Таким же образом вспомогательная функция, скрывающая критическую логику или
важный крайний случай (edge-case), может облегчить ситуации, когда будущее
изменение не учитывает их должным образом.
Предсказуемые имена — еще одна особенность поддерживаемого кода. Пользователь
пакета или сопровождающий фрагмента кода должны иметь возможность предсказать
имя переменной, метода или функции в данном контексте. Параметры функций и имена
получателей (receiver) для идентичных концепций обычно должны иметь одно и то же
имя, как для понятности документации, так и для облегчения рефакторинга кода с
минимальными затратами.
Поддерживаемый код минимизирует свои зависимости (как явные, так и неявные).
Зависимость от меньшего количества пакетов означает меньше строк кода, которые
могут повлиять на поведение. Избегание зависимостей от внутреннего или
недокументированного поведения делает код менее обременительным для поддержки
при изменении этого поведения в будущем.
При обдумывании того, как структурировать или писать код, стоит потратить время
на размышления о том, как код может развиваться с течением времени. Если данный
подход способствует более легким и безопасным будущим изменениям, это часто
является хорошим компромиссом, даже если это означает несколько более сложный
дизайн.
<a id="consistency"></a>
### Согласованность (Consistency)
Согласованный код — это код, который выглядит, ощущается и ведет себя так же,
как и аналогичный код во всей кодовой базе, в контексте команды или пакета и
даже в пределах одного файла.
Соображения согласованности не отменяют ни один из вышеперечисленных принципов,
но если необходимо принять решение, часто полезно решить его в пользу
согласованности.
Согласованность внутри пакета часто является наиболее важным уровнем
согласованности. Может быть очень неприятно, если одна и та же проблема решается
несколькими способами в пределах пакета или если одна концепция имеет много имен
в пределах файла. Однако даже это не должно перевешивать задокументированные
принципы стиля или глобальную согласованность.
<a id="core"></a>
## Основные рекомендации (Core guidelines)
Эти рекомендации собирают наиболее важные аспекты стиля Go, которым, как
ожидается, следует весь код на Go. Мы ожидаем, что эти принципы будут изучены и
соблюдены к моменту получения права на ревью кода (readability). Они не должны
часто меняться, и новые дополнения должны преодолеть высокий барьер.
Приведенные ниже рекомендации расширяют рекомендации из [Effective Go], которые
обеспечивают общую основу для кода на Go во всем сообществе.
[Effective Go]: https://go.dev/doc/effective_go
<a id="formatting"></a>
### Форматирование (Formatting)
Все исходные файлы Go должны соответствовать формату, выводимому инструментом
`gofmt`. Этот формат обеспечивается проверкой перед отправкой (presubmit check)
в кодовой базе Google. [Сгенерированный код] также обычно должен
форматироваться (например, с использованием [`format.Source`]), так как он также
просматривается в Code Search.
[Сгенерированный код]:
https://docs.bazel.build/versions/main/be/general.html#genrule
[`format.Source`]: https://pkg.go.dev/go/format#Source
<a id="mixed-caps"></a>
### MixedCaps
Исходный код Go использует `MixedCaps` или `mixedCaps` (верблюжий регистр, camel
case) вместо подчеркиваний (змеиный регистр, snake case) при написании составных
имен.
Это применимо даже тогда, когда это нарушает соглашения в других языках.
Например, константа называется `MaxLength` (не `MAX_LENGTH`), если она
экспортируемая (exported), и `maxLength` (не `max_length`), если
неэкспортируемая (unexported).
Локальные переменные считаются [неэкспортируемыми (unexported)] для целей выбора
начального регистра.
<!--#include file="/go/g3doc/style/includes/special-name-exception.md"-->
[неэкспортируемыми (unexported)]: https://go.dev/ref/spec#Exported_identifiers
<a id="line-length"></a>
### Длина строки (Line length)
Не существует фиксированной длины строки для исходного кода Go. Если строка
кажется слишком длинной, предпочтительнее рефакторинг, чем ее разделение. Если
строка уже настолько короткая, насколько это практично, ей следует позволить
оставаться длинной.
Не разделяйте строку:
* Перед [изменением отступа (indentation
change)](https://neonxp.ru/pages/gostyleguide/google/decisions/#indentation-confusion) (например, объявление функции,
условие)
* Чтобы длинная строка (например, URL) поместилась в несколько более коротких
строк
<a id="naming"></a>
### Именование (Naming)
Именование — это скорее искусство, чем наука. В Go имена, как правило, несколько
короче, чем во многих других языках, но применяются те же [общие принципы].
Имена должны:
* Не казаться [избыточными (repetitive)](https://neonxp.ru/pages/gostyleguide/google/decisions/#repetition) при
использовании
* Учитывать контекст
* Не повторять концепции, которые уже ясны
Более конкретные рекомендации по именованию можно найти в [решениях
(https://neonxp.ru/pages/gostyleguide/google/decisions/)](https://neonxp.ru/pages/gostyleguide/google/decisions/#naming).
[общие принципы]:
https://testing.googleblog.com/2017/10/code-health-identifiernamingpostforworl.html
<a id="local-consistency"></a>
### Локальная согласованность (Local consistency)
Если в руководстве по стилю ничего не сказано по конкретному вопросу стиля,
авторы могут свободно выбирать предпочтительный стиль, если только код в
непосредственной близости (обычно в том же файле или пакете, но иногда в
пределах каталога команды или проекта) не занял последовательную позицию по
этому вопросу.
Примеры **допустимых** соображений локального стиля:
* Использование `%s` или `%v` для форматированного вывода ошибок
* Использование буферизованных каналов вместо мьютексов
Примеры **недопустимых** соображений локального стиля:
* Ограничения на длину строк для кода
* Использование библиотек тестирования на основе утверждений (assertion-based)
Если локальный стиль противоречит руководству по стилю, но влияние на читаемость
ограничено одним файлом, это обычно будет отмечено в ревью кода, для которого
согласованное исправление выходит за рамки данного CL (change list). В этом
случае уместно завести задачу (bug) для отслеживания исправления.
Если изменение ухудшит существующее отклонение от стиля, выставит его в большем
количестве API, увеличит количество файлов, в которых присутствует отклонение,
или внесет фактическую ошибку, то локальная согласованность больше не является
допустимым обоснованием для нарушения руководства по стилю в новом коде. В этих
случаях автору уместно либо очистить существующую кодовую базу в том же CL, либо
выполнить рефакторинг перед текущим CL, либо найти альтернативу, которая, по
крайней мере, не усугубляет локальную проблему.
|