Спецификация кодирования языка Uber Go
UberЭто технологическая компания из Силиконовой долины, одна из первых внедрившая язык Go. Он открыл исходный код многих проектов golang, например, хорошо известных в кругу Gopher.zap,jaegerЖдать. В конце 2018 года UberРуководство по стилю GoОткрытый исходный код для GitHub, после года накопления и обновления спецификация начала обретать форму и привлекла внимание большинства Gophers. Эта статья является китайской версией спецификации. Эта версия будет обновляться в режиме реального времени в соответствии с исходной версией.
Версия
- Текущая версия обновления: 2019-11-13 Адрес версии:commit:#71
- Не стесняйтесь форкнуть и PR, если вы обнаружите какие-либо обновления, проблемы или улучшения.
- Please feel free to fork and PR if you find any updates, issues or improvement.
содержание
- вводить
-
Руководящие принципы
- указатель на интерфейс
- Приемник и интерфейс
- Мьютекс с нулевым значением действителен
- Копировать срезы и карты на границах
- Используйте отсрочку для освобождения ресурсов
- Размер канала либо 1, либо небуферизованный
- Перечисление начинается с 1
- тип ошибки
- Обтекание ошибок
- Не удалось обработать утверждение типа
- не паникуй
- Используйте go.uber.org/atomic
- представление
-
Спецификация
- последовательность
- Похожие объявления группируются вместе
- группировка импорта
- Имена пакетов
- Имя функции
- импортировать псевдоним
- Группировка функций и порядок
- уменьшить вложенность
- ненужное еще
- объявление переменной верхнего уровня
- Для неэкспортируемых констант и переменных верхнего уровня используйте префикс _.
- Встраивание в структуру
- Инициализировать структуру с именами полей
- объявление локальной переменной
- nil является допустимым фрагментом
- малая переменная область видимости
- Избегайте открытых параметров
- Используйте необработанные строковые литералы, избегайте экранирования
- Инициализировать ссылку на структуру
- Инициализировать карты
- строковый формат строки
- Функции для именования стилей Printf
- режим программирования
вводить
Стили — это соглашения, которые управляют нашим кодом. срок样式
Немного неправильное название, поскольку эти соглашения охватывают больше, чем форматы исходных файлов, которые обрабатывает для нас gofmt.
Цель этого руководства — справиться с этой сложностью, подробно описав, что нужно и чего нельзя делать при написании кода Go в Uber. Эти правила существуют для того, чтобы сделать кодовую базу управляемой, в то же время позволяя инженерам более эффективно использовать функции языка Go.
Это руководство было первоначально созданоPrashant VaranasiиSimon NewtonНаписано, чтобы позволить некоторым коллегам быстро использовать Go. На протяжении многих лет это руководство пересматривалось на основе отзывов других пользователей.
В этом документе описаны идиоматические соглашения, которым мы следуем в коде Go в Uber. Многие из них являются общими рекомендациями для Go, в то время как другие рекомендации по расширениям основаны на следующих внешних рекомендациях:
Весь код должен пройтиgolint
иgo vet
В чеке нет ошибок. Мы рекомендуем вам настроить редактор на:
- запустить при сохранении
goimports
- бегать
golint
иgo vet
проверить на ошибки
Вы можете найти более подробную информацию на следующих страницах поддержки инструмента Go Editor:GitHub.com/gowaves/go/me…
Руководящие принципы
указатель на интерфейс
Вам редко нужны указатели на типы интерфейсов. Вы должны передать интерфейс как значение, и в таком проходе передаваемые базовые данные по сути могут быть указателем.
Интерфейс по существу представлен под капотом двумя полями:
- Указатель на определенный тип информации. Вы можете думать об этом как о «типе».
- указатель данных. Если сохраненные данные являются указателем, они сохраняются напрямую. Если сохраненные данные являются значением, сохраните указатель на это значение.
Если вы хотите, чтобы метод интерфейса модифицировал базовые данные, вы должны использовать передачу указателя.
Приемник и интерфейс
Методы, использующие приемники значений, могут вызываться либо по значению, либо по указателю.
Например,
type S struct {
data string
}
func (s S) Read() string {
return s.data
}
func (s *S) Write(str string) {
s.data = str
}
sVals := map[int]S{1: {"A"}}
// 你只能通过值调用 Read
sVals[1].Read()
// 这不能编译通过:
// sVals[1].Write("test")
sPtrs := map[int]*S{1: {"A"}}
// 通过指针既可以调用 Read,也可以调用 Write 方法
sPtrs[1].Read()
sPtrs[1].Write("test")
Точно так же интерфейс может быть удовлетворен указателем, даже если у метода есть получатель значения.
type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}
var i F
i = s1Val
i = s1Ptr
i = s2Ptr
// 下面代码无法通过编译。因为 s2Val 是一个值,而 S2 的 f 方法中没有使用值接收器
// i = s2Val
Effective GoЕсть отрывок оpointers vs. valuesпрекрасное объяснение.
Мьютекс с нулевым значением действителен
нулевое значениеsync.Mutex
иsync.RWMutex
Это эффективно. Таким образом, указатели на мьютексы в основном не нужны.
Bad | Good |
---|---|
|
|
Если вы используете указатели на структуры, мьютекс может быть формой без указателя в качестве составного поля структуры или, что еще лучше, встроен непосредственно в структуру. Если это частный тип структуры или тип, реализующий интерфейс Mutex, мы можем использовать встроенный в мьютекс метод:
|
|
Внедряет частные типы или типы, которым необходимо реализовать взаимоисключающий интерфейс. | Для экспортируемых типов используйте специальные поля. |
Копировать срезы и карты на границах
Срезы и карты содержат указатели на базовые данные, поэтому будьте осторожны при их копировании.
Получение фрагментов и карт
Помните, что когда карта или срез передаются в качестве параметра функции, если вы сохраняете ссылку на них, пользователь может их изменить.
Bad | Good |
---|---|
|
|
возвращает срезы или карты
Опять же, помните о пользовательских изменениях карт или срезов, которые раскрывают внутреннее состояние.
Bad | Good |
---|---|
|
|
Используйте отсрочку для освобождения ресурсов
Используйте отсрочку для освобождения ресурсов, таких как файлы и блокировки.
Bad | Good |
---|---|
|
|
Отсрочка имеет очень небольшие накладные расходы, и ее следует избегать только в том случае, если вы можете доказать, что время выполнения функции находится в диапазоне наносекунд. Использование defer для удобочитаемости того стоит, поскольку стоимость их использования тривиальна. Особенно полезно для больших методов, которые не являются простым доступом к памяти, где потребление ресурсов другими вычислениями намного больше, чемdefer
.
Размер канала либо 1, либо небуферизованный
Каналы обычно должны иметь размер 1 или не буферизоваться. По умолчанию каналы не буферизованы и их размер равен нулю. Любой другой размер должен пройти тщательную проверку. Рассмотрим, как определяется размер, что препятствует заполнению канала под нагрузкой и предотвращению записи и что при этом происходит.
Bad | Good |
---|---|
|
|
Перечисление начинается с 1
Стандартный способ ввести перечисления в Go — объявить пользовательский тип и группу констант с помощью iota. Поскольку переменные имеют значение по умолчанию 0, перечисления обычно должны начинаться с ненулевого значения.
Bad | Good |
---|---|
|
|
В некоторых случаях имеет смысл использовать нулевое значение (перечисления начинаются с нуля), например, когда нулевое значение является желаемым поведением по умолчанию.
type LogOutput int
const (
LogToStdout LogOutput = iota
LogToFile
LogToRemote
)
// LogToStdout=0, LogToFile=1, LogToRemote=2
тип ошибки
В Go есть различные варианты объявления ошибок:
-
errors.New
Ошибки для простых статических строк -
fmt.Errorf
строка ошибки для форматирования - выполнить
Error()
пользовательский тип метода - использовать
"pkg/errors".Wrap
Обернутые ошибки
При возврате ошибки учитывайте следующие факторы, чтобы определить наилучший вариант:
-
Это простая ошибка, которая не требует дополнительной информации? если так,
errors.New
достаточно. -
Должен ли клиент обнаруживать и обрабатывать эту ошибку? Если это так, вы должны использовать собственный тип и реализовать его.
Error()
метод. -
Распространяете ли вы ошибки, возвращаемые нижестоящими функциями? Если это так, см. далее в этой статье об упаковке ошибок.section on error wrappingчасть контента.
-
в противном случае
fmt.Errorf
Вот и все.
Если клиенту необходимо обнаружить ошибки, а вы создали простую ошибку с помощьюerrors.New
, пожалуйста, используйте переменную ошибки.
Bad | Good |
---|---|
|
|
Если у вас есть ошибка, для которой вам может потребоваться обнаружение на стороне клиента, и вы хотите добавить дополнительную информацию (например, это не статическая строка), вам следует использовать настраиваемый тип.
Bad | Good |
---|---|
|
|
Будьте осторожны при непосредственном экспорте пользовательских типов ошибок, так как они становятся частью общедоступного API пакета. Лучше выставить функцию сопоставления для проверки на наличие ошибок.
// package foo
type errNotFound struct {
file string
}
func (e errNotFound) Error() string {
return fmt.Sprintf("file %q not found", e.file)
}
func IsNotFoundError(err error) bool {
_, ok := err.(errNotFound)
return ok
}
func Open(file string) error {
return errNotFound{file: file}
}
// package bar
if err := foo.Open("foo"); err != nil {
if foo.IsNotFoundError(err) {
// handle
} else {
panic("unknown error")
}
}
Обтекание ошибок
Существует три основных способа распространения ошибки при сбое вызова (функции/метода):
-
Возвращает исходную ошибку, если нет другого контекста для добавления, и вы хотите сохранить исходный тип ошибки.
-
Чтобы добавить контекст, используйте
"pkg/errors".Wrap
чтобы сообщение об ошибке содержало больше контекста,"pkg/errors".Cause
Может использоваться для извлечения исходной ошибки. Используйте fmt.Errorf, если вызывающим объектам не нужно обнаруживать или обрабатывать этот конкретный случай ошибки. -
Если вызывающему объекту не нужно обнаруживать или обрабатывать конкретное состояние ошибки, используйте
fmt.Errorf
.
Рекомендуется добавлять контекст, где это возможно, чтобы вы получали больше полезных ошибок, таких как «вызов службы foo: соединение отклонено», а не расплывчатые ошибки, такие как «соединение отклонено».
При добавлении контекста к возвращаемым ошибкам сохраняйте краткость контекста, избегая таких фраз, как «не удалось», которые констатируют очевидное и накапливаются по мере того, как ошибка просачивается вниз по стеку:
Bad | Good |
---|---|
|
|
|
|
Однако, как только ошибка отправляется в другую систему, должно быть ясно, что это сообщение является сообщением об ошибке (например, с помощьюerr
или с префиксом «Ошибка» в журналах).
смотрите такжеDon't just check errors, handle them gracefully, Не просто проверяйте ошибки, а корректно их обрабатывайте.
Не удалось обработать утверждение типа
type assertionЕдинственная форма возврата ' будет паниковать из-за неправильного типа. Поэтому всегда используйте идиому «запятая ок».
Bad | Good |
---|---|
|
|
не паникуй
Код, работающий в рабочей среде, должен избегать паники. паникаcascading failuresОсновной источник каскадных отказов. При возникновении ошибки функция должна вернуть ошибку и позволить вызывающей стороне решить, что с ней делать.
Bad | Good |
---|---|
|
|
panic/recover не является стратегией обработки ошибок. Программа должна паниковать только тогда, когда происходит что-то неисправимое (например, нулевая ссылка). Инициализация программы является исключением: нежелательные условия, которые должны привести к прерыванию программы при ее запуске, могут вызвать панику.
var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))
Предпочитаю использовать даже в тестовом кодеt.Fatal
илиt.FailNow
вместо паники, чтобы убедиться, что сбой отмечен.
Bad | Good |
---|---|
|
|
Используйте go.uber.org/atomic
использоватьsync/atomicАтомарные операции над примитивными типами (int32
, int64
и т. д.), потому что легко забыть использовать атомарные операции для чтения или изменения переменных.
go.uber.org/atomicБезопасность типов добавляется к этим операциям путем сокрытия базового типа. Кроме того, он включает в себя удобныйatomic.Bool
тип.
Bad | Good |
---|---|
|
|
представление
Конкретные рекомендации по производительности применяются только к высокочастотным сценариям.
Предпочитаю strconv, а не fmt
При преобразовании примитивов в строки или из строкstrconv
соотношение скоростейfmt
быстрый.
Bad | Good |
---|---|
|
|
|
|
Избегайте преобразования строки в байт
Не создавайте повторно байтовые срезы из фиксированных строк. Вместо этого выполните преобразование и зафиксируйте результат.
Bad | Good |
---|---|
|
|
|
|
Попробуйте указать емкость карты при инициализации
Насколько это возможно, используйтеmake()
Предоставление информации о емкости во время инициализации
make(map[T1]T2, hint)
заmake()
Предоставьте информацию о емкости (подсказка) попробуйте изменить размер карты при инициализации,
Это уменьшает накладные расходы на увеличение и выделение при добавлении элементов на карту.
Обратите внимание, что карта не гарантирует выделение емкости подсказки. Таким образом, добавление элементов по-прежнему может выполнять распределение, даже если емкость предоставлена.
Bad | Good |
---|---|
|
|
|
|
Спецификация
последовательность
Некоторые из критериев, изложенных в этой статье, являются объективными оценками, основанными на контексте, контексте или субъективных суждениях;
Но самое главное,быть последовательным.
Непротиворечивый код легче поддерживать, он более разумен, требует меньшего обучения и его легче переносить, обновлять и исправлять ошибки по мере появления новых соглашений или возникновения ошибок.
И наоборот, единая кодовая база приводит к накладным расходам на обслуживание, неопределенности и когнитивным искажениям. Все это напрямую приводит к замедлению, Проверка кода болезненна и увеличивает количество ошибок
При применении этих стандартов к кодовой базе рекомендуются изменения на уровне пакета (или выше), а приложения на уровне подпакета нарушают вышеуказанные проблемы, вводя несколько стилей в один и тот же код.
Похожие объявления группируются вместе
Язык Go поддерживает группировку похожих объявлений внутри группы.
Bad | Good |
---|---|
|
|
То же самое относится к константам, переменным и объявлениям типов:
Bad | Good |
---|---|
|
|
Группируйте только связанные объявления вместе. Не группируйте несвязанные объявления вместе.
Bad | Good |
---|---|
|
|
Нет ограничений на то, где можно использовать группировки, например: вы можете использовать их внутри функций:
Bad | Good |
---|---|
|
|
группировка импорта
Импорт следует разделить на две группы:
- Стандартная библиотека
- другие библиотеки
По умолчанию это группа приложений goimports.
Bad | Good |
---|---|
|
|
Имена пакетов
При именовании пакета выбирайте имя в соответствии со следующими правилами:
- Все в нижнем регистре. Без заглавных букв и подчеркивания.
- В большинстве случаев при использовании именованного импорта переименование не требуется.
- Коротко и лаконично. Помните, что имя полностью идентифицируется в каждом месте, где оно используется.
- Нет множественного числа. Например
net/url
, вместоnet/urls
. - Не используйте "common", "util", "shared" или "lib". Это плохие, малоинформативные имена.
смотрите такжеPackage NamesиРуководство по стилю Go Pack.
Имя функции
Мы следим за сообществом Go об использованииMixedCaps как имя функциисоглашение. За одним исключением, чтобы сгруппировать связанные тестовые примеры, имена функций могут содержать символы подчеркивания, например:TestMyFunction_WhatIsBeingTested
.
импортировать псевдоним
Если имя пакета не соответствует последнему элементу пути импорта, необходимо использовать псевдоним импорта.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
Во всех других случаях следует избегать псевдонимов импорта, если только между импортами нет прямого конфликта.
Bad | Good |
---|---|
|
|
Группировка функций и порядок
- Функции должны быть отсортированы в грубом порядке вызова.
- Функции в одном файле должны быть сгруппированы по получателю.
Поэтому экспортируемые функции должны появиться в файле первыми, вstruct
, const
, var
за определением.
После определения типа, но перед остальными методами получателя,newXYZ()
/NewXYZ()
Поскольку функции сгруппированы по получателям, обычные служебные функции должны появиться в конце файла.
Bad | Good |
---|---|
|
|
уменьшить вложенность
Код должен уменьшать вложенность, сначала обрабатывая условия ошибок/особые случаи и возвращаясь как можно раньше или продолжая цикл как можно раньше. Уменьшите объем кода, который содержит несколько уровней кода.
Bad | Good |
---|---|
|
|
ненужное еще
Если переменная установлена в обеих ветвях if, ее можно заменить одним if.
Bad | Good |
---|---|
|
|
объявление переменной верхнего уровня
На верхнем уровне используйте стандартныйvar
ключевые слова. Не указывайте тип, если он не отличается от типа выражения.
Bad | Good |
---|---|
|
|
Укажите тип, если тип выражения не совсем соответствует желаемому типу.
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// F 返回一个 myError 类型的实例,但是我们要 error 类型
Для неэкспортируемых констант и переменных верхнего уровня используйте префикс _.
на неэкспортированном верхнем уровнеvars
иconsts
, с префиксом _, чтобы сделать их явными при использовании для обозначения того, что они являются глобальными символами.
Исключение: неэкспортированное значение ошибки, должно начинаться сerr
начало.
Обоснование: переменные и константы верхнего уровня имеют область действия пакета. Использование общего имени может упростить случайное использование неправильного значения в других файлах.
Bad | Good |
---|---|
|
|
Встраивание в структуру
Встроенные типы (такие как мьютекс) должны быть вверху списка полей в структуре, и должна быть пустая строка, отделяющая встроенные поля от обычных полей.
Bad | Good |
---|---|
|
|
Инициализировать структуру с именами полей
При инициализации структуры вы почти всегда должны указывать имена полей. сейчасgo vet
Обязательный.
Bad | Good |
---|---|
|
|
Исключение: Имена полей могут быть опущены в тестовой таблице, если есть 3 или меньше полей.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
объявление локальной переменной
Если вы явно устанавливаете переменную в значение, вы должны использовать короткую форму объявления переменной (:=
).
Bad | Good |
---|---|
|
|
Однако в некоторых случаяхvar
Значения по умолчанию более понятны при использовании ключевых слов. Например, объявить пустой слайс.
Bad | Good |
---|---|
|
|
nil является допустимым фрагментом
nil
является допустимым фрагментом длины 0, что означает,
-
Вы не должны явно возвращать срез нулевой длины. должен вернуться
nil
вместо.Bad Good if x == "" { return []int{} }
if x == "" { return nil }
-
Чтобы проверить, пуст ли срез, всегда используйте
len(s) == 0
. вместоnil
.Bad Good func isEmpty(s []string) bool { return s == nil }
func isEmpty(s []string) bool { return len(s) == 0 }
-
Срез с нулевым значением (с
var
объявленные слайсы) доступны сразу без вызоваmake()
Создайте.Bad Good nums := []int{} // or, nums := make([]int) if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) }
var nums []int if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) }
малая переменная область видимости
Если возможно, постарайтесь сузить область переменных. если это не соответствуетуменьшить вложенностьконфликт правил.
Bad | Good |
---|---|
|
|
Если вам нужно использовать результат вызова функции за пределами if , вам не следует пытаться сузить его.
Bad | Good |
---|---|
|
|
Избегайте открытых параметров
в вызове функции意义不明确的参数
Может повредить читаемости. Если значение имени параметра не очевидно, добавьте к параметру комментарий в стиле C (/* ... */
)
Bad | Good |
---|---|
|
|
Для приведенного выше примера кода есть лучший способ справиться с вышеуказаннымbool
Введите пользовательский тип. В будущем этот параметр может поддерживать более двух состояний (true/false).
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady = iota + 1
StatusDone
// Maybe we will have a StatusInProgress in the future.
)
func printInfo(name string, region Region, status Status)
Используйте необработанные строковые литералы, избегайте экранирования
Go поддерживает использованиенеобработанный строковый литерал, то есть " ` " для представления собственной строки. В сценариях, которые необходимо экранировать, мы должны попытаться использовать эту схему для замены.
Может занимать несколько строк и включать кавычки. Используйте эти строки, чтобы избежать экранированных вручную строк, которые труднее читать.
Bad | Good |
---|---|
|
|
Инициализировать ссылку на структуру
При инициализации ссылки на структуру используйте&T{}
заменятьnew(T)
, чтобы сделать его совместимым с инициализацией структуры.
Bad | Good |
---|---|
|
|
Инициализировать карты
Для использования пустой картыmake(..)
инициализируется, и карта заполняется программно.
Это заставляет инициализацию карты вести себя иначе, чем объявление, а также удобно добавляет подсказки размера после make .
Bad | Good |
---|---|
|
|
Декларация и инициализация выглядят очень похоже. |
Декларация и инициализация выглядят очень по-разному. |
Если возможно, укажите емкость карты во время инициализации.Подробнее см.Попробуйте указать емкость карты при инициализации.
Кроме того, если карта содержит фиксированный список элементов, используйте литералы карты (список инициализации карты) для инициализации карты.
Bad | Good |
---|---|
|
|
Основное правило: используйте список инициализатора карты, чтобы добавить фиксированный набор элементов во время инициализации. в противном случае используйтеmake
(Если возможно, попробуйте указать емкость карты).
строковый формат строки
если тыPrintf
Функция -style объявляет строку формата, помещает строку формата снаружи и устанавливает для нее значениеconst
постоянный.
это может помочьgo vet
Выполните статический анализ строки формата.
Bad | Good |
---|---|
|
|
Функции для именования стилей Printf
утверждениеPrintf
-style, убедитесь, чтоgo vet
Его можно обнаружить и проверить строку формата.
Это означает, что вы должны использовать предопределенныйPrintf
имя функции в стиле.go vet
Они будут проверены по умолчанию. Для получения дополнительной информации см.Серия Printf.
Если предопределенное имя нельзя использовать, завершите выбранное имя буквой f:Wrapf
, вместоWrap
.go vet
Можно попросить проверить определенное имя стиля Printf, но имя должно начинаться сf
конец.
$ go vet -printfuncs=wrapf,statusf
смотрите такжеgo vet: Printf family check.
режим программирования
табличное тестирование
Когда логика теста повторяется, передатьsubtestsНаписание case-кода табличным способом выглядит намного чище.
Bad | Good |
---|---|
|
|
Очевидно, что использование метода тестовой таблицы будет более понятным при расширении логики кода, например при добавлении тестовых случаев.
Мы следуем соглашению, что мы называем структурные срезы какtests
. Каждый тестовый пример называетсяtt
. Кроме того, мы рекомендуем использоватьgive
иwant
Префиксы описывают входные и выходные значения для каждого теста.
tests := []struct{
give string
wantHost string
wantPort string
}{
// ...
}
for _, tt := range tests {
// ...
}
Параметры функций
Функциональная опция — это шаблон, в котором вы объявляете непрозрачный тип опции, который записывает информацию в некоторую внутреннюю структуру. Вы принимаете переменную нумерацию этих опций и действуете на полной информации, записанной опциями внутренней структуры.
Используйте этот шаблон для необязательных параметров в конструкторах и других общедоступных API-интерфейсах, которые необходимо расширить, особенно если эти функции уже имеют три или более параметра.
Bad | Good |
---|---|
|
|
Вы также можете обратиться к следующей информации:
Эта статья отредактирована или перепечатана учебными заметками zshipu.com.Если есть какие-либо нарушения, пожалуйста, свяжитесь с нами, и это должно быть изменено.