Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Konstantin8105/Effective_Go_RU
Перевод - Эффективный Go
https://github.com/Konstantin8105/Effective_Go_RU
documentation effective-go go golang
Last synced: 3 months ago
JSON representation
Перевод - Эффективный Go
- Host: GitHub
- URL: https://github.com/Konstantin8105/Effective_Go_RU
- Owner: Konstantin8105
- License: mit
- Created: 2017-01-25T18:35:49.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2024-05-03T19:27:13.000Z (6 months ago)
- Last Synced: 2024-05-20T23:03:18.653Z (6 months ago)
- Topics: documentation, effective-go, go, golang
- Language: HTML
- Homepage: https://golang.org/doc/effective_go.html
- Size: 389 KB
- Stars: 730
- Watchers: 47
- Forks: 116
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-skills - Эффективный Go
README
# Effective Go (RU) (Эффективный Go)
------
Оригинал смотри: https://golang.org/doc/effective_go.html
`go version go1.7.4`------
**Список дополнительных материалов:**
* [Руководство сотрудничества](https://github.com/Konstantin8105/Contribution_Guide_RU)
* [Эффективный Go](https://github.com/Konstantin8105/Effective_Go_RU)------
# Оглавление* [Введение](#Введение)
* [Примеры](#Примеры)
* [Форматирование](#Форматирование)
* [Комментарии](#Комментарии)
* [Именование](#Именование)
* [Именование пакетов](#Именование-пакетов)
* [Геттеры](#Геттеры)
* [Имена интерфейсов](#Имена-интерфейсов)
* [MixedCaps](#mixedcaps)
* [Точка с запятой](#Точка-с-запятой)
* [Управляющие структуры](#Управляющие-структуры)
* [If](#if)
* [Переопределение и переприсваивание](#Переопределение-и-переприсваивание)
* [Оператор For](#Оператор-for)
* [Switch(переключатель)](#switchпереключатель)
* [Переключатель типов (Типизированный переключатель, Type switch)](#Переключатель-типов-типизированный-переключатель-type-switch)
* [Функции и методы(Functions, методы)](#Функции-и-методыfunctions-методы)
* [Множественное возвращение результатов](#Множественное-возвращение-результатов)
* [Именование параметров результата](#Именование-параметров-результата)
* [Отсроченный вызов (Defer)](#Отсроченный-вызов-defer)
* [Данные](#Данные)
* [Созданные с помощью new](#Созданные-с-помощью-new)
* [Конструкторы и составные литералы](#Конструкторы-и-составные-литералы)
* [Создание с помощью make](#Создание-с-помощью-make)
* [Массивы](#Массивы)
* [Срезы(Slices, слайсы)](#Срезыslices-слайсы)
* [Двухмерные срезы](#Двухмерные-срезы)
* [Карты(Maps)](#Картыmaps)
* [Печать(Printing)](#Печатьprinting)
* [Присоединение(Append)](#Присоединениеappend)
* [Инициализация(Initialization)](#Инициализацияinitialization)
* [Константы(Constants)](#Константыconstants)
* [Переменные(Variables)](#Переменныеvariables)
* [Функция init](#Функция-init)
* [Методы(Methods)](#Методыmethods)
* [Указатели или Значения](#Указатели-или-Значения)
* [Интерфейсы и другие типы](#Интерфейсы-и-другие-типы)
* [Интерфейсы](#Интерфейсы)
* [Преобразование (Conversions)](#Преобразование-conversions)
* [Конвертация интерфейсов и привязка типов](#Конвертация-интерфейсов-и-привязка-типов)
* [Общее(Generality)](#Общееgenerality)
* [Интерфейсы и методы(функции)](#Интерфейсы-и-методыфункции)
* [Пустой идентификатор (The blank identifier **_**)](#Пустой-идентификатор-the-blank-identifier-_)
* [Пустой идентификатор в множественном присваивании (**_**)](#Пустой-идентификатор-в-множественном-присваивании-_)
* [Неиспользуемое импортирование и значения](#Неиспользуемое-импортирование-и-значения)
* [Импортирование для побочного эффекта (Import for side effect)](#Импортирование-для-побочного-эффекта-import-for-side-effect)
* [Проверка интерфейса (Interface checks)](#Проверка-интерфейса-interface-checks)
* [Вложение (Embedding)](#Вложение-embedding)
* [Согласованность, параллельная обработка, параллельное выполнение (Concurrency)](#Согласованность-параллельная-обработка-параллельное-выполнение--concurrency)
* [Распределение памяти по сообщениям (Share by communicating)](#Распределение-памяти-по-сообщениям-share-by-communicating)
* [Го-рутины (Goroutines)](#Го-рутины-goroutines)
* [Каналы (Channels)](#Каналы-channels)
* [Канал каналов (Channels of channels)](#Канал-каналов-channels-of-channels)
* [Параллелизм (Parallelization)](#Параллелизм-parallelization)
* [Текущий буфер (A leaky buffer)](#Текущий-буфер-a-leaky-buffer)
* [Ошибки (Errors)](#Ошибки-errors)
* [Паника (Panic)](#Паника-panic)
* [Восстановление (Recover)](#Восстановление-recover)
* [Веб-сервер](#Веб-сервер)--------------
## Введение
[^](#Оглавление)
Go - это новый язык программирования. Хотя, он заимствует идеи из существующих языков, он обладает необычными свойствами, которые позволяют создавать эффективные программы, язык Go отличается по своему характеру от программ, написанных на родственных языках. Прямолинейный перевод C++ или Java программ в Go вряд ли даст удовлетворительный результат, т.к. Java программы написаны на Java, не на Go. С другой стороны, думая о проблеме с точки зрения Go можно добиться успеха, но это уже другая программа. Другими словами, для хорошего написания кода на языке Go, важно понимать его особенности и идиомы.
Также важно знать установленные соглашения для программирования на Go, такие как именование, форматирование, разработка программ и так далее, так чтобы программы написанные Вами были простыми для понимания другими программистами Go.Этот документ даёт примеры для написания чистого, идеоматичного кода на Go.
Он дополняет [спецификацию языка](https://golang.org/ref/spec), [Тур по Go](https://tour.golang.org/),
и [Как писать на Go](https://golang.org/doc/code.html), каждую из которых необходимо прочитать в первую очередь.[^](#Оглавление)
### Примеры
[^](#Оглавление)
[Go пакеты исходных кодов](https://golang.org/src/) предназначены не только в качестве основных библиотек, но и в качестве примеров использования языка.
Кроме того, многие пакеты имеют работающие, автономные исполняемые примеры и Вы можете запустить напрямую с помощью страницы [golang.org](https://golang.org), такие как [этот](https://golang.org/pkg/strings/#example_Map) (если необходимо, нажмите на слово "Примеры" чтобы открыть их).
Если у Вас есть вопрос о том как решить какую-либо проблему или как что-то реализовать, то документация, исходные коды и примеры в библиотеке могут дать ответ, идею или объяснение.[^](#Оглавление)
## Форматирование
[^](#Оглавление)
Форматирование является наиболее спорным, но не сильно важным вопросом.
Люди могут привыкнуть к различным стилям форматирования, но было бы лучше, если бы этого не приходилось делать и меньше времени придавалось этой теме, если бы все использовали одинаковый стиль.
Проблема данной утопии в том, как это сделать без длинного руководства по стилю.В Go мы используем нетипичный подход и передаем машине заботу о форматировании.
Программа `gofmt` (также доступна, как `go fmt`, которая производит действия на уровне пакета, а не на уровне файлов) читает код на Go и выпускает исходный код со стандартным стилем отступов и вертикальным выравниванием, сохраняет, и при необходимости, переформатирует комментарии.
Если Вы хотите знать, как по-новому структурировать код, запустите `gofmt`; если структура неверна, `gofmt` поправит Вашу программу (или файл сообщит об ошибке `gofmt`), не работайте в обход форматирования программой `gofmt`.К примеру, нет необходимости тратить время на выравнивание комментариев для полей структур, т.к. `gofmt` сделает это за Вас.
Для данного фрагмента кода```golang
type T struct {
name string // name of the object
value int // its value
}
````gofmt` произведет выравнивание по колонкам:
```golang
type T struct {
name string // name of the object
value int // its value
}
```Все стандартные пакеты Go отформатированы с помощью `gofmt`.
Очень коротко о некоторых деталях форматирования:
#### Абзац
Мы используем табуляцию для абзацев и `gofmt` делает это по умолчанию. Используйте пробелы только при острой необходимости.
#### Длина строки
Go не имеет предела длины строки. Не беспокойтесь о длинных строках. Если строка кажется слишком длинной, прервите ее и добавьте дополнительный отступ (символ табуляции) на новой строке.
#### Круглые скобки
Go нуждается в меньшем количестве круглых скобок, чем C и Java: структуры ветвления, цикла ( `if` , `for` , `switch`) не имеют круглых скобок в своём синтаксисе. Также, иерархия операторов стала проще и короче. К примеру, выражение```
x<<8 + y<<16
```
не нуждается в добавлении пробелов, в отличии от других языков.[^](#Оглавление)
## Комментарии
[^](#Оглавление)
Go использует C-стиль `/* */` для блока комментариев и C++-стиль `//` для однострочных комментариев.
Как правило, используются однострочные комментарии. Блок комментариев, в основном, используется при комментировании пакетов,
но также для выразительности или отключения большого участка кода.Программа и веб-сервер - `godoc` обрабатывает Go исходники пакета для формирования документации.
Комментарии, расположенные сразу над объявлением (без дополнительных пустых строк), извлекаются вместе с объявлением для пояснения данного элемента.
Характер и стиль комментариев напрямую влияет на качество документации производимой `godoc`.Каждый пакет должен иметь *комментарий пакета* - это блок комментариев предшествующий объявлению пакета.
Для пакетов состоящих из нескольких файлов, комментарий пакета может быть расположен в любом из файлов, но только в одном из них.
Комментарий пакета должен представлять информацию о пакете в целом.
Он будет отображен вначале страницы `godoc` и должен представлять из себя детальную информацию, которой можно пользоваться.```golang
/*
Package regexp implements a simple library for regular expressions.The syntax of the regular expressions accepted is:
regexp:
concatenation { '|' concatenation }
concatenation:
{ closure }
closure:
term [ '*' | '+' | '?' ]
term:
'^'
'$'
'.'
character
'[' [ '^' ] character-ranges ']'
'(' regexp ')'
*/
package regexp
```Если пакет простой, то комментарий может быть кратким.
```golang
// Package path implements utility routines for
// manipulating slash-separated filename paths.
```Дополнительное форматирование, к примеру баннер из * (звездочек), не требуется.
Шрифт для сформированного результата не обязательно будет моноширинный, поэтому не полагайтесь на пробелы при выравнивании, `godoc`, также как `gofmt`, позаботятся об этом.
Комментарии интерпретируются как простой текст, поэтому HTML и другие аннотации такие как `_эта_` воспроизводятся *дословно* и поэтому не должны использоваться. Единственное исключение,
которое делает `godoc`, это выделение моноширинным шрифтом участков кода с отступами.
Хорошим примером такого исключения является комментарий к пакету [`fmt`](https://golang.org/pkg/fmt/).В зависимости от контекста, `godoc` не может переформатировать комментарии, поэтому убедитесь, что они выглядят хорошо: используйте правильное правописание, знаки препинания, структуру предложения и т.д.
Любые комментарии внутри пакета, предшествующие объявлению, используются как описание этого объявления.
Каждый экспортируемый объект, название которого начинается с большой буквы, должен иметь комментарий.Лучше всего использовать комментарии в виде полных предложений. Это позволяет производить их автоматическую обработку.
Первое предложение должно быть ключевым и начинаться с имени объявления.```golang
// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {
```Если комментарий начинается с имени, то `godoc` может с использоваться совместно с `grep`.
Представьте, что Вы не можете вспомнить имя "Compile", но Вы ищите *the parsing function* для регулярных выражений и тогда Вы можете выполнить команду:```command
$ godoc regexp | grep -i parse
```Если все комментарии в пакете начинаются с "This function...", `grep` не сможет помочь с поиском имени.
Если же комментарии начинаются с имени, Вы можете увидеть что-то вроде следующего результата, который напомнит Вам о том, что Вы искали.```command
$ godoc regexp | grep parse
Compile parses a regular expression and returns, if successful, a Regexp
parsed. It simplifies safe initialization of global variables holding
cannot be parsed. It simplifies safe initialization of global variables
$
```Синтаксис Go допускает групповое объявление. Для каждой группы констант или переменных может быть представлен один общий комментарий. Однако такое объявление выглядит небрежно.
```golang
// Error codes returned by failures to parse an expression.
var (
ErrInternal = errors.New("regexp: internal error")
ErrUnmatchedLpar = errors.New("regexp: unmatched '('")
ErrUnmatchedRpar = errors.New("regexp: unmatched ')'")
...
)
```Группировка также может показать взаимосвязи между элементами, к примеру, группа переменных защищенных mutex:
```golang
var (
countLock sync.Mutex
inputCount uint32
outputCount uint32
errorCount uint32
)
```[^](#Оглавление)
## Именование
[^](#Оглавление)
Именование очень важно в Go, как и в других языках.
Они имеют семантический эффект: **Видимость имени за пределами пакета, определяется по первой букве имени, которая, если является заглавной, то имя будет видно вне это пакета**.
Именно поэтому стоит уделить время обсуждению соглашения об именовании в программах Go.### Именование пакетов
[^](#Оглавление)
Когда пакет импортируется, имя пакета используется для доступа к его содержимому.
После того, как пакет импортирован,```golang
import "bytes"
```можно использовать `bytes.Buffer`. Это полезно, если все, кто использует пакет, могут использовать одно и то же имя, для обращения к его содержимому, подразумевается, что имя пакета должно быть коротким, четким и запоминающимся. В соответствии с соглашением,имена пакетов состоят из одного слова в нижнем регистре; нет необходимости в использовании подчеркиваний или СмешанногоРегистра. При выборе длинного имени пакета, всем, кто будет его использовать, придётся писать это имя. Но не беспокойтесь об уникальности имени.
Имя пакета только по умолчанию используется при импорте; оно не должно быть глобально уникальным, и в редких случаях, при импорте может быть указано другое имя. В любом случае,
путаница встречается редко, так как имя файла в импорте определяет, какой именно пакет используется.Согласно другому соглашению, имя пакета является базовым именем его исходного каталога; пакет `src/encoding/base64` импортируется как `"encoding/base64"` и имеет название `base64`, а не `encoding_base64` и не `encodingBase64`.
Импортирующий пакет будет использовать имя пакета для обозначения его содержимого, поэтому при экспорте может учитываться этот факт, чтобы избежать повторения.
(Не используйте `import .`, это, конечно, может упростить запуск тестов вне пакета, но в других случаях использоваться не должно). Например, тип *reader* для буферного чтения описанный в пакете `bufio` называется `Reader`, а не `BufReader`, т.к пользователи его видят как `bufio.Reader`, имя которого кратко и понятно.Более того, т.к. импортируемые объекты адресуются по имени пакета, следовательно `bufio.Reader` не будет конфликтовать с `io.Reader`.
Аналогично, функция для создания нового экземпляра объекта `ring.Ring`, которая объявлена как *конструктор* в Go, может называться `NewRing`, но т.к. `Ring` - это экспортируемый тип из пакета `ring`, функция-конструктор может называться просто `New`, которую, можно будет вызвать как `ring.New`. Используйте структуру пакетов при
выборе имен.Другой короткий пример функция `once.Do`; `once.Do(setup)` читается хорошо, и при этом
лучше не станет, если ее переименовать в `once.DoOrWaitUntilDone(setup)`.
Длинные имена не делают названия более читабельными. В то время как комментарии
могут быть более ценным, чем длинные имена.## Геттеры
[^](#Оглавление)
Go не предоставляет автоматическую поддержку геттеров и сеттеров.
Но не будет ошибкой создание геттеров и сеттеров самостоятельно, и если это необходимо, то делайте так, но идиоматически нет необходимости добавлять `Get` в имя геттера.
Если у Вас есть поле с именем `owner` (с маленькой буквы, неэкспортируемое), то геттер может называться `Owner` (с большой буквы, экспортируемый), а не `GetOwner`.
Использование имен, начинающихся с заглавной буквы, позволяет отделить экспортируемые методы от неэкспортируемых полей. Cеттер, при необходимости, может быть назван `SetOwner`.
Оба примера в следующем коде:```golang
owner := obj.Owner()
if owner != user {
obj.SetOwner(user)
}
```### Имена интерфейсов
[^](#Оглавление)
По соглашению, интерфейсы с одним методом должны называться как метод с суффиксом `-er` или подобно этому, для образования существительного: `Reader`, `Writer`, `Formatter`, `CloseNotifier` и т.д.
Существует целый ряд имен, которыe соблюдают это соглашение и содержат подобные методы. `Read` , `Write` , `Close`, `Flush`, `String` и т.д., имеют канонические подписи и значения. Чтобы избежать путаницы, не давайте методу ни одного из этих имен, если оно не имеет ту же сигнатуру и значение. С другой стороны, если ваш тип реализует метод с тем же значением, как и метод хорошо известного типа, то дайте ему то же имя и значение; назовите Ваш метод конвертации в строку `String` , а не `ToString`.
### MixedCaps
[^](#Оглавление)
В заключении, Go соглашение использует `MixedCaps` или `mixedCaps` , а не подчеркивание для имен из нескольких слов.
### Точка с запятой
[^](#Оглавление)
Как и в С, грамматика Go формально использует точку с запятой для разделения
операций-выражений (инструкций), но в отличии от C, точка с запятой не представлена
в исходном коде. Вместо этого, лексер использует простое правило добавления
точки с запятой автоматически, при сканировани. Таким образом текст на входе
по большей части освобожден от них.Правило такое. Если последний токен(лексема) перед символом новой строки - идентификатор (который включает такие слова, как `int` и `float64`), базовый литерал, такой как число или строковая константа, или один из нижеперечисленных токенов
```golang
break continue fallthrough return ++ -- ) }
```то, лексер всегда добавляет точку с запятой после него. Вкратце, это может звучать так: "Если новая строка начинается после токена, который может закрывать операцию-выражение, то добавить точку с запятой".
Точка с запятой также может быть опущена сразу перед закрывающей скобкой, таким
образом для операции-выражения такой как:```golang
go func() { for { dst <- <-src } }()
```точка с запятой не требуется.
Как следствие из правила, вы не можете перенести открывающую скобку управляющих
структур (`if`, `for`, `switch` или `select`) на новую строку. Если перенесете,
точка с запятой будет вставлена перед скобкой, которая может стать причиной
нежелательных эффектов. Пишите так,```golang
if i < f() {
g()
}
```но не так
```golang
if i < f() // ошибка!
{ // ошибка!
g()
}
```[^](#Оглавление)
## Управляющие структуры
[^](#Оглавление)
Управляющие структуры в Go аналогичны тем же структурам в C, но имеют ряд важных отличий. Во-первых нет циклов `do` и `while`, есть лишь обобщенный `for`. Во-вторых, `switch` более гибкий. В-третьих `if` и `switch` имеют опциональную инициализацию переменных, как и в `for`. В-четвертых, `break` и `continue` опционально принимают метку, к которой необходимо перейти. В-пятых, есть новые операторы, такие как типизированный `switch` и многоканальный `select`. Синтаксис также немного отличается: отсутствуют круглые скобки в условии, и тело структуры всегда должно быть ограничено фигурными скобками.
[^](#Оглавление)
### If
[^](#Оглавление)
В Go простой `if` выглядит так:
```golang
if x > 0 {
return y
}
```Обязательные фигурные скобки упрощают написание простых условий `if` на
несколько строк. Это хороший стиль в любом случае, особенно когда тело содержит управляющие операторы, такие как `return` или `break`.Поскольку `if` и `switch` допускают инициализацию переменных, то часто можно
видеть подобную запись:```golang
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
```В библиотеках Go, вы найдёте подобную запись, если `if` не переходит в следующий блок, т.е. в теле используется `break`, `continue`, `goto` или `return`, а необязательный `else` опускается.
```golang
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
```В данном примере представлена общая схема, где код защищен от серии ошибок. Код читается хорошо, если выполняется без ошибок, обходя случаи их возникновения. Так как ошибки приводят к завершению выполнения блока с помощью `return`, то блок `else` не требуется.
```golang
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
```[^](#Оглавление)
### Переопределение и переприсваивание
[^](#Оглавление)
Последний пример предыдущего раздела демонстрирует использование краткой формы объявления переменных `:=`. Вызов `os.Open` объявляет сразу две переменных `f` и `err`
```golang
f, err := os.Open(name)
```Несколькими строками ниже вызывается `f.Stat`,
```golang
d, err := f.Stat()
```который выглядит как объявления двух переменных `d` и `err`. Хотя `err` присутствует в обоих объявлениях. Это дублирование вполне законно: `err` объявляется в первом случае, и лишь переприсваивается во втором. Это означает, что `f.Stat` использует уже существующую переменную `err`, определенную выше, и просто присваивает ей новое значение.
В объявлении `:=` переменная `v` может присутствовать, даже если она уже объявлена, при условии:
* если объявление происходит в той же самой области видимости, что и существующая переменная `v` (если `v` уже объявлена за пределами видимости, то объявление создаст новую переменную §)
* соответствующее значение, при инициализации, может быть присвоено `v`
* существует хотя бы одна новая переменная в объявлении, которая будет создана зановоЭто необычное свойство - чистая практичность, которая служит для упрощения
использования одной переменной `err`, к примеру, в длинных цепочках `if-else`.
Вы увидите, это используется часто.§ Нет ничего плохого в том, что в Go область видимости параметров и возвращаемых значений функции - есть само тело функции, хотя они лексически находятся за скобками, ограничивающими тело функции.
[^](#Оглавление)
### Оператор For
[^](#Оглавление)
В Go цикл `for` очень похож, но не такой же как в C. Он унифицирует `for` и `while`, при этом отсутствует `do-while` цикл. Существует 3 различных формы, и только в одной из них используется точка с запятой.
```golang
// C-подобный for
for init; condition; post { }// C-подобный while
for condition { }// C-подобный for(;;)
for { }
```Краткая запись позволяет легко объявить начальные условия прямо в цикле:
```golang
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
```Если Вы итерируетесь по массиву, срезу, строке или map'у, или читаете из канала, то для управления можно использовать `range`.
```golang
for key, value := range oldMap {
newMap[key] = value
}
```Если необходимо использовать только первый элемент *диапазона* (ключ или индекс), отбросьте второй:
```golang
for key := range m {
if key.expired() {
delete(m, key)
}
}
```Если вам необходим только второй элемент (значение), то используйте *пустой идентификатор* (**_**) в качестве первого элемента:
```golang
sum := 0
for _ , value := range array {
sum += value
}
```Пустой идентификатор используется в разных случаях и будет описан позже.
Для строк, оператор `range` выполняет ещё больше работы, к примеру разделяет строку по символам Unicode в соответствии с UTF-8. При ошибочном использование кодировки, побайтово заменяет рунами(*rune*) U+FFFD. (`rune` (и одноименный встроенный тип) в терминологии Go используется для работы с символами Unicode. Смотрите детальную информацию в [Спецификации языка](https://golang.org/ref/spec#Rune_literals)).
Данный цикл:
```golang
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
```Выводит:
```command
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7
```И в заключении, в языке Go нет оператора `запятая`, а `++` и `--` являются инструкциями, но не выражениями. Таким образом, если Вам необходимо использовать несколько переменных в цикле `for`, то Вы можете использовать параллельное определение переменных (без использования `++` и `--`).
```golang
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
```[^](#Оглавление)
### Switch(переключатель)
[^](#Оглавление)
В языке Go `switch` более обобщён, нежели в C. Выражения не обязательно должны
быть константами или даже целыми числами, условия проверяются сверху-вниз до нахождения соответствия, и если `switch` не имеет выражений, то переходит в `true`. Следовательно, идиоматически возможно записывать `if-else-if-else` цепочку как `switch`.```golang
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
```Автоматический пропуск условий отсутствует, но, при этом, условия могут быть записаны через запятую:
```golang
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
```
Несмотря на то, что они не столь распространены в Go, как в некоторых других C-подобных языках, `break` может быть использован для досрочного прерывания `switch`.
Хотя, иногда, надо прервать внешний (по отношению к `switch`) цикл, а не сам `switch`, и в Go это может быть достигнуто путём добавления метки перед циклом, и переходом к этой метке в случае вызова `break`. В следующем примере представлены оба случая:```golang
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]< b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
```[^](#Оглавление)
### Переключатель типов (Типизированный переключатель, Type switch)
[^](#Оглавление)
`switch` может быть использован для определения динамических типов интерфейсных переменных. Так, типизированный `switch` использует синтаксис приведения типов,
с ключевым словом `type` внутри скобок. Если `switch` объявляет переменную в
выражении, то переменная будет иметь соответствующий тип в каждом пункте. Также, идиоматически верно переиспользовать имена переменных для объявления новых переменных
с тем же именем, но другим типом в каждом случае:```golang
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
```## Функции и методы(Functions, методы)
[^](#Оглавление)
### Множественное возвращение результатов
[^](#Оглавление)
Одно из особенностей языка Go - это то, что функции и методы могут возвращать множество значений.
При использовании языка С, передача ошибки производится через отрицательное значение с описанием причины ошибки в "другом" месте.
При использовании языка Go, функция `Write` может вернуть одновременно **и** возвращаемое значение **и** ошибку.
Сигнатура метода `Write` в файлах пакета `os`:```golang
func (file *File) Write(b []byte) (n int, err error)
```и как предусмотрено документацией, он возвращает число записанных байт и ненулевое значение ошибки `error`, когда `n` `!=` `len(b)`.
Это общий стиль, смотрите также раздел посвящённый ошибкам в качестве примера.Данный подход исключает необходимость в возращении значимого параметра.
Это очень простой способ возвращения из функции количества байт среза, возвращая число и следующий параметр.```golang
func nextInt(b []byte, i int) (int, int) {
for ; i < len(b) && !isDigit(b[i]); i++ {
}
x := 0
for ; i < len(b) && isDigit(b[i]); i++ {
x = x*10 + int(b[i]) - '0'
}
return x, i
}
```Вы можете сканировать число чисел во входном срезе `b` следующим образом:
```golang
for i := 0; i < len(b); {
x, i = nextInt(b, i)
fmt.Println(x)
}
```### Именование параметров результата
[^](#Оглавление)
Возвращаемым "параметрам" в языке Go можно давать имена и это часто используется как входные параметры.
Когда они именованы, то они инициализируются нулевым значением необходимого типа в самом начале функции.
Если функция, в которой определены именованные параметры, вызывает конструкцию возврата без аргументов, то значения именованных параметров будут использованы ей как возвращаемые значения.
Именование не обязательное, но оно может сделать код короче и чище - самодокументированным.
Если имя результата будет `nextInt`, то очевидно что тип результата `int`.```golang
func nextInt(b []byte, pos int) (value, nextPos int) {
```На примере `io.ReadFull`:
```golang
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
```[^](#Оглавление)
### Отсроченный вызов (Defer)
[^](#Оглавление)
В языке Go есть оператор `defer` для управления отложенного вызова функции, который будет вызван, как только функция имеющая `defer` оканчивается.
Это не типичный но эффективный способ, когда необходимо закрыть ресурс после окончания функции.
Канонические примеры - работа с mutex или закрытие файла.```golang
// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}
```Отложенный вызов функции `Close` имеет 2 преимущества. Во-первых, гарантирует что не будет забыто закрытие файла - ошибка, которую легко сделать, если в последствии в функции будет изменен параметр на другую папку. Во-вторых, закрытие близко расположено к открытию, что более ясно, чем располагать его в конце функции.
Аргументы отложенной функции выполняются когда выполняется `defer`, а не когда функция вызвана.
Кроме того , во избежания беспокойства по поводу изменяющихся переменных в функции, одна отложенная функция может отложить вызов множества функций.Вот простой пример:
```golang
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
```Откладывание функции в LIFO очередь, приведет к следующей работе функции при печати на экран `4 3 2 1 0` . Более интересный пример - простое отслеживание функции в программе. Мы могли бы написать простое отслеживание, как это:
```golang
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }// Use them like this:
func a() {
trace("a")
defer untrace("a")
// do something....
}
```Мы могли бы сделать лучше - используя факт отложенных функций для оценки когда будет запущен `defer`. Отслеживаемая функция может настроить аргументы неотслеживаемой функции.
К примеру:```golang
func trace(s string) string {
fmt.Println("entering:", s)
return s
}func un(s string) {
fmt.Println("leaving:", s)
}func a() {
defer un(trace("a"))
fmt.Println("in a")
}func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}func main() {
b()
}
```выводит:
```
entering: b
in b
entering: a
in a
leaving: a
leaving: b
```Для программистов привыкших к блочному управлению ресурсами в других языках, функция `defer` может показаться странной, но интересной и мощной, так как позволяет уйти от блочного управления к управлению в функции. В разделах `panic` и `recover` будут также рассматриваться несколько примеров.
[^](#Оглавление)
## Данные
[^](#Оглавление)
### Созданные с помощью `new`
[^](#Оглавление)
Для создания примитивов в языке Go используются функции `new` и `make`.
Они разные и применяются для разных типов, это может сбить с толку, но правило очень просто.
Для начала обсудим функцию `new`.
Данная функция резервирует память, но не также как в других языках программирования, она не просто *инициализирует* память, а вместо этого заполняет *нулями*.К примеру `new(T)` резервирует память нулями для нового элемента типа `T` и возвращает его указатель на значение типа `*T`. В терминологии Go, он возвращает указатель на новую зарезервированную память заполненная нулями с типом `T`.
**TODO**
Since the memory returned by `new` is zeroed, it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization. This means a user of the data structure can create one with `new` and get right to work.
For example, the documentation for `bytes.Buffer` states that "the zero value for `Buffer` is an empty buffer ready to use."
Similarly, `sync.Mutex` does not have an explicit constructor or `Init` method.
Instead, the zero value for a `sync.Mutex` is defined to be an unlocked mutex.
The zero-value-is-useful property works transitively. Consider this type declaration.
**-**```golang
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
```**TODO**
Values of type `SyncedBuffer` are also ready to use immediately upon allocation or just declaration. In the next snippet, both `p` and `v` will work correctly without further arrangement.
**-**```golang
p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer
```### Конструкторы и составные литералы
[^](#Оглавление)
Иногда нулевых значений не достаточно и необходимо иметь конструктор, следующий пример взят из пакета `os`.
```golang
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File)
f.fd = fd
f.name = name
f.dirinfo = nil
f.nepipe = 0
return f
}
```Существует много шаблонов. Мы просто можем использовать *составные литералы*, которые будут создавать новые сущности каждый раз.
```golang
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := File{fd, name, nil, 0}
return &f
}
```Обратите внимание на то, что в отличии от языка С, это нормально, возвращать адрес локальных переменных, так как переменная уже существует после возвращения из функции.
На самом деле, возвращение адресов составных литералов создает новую сущность каждый раз, как он вычисляется.
Итак мы можем объединить последние две строки:```golang
return &File{fd, name, nil, 0}
```Поля составных литералов должны быть в порядке объявления и все должны присутствовать.
Однако, используя маркировку как пара *поле*`:`*значение*, могут инициализироваться в любом порядке, с пропущенными полями заполняемые нулями.
Таким образом, можно объявить:```golang
return &File{fd: fd, name: name}
```В предельном случае, когда составной литерал без полей вообще, то создание нулевым значением будет тип. Выражения `new(File)` и `&File{}` одинаковы.
Составные литералы могут также создавать массивы, срезы, карты, с пометкой полей как индексов или ключами карт.
К примеру, инициализированные значения `Enone`, `Eio`, и `Einval` разные.```golang
a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
```[^](#Оглавление)
### Создание с помощью `make`
[^](#Оглавление)
Возвращаясь к созданию элементов.
Встроенная функция `make(T, `*args*`)` служит для других целей нежели `new(T)`.
Он создает только срезы, карты и каналы, и возвращают *инициализированные* (не нулевые) значение типа `T` (а не `*T`).
Причиной различия для этих трех типов, в том что внутри они представляют из себя структуры данных, которые необходимо инициализировать перед использованием.
К примеру, срезы - это трехэлементная структура, содержащая указатель на данные(внутри массив), длину, и емкость, причём пока все элементы не инициализированы - срез *нулевой* `nil`.
Для срезов, карт и каналов, встроенная команда `make` инициализирует внутреннюю структуру данных и подготавливает значения к использованию.К примеру:
```golang
make([]int, 10, 100)
```создает массив из 100 значений типа `int` и затем создает структуру среза длинной 10 и емкостью 100 со ссылкой только на первые 10 элементов.
(Когда создается слайс, его емкость задавать не обязательно, смотрите раздел посвящённый срезам.)
В противоположность, `new([]int)` возвращает указатель на новый, созданный, заполненный нулями срез, это указатель на значение `nil` среза.Эти примеры показывают различие между `new` и `make`.
```golang
var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful
var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)// Idiomatic:
v := make([]int, 100)
```Помните что `make` используется только для карт, срезов и каналов и не возвращают указатель.
Для получения указателя в явном виде используйте `new` или возьмите указатель в явном виде.[^](#Оглавление)
### Массивы
[^](#Оглавление)
Массивы популярны когда точно известно необходимое количество памяти, чтобы не делать излишних пересозданий, но в первую очередь они являются составной частью для срезов, о которых будет описано в следующем разделе.
Какие основные отличия между обращением с массивами между языками Go и C:
* Массивы значений. Присвоение одно массива другому копирует все элементы.
* Если вы передаёте массив в функцию, то передаётся копия массива, а не указатель на него.
* Размер массива является частью массива. Типы `[10]int` и `[20]int` разные.Массивы могут быть полезными, но дорогими(с точки зрения производительности) и если Вы хотите иметь гибкость и эффективность схожее с поведением в языке C-like, то необходимо использовать в функциях указатели.
```golang
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
```Но данный стиль не подходит Go.
Используйте срезы вместо массивов.[^](#Оглавление)
### Срезы(Slices, слайсы)
[^](#Оглавление)
Срезы это обёртка для массивов и при этом более общий и мощный, и предоставляет собой более удобный интерфейс по управлению данными, в случаях, когда не известно точное количество элементов и необходимо преобразование размера массивов.
Большинство программ на языке Go, выполнены с использованием срезов, а не простых массивов.Срез хранит ссылку на массив и поэтому если приравнять срез к другому срезу, то будет тот же массив.
Если срез является аргументом функции, то изменения элементов в срезе будут видны вызывающему данному функцию, это аналогично передаче указателя на базовый массив.
В функция `Read` может принимать в качестве аргумента срез, что равнозначно указателю на массив и длины массива; длина среза указывает верхний предел количество данных которые необходимо прочитать.
В данном случае тип `File` пакета `os` имеет следующую сигнатуру метода `Read`:```golang
func (f * File) Read(buf []byte) (n int, err error)
```Метод возвращает количество прочитанных байт или если есть, то ошибку.
Для чтения первых 32 байт в буфере `buf`, *получить(срезать) часть* буфера.```golang
n, err := f.Read(buf[0:32])
```Такой срез является эффективным. На самом деле, если оставить в стороне эффективность, то следующий пример показывает чтение первых 32 байт из буфера.
```golang
var n int
var err error
for i := 0; i < 32; i++ {
nbytes, e := f.Read(buf[i:i+1]) // Read one byte.
if nbytes == 0 || e != nil {
err = e
break
}
n += nbytes
}
```Длина среза может меняться, пока не исчерпает размер внутреннего массива.
С помощью встроенной функции `cap` можно узнать *емкость* среза, представляющий максимальную длину среза.
В следующем примере рассматривается функция для добавления данных в срез.
Если данные превышают ёмкость среза, то срез необходимо переопределить.
Функция `Append` возвращает результирующий срез. Функция использует тот факт что использование `len` и `cap` допустимо, даже если у нас имеется нулевой срез `nil` - при этом возвращая 0.```golang
func Append(slice, data []byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))* 2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
for i, c := range data {
slice[l+i] = c
}
return slice
}
```**TODO**
We must return the slice afterwards because, although `Append`
can modify the elements of `slice`, the slice itself (the run-time data
structure holding the pointer, length, and capacity) is passed by value.
**-**Добавление элементов в срез настолько популярно, что функция `append` стала встроенной. Для того чтобы понять принцип работы данной функции нам необходимо больше информации, поэтому мы вернёмся к этому позже.
[^](#Оглавление)
### Двухмерные срезы
[^](#Оглавление)
Массивы и срезы в Go - одномерные.
Для создания двухмерного массива или среза, нам необходимо определять массив-массивов или срез-срезов, как в примере:```golang
type Transform [3][3]float64 // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte // A slice of byte slices.
```В связи с тем, что срезы переменной длины, то допустимо иметь каждый внутренний срез разной длины.
Это наиболее общая ситуация, как в примере `LinesOfText`, в котором каждая строка имеет независимую длину.```golang
text := LinesOfText{
[]byte("Now is the time"),
[]byte("for all good gophers"),
[]byte("to bring some fun to the party."),
}
```Иногда необходимо создавать двухмерные срезы, к примеру при обработки пикселей.
Есть 2 способа для этого:
* Первый, создание каждого среза независимо
* Второй, создание простого массива срезов.
Наилучший способ выбирается в зависимости от программы.
Если срез можно увеличивать или уменьшать, они должны быть независимы, для того чтобы избежать перезаписи новых строк. Если не требуется изменять размер, то наиболее эффективным был бы способ с создание одним их аллоцированием(инициализацией).
Рассмотрим оба способа.```golang
// Allocate the top-level slice.
picture := make([][]uint8, YSize) // One row per unit of y.
// Loop over the rows, allocating the slice for each row.
for i := range picture {
picture[i] = make([]uint8, XSize)
}
```с одним созданием:
```golang
// Allocate the top-level slice, the same as before.
picture := make([][]uint8, YSize) // One row per unit of y.
// Allocate one large slice to hold all the pixels.
pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
// Loop over the rows, slicing each row from the front of the remaining pixels slice.
for i := range picture {
picture[i], pixels = pixels[:XSize], pixels[XSize:]
}
```[^](#Оглавление)
### Карты(Maps)
[^](#Оглавление)
Карты - это удобная и мощная встроенная структура данных, связывающая значение одного типа(*ключ (key)*) со значением другого типа (*элемент (element)* или *значение (value)*).
Ключ может быть любого типа, для которого определён оператор равно, как для целых чисел, чисел с плавающей точкой или комплексные числа, строки, указатели, интерфейсы (если динамические типы поддерживают равенство), структуры и массивы.
Срезы не используются в качестве ключа для карт, так как равенство не определено для них.
Карты, также как и срезы, имеют внутреннюю структуру данных.
Если Вы передадите карту в функции и измените содержание карты, то изменения останутся для вызывающего.
Карты могут быть созданы с использованием синтаксиса составных литералов с разделением по колонкам пар ключ-значение, поэтому легко создать начальные данные.```golang
var timeZone = map[string]int{
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
"MST": -7*60*60,
"PST": -8*60*60,
}
```Добавление и получение значений из карт, синтаксически, выглядит как для массивов или срезов, за тем исключением того что индекс не обязательно должен быть целым числом.
```golang
offset := timeZone["EST"]
```При попытке получения значения из карты по ключу, которого нет в карте, приведёт к возвращению нулевого значения.
К примеру, если карта содержит целые числа, как описывалось выше, для несуществующего ключа будет возвращено `0`.
Это можно представить как карту у которой в качестве типа значения используется `bool`. Добавление записи в карту это как добавление со значением `true` в карту и дальнейшая простая проверка на индексирование.```golang
attended := map[string]bool{
"Ann": true,
"Joe": true,
...
}if attended[person] { // will be false if person is not in the map
fmt.Println(person, "was at the meeting")
}
```Иногда необходимо отличать отсутствие записи от нулевого значения. К примеру, есть ли запись для `"UTC"` или это пустая строка потому что отсутствует значение в карте?
Для того чтобы отличить - Вы можете использовать множественное присвоение.```golang
var seconds int
var ok bool
seconds, ok = timeZone[tz]
```Очевидная причина называть данную идиому "запятая ок".
В данном примере, если `tz` существует, то `seconds` будет иметь необходимое значение и `ok` будет `true`, но если не существует, то `seconds` будет иметь нулевое значение а `ok` будет `false`.
В следующем примере, представлена функция с хорошим описанием ошибки:```golang
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}
```В случаи, если нас не интересует само значение, а лишь его наличие, то можно использовать **пустой идентификатор `_`**, расположенный вместо значения.
```golang
_ , present := timeZone[tz]
```Для удаления записи из карты, необходимо использовать встроенную функцию `delete`, где в качестве аргументов задаётся карта и ключ для удаления.
Данная операция безопасна, даже если данного ключа уже нет в карте.```golang
delete(timeZone, "PDT") // Now on Standard Time
```[^](#Оглавление)
### Печать(Printing)
[^](#Оглавление)
Форматированная печать в Go подобна стилю в языке C `printf`, но более богаче и более обобщенное. Необходимые функции расположены в пакете `fmt` и имеют названия с большой буквы: `fmt.Printf`, `fmt.Fprintf`, `fmt.Sprintf` и так далее. Функции (`Sprintf` и другие) возвращают строку, а не заполняют предоставленный буфер.
Вам нет необходимости в создании форматировании строк, так как для каждой `Printf`, `Fprintf` and `Sprintf` есть пара функций к примеру `Print` и `Println`.
Данные функции не берут формат строки, а вместо этого устанавливают форматирование по умолчанию для каждого аргумента. Функция `Println` также добавляет пробел между аргументами и добавляет разрыв строки в конце строки. Функция `Print` добавляет пробел только той же строке.
В примере каждая строка производит одинаковый результат.```golang
fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))
```Для форматированной печати функцией `fmt.Fprint` и его друзьями, принимают в качестве первого аргумента объект реализующий интерфейс `io.Writer`.
Значения `os.Stdout` и `os.Stderr` знакомы.Следующее расходится с реализацией на языке С. Первое, числовые форматы `%d` не имеют флагов знаковости или размера; Вместо этого, функции печати используют тип аргумента для задания свойств.
```golang
var x uint64 = 1<<64 - 1
fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))
```печатает
```
18446744073709551615 ffffffffffffffff; -1 -1
```Если вы используете соглашение по умолчанию, то для целых чисел можно использовать обобщенный формат `%v` (для "значений"); и результат будет одинаков как для `Print` так и для `Println`.
Более того, данный формат может напечатать *любое* значение, даже срез, структуру или карту.
Печать карты временной зоны из предыдущего раздела.```golang
fmt.Printf("%v\n", timeZone) // or just fmt.Println(timeZone)
```который печатает следующий результат
```
map[CST:-21600 PST:-28800 EST:-18000 UTC:0 MST:-25200]
```Ключи карт могут быть напечатаны в любом порядке.
При печати структуры, с аннотацией `%+v` производиться печать полей структуры с их именами и для каждого значения с форматом `%#v` печатается значение с полным синтаксисом Go.```golang
type T struct {
a int
b float64
c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
fmt.Printf("%#v\n", timeZone)
```печатает
```
&{7 -2.35 abc def}
&{a:7 b:-2.35 c:abc def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string] int{"CST":-21600, "PST":-28800, "EST":-18000, "UTC":0, "MST":-25200}
```(На заметку: обратите внимание на амперсанды)
Для ссылок на строки подходит `%q`, который принимает значение на `string` или `[]byte`.
Альтернативный формат `%#q` будет использовать обратные кавычки, если это возможно.
(Формат `%q` также допустим для целых чисел и рун, создавая односсылочные константы рун.)
Также, `%x` работает со строками, массивом байт и срезом байт также как с целыми числами, создаёт шестнадцатеричные целые строки, а с пробелом в формате (`% x`) добавляет пробелы между байтами.Другой удобный формат `%T`, который печатает *тип* значения.
```golang
fmt.Printf("%T\n", timeZone)
```печатает
```
map[string] int
```Если Вы хотите свой собственный формат типа, то для этого достаточно метод с сигнатурой `String() string` для Вашего типа.
Для нашего простого примера, тип `T`, выглядит следующим образом.```golang
func (t * T) String() string {
return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}
fmt.Printf("%v\n", t)
```Печатает в следующем формате
```
7/-2.35/"abc\tdef"
```(Если Вам необходимо напечатать *значение* типа `T` как указателя на тип `T`, то метод `String` должен иметь значение типа; этот пример использует указатель, т.к. они более эффективны и идиоматичны типу структуры.)
Наша функция `String` может вызывать `Sprintf`, потому что функция печати возвращаемая и поэтому можно её обернуть. Это важно для понимания данного подхода.
Однако, не создавайте функцию `String` вызывающую метод `Sprintf`, в случаи если далее будет рекурсивно вызвана `String`.
Это может произойти если `Sprintf` вызывает на печать строку получателя, который вызовет функцию снова. Эту ошибку можно легко создать и она показана на следующем примере.```golang
type MyString stringfunc (m MyString) String() string {
return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.
}
```Для того чтобы решить эту проблему, необходимо изменить аргумент на базовый тип, который не имеет функции.
```golang
type MyString string
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.
}
```Другой способ печати это допустить печать функции аргументов напрямую в другую функцию.
Сигнатура `Printf` используется для типов `...interface{}`, что допускает произвольное число аргументов, которые добавляются после формата *format*.```golang
func Printf(format string, v ...interface{}) (n int, err error) {
```**TODO**
Within the function `Printf`, `v` acts like a variable of type `[]interface{}` but if it is passed to another variadic function, it acts like a regular list of arguments.
Here is the implementation of the function `log.Println` we used above. It passes its arguments directly to `fmt.Sprintln` for the actual formatting.
**-**```golang
// Println prints to the standard logger in the manner of fmt.Println.
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...)) // Output takes parameters (int, string)
}
```Запись `...` после `v` при вызове функции `Sprintln` объявляет компилятору о том что `v` является списком аргументов; с другой стороны `v` воспринимается как простой срез аргументов.
Если Вам необходимо большее количество информации, то смотрите документацию `godoc` в пакете `fmt`.
Кстати параметр `...` может иметь тип, для примера`...int` для функции определения минимума используется список целых чисел:
```golang
func Min(a ...int) int {
min := int(^uint(0) >> 1) // largest int
for _ , i := range a {
if i < min {
min = i
}
}
return min
}
```[^](#Оглавление)
### Присоединение(Append)
[^](#Оглавление)
В настоящий момент? пришел момент для разъяснения конструкции встроенной функции `append`. Сигнатура функции `append` отличается от ранее описанной функции `Append`.
Схематично, выглядит следующим образом:```golang
func append(slice []*T*, elements ...*T*) []*T*
```где *T* любой тип. Вы не можете написать в языке Go функцию в которой `T` определена вызывающим. Поэтому необходима поддержка компилятора для функции `append`.
Данная функция `append` добавляет элемент в конец среза и возвращает результат.
Причина возврата результата, в том что как и в рукописной функции `Append` массив может измениться.
Простой пример:```golang
x := []int{1,2,3}
x = append(x, 4, 5, 6)
fmt.Println(x)
```печатает `[1 2 3 4 5 6]`. Итак, `append` работает в принципе как `Printf` с произвольным количеством аргументов.
Но что если необходимо добавить срез в срез, как в нашей реализации `Append`? Все просто: используем `...` который мы использовали в `Output`. Вот пример кода для получение того же результата.
```golang
x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)
```Обращаю внимание, что без `...` компилятор напишет ошибку, так как `y` не имеет тип `int`.
[^](#Оглавление)
## Инициализация(Initialization)
[^](#Оглавление)
Инициализация в языке Go более мощный инструмент нежели в языках С или С++.
Даже сложные структуры можно инициализировать. Упорядочивание между инициализируемыми объектами разных пакетов, обрабатывается корректно.[^](#Оглавление)
### Константы(Constants)
[^](#Оглавление)
Константы в Go это просто константы.
Они создаются во время компиляции даже если она определена в локальной функции и могут быть цифры, символы(руны), строки или булевый тип.
Из-за ограничения времени компиляции, компилятор должен определять какие выражения могут быть константами. К примеру, выражение `1<<3` это константное выражение, в то время как выражение `math.Sin(math.Pi/4)` не является константой, так как вызывает функцию `math.Sin` требующую выполнения по время выполнения.В языке Go, перечисление констант производиться с помощью перечислителя **`iota`**. Так как `iota` может быть неявно повторяемой для выражения или выражений, то легко можно строить сложные наборы значений.
```golang
//{{code "/doc/progs/eff_bytesize.go" `/^type ByteSize/` `/^\)/`}}
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package main
import "fmt"
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
```Использование функции `String` к пользовательским типам производить печать необходимым образом.
**TODO**
Although you'll see it most often applied to structs, this technique is also useful for scalar types such as floating-point types like `ByteSize`.
**-**```golang
//See code "/doc/progs/eff_bytesize.go"func (b ByteSize) String() string {
switch {
case b >= YB:
return fmt.Sprintf("%.2fYB", b/YB)
case b >= ZB:
return fmt.Sprintf("%.2fZB", b/ZB)
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
```Выражение `YB` печатается как `1.00YB`, когда `ByteSize(1e13)` печатает как `9.09TB`.
Используемый здесь `Sprintf` в функции `String` типа `ByteSize` безопасна(не вызывается рекурсивно), не потому что происходит конвертирование, а потому что вызывается функция `Sprintf` с `%f`, который не строковый формат:`Sprintf` будет вызывать функцию `String`, функцию которой необходима строка и `%f` число с плавающей точкой.
[^](#Оглавление)
### Переменные(Variables)
[^](#Оглавление)
Переменные могут инициализироваться как константы, но инициализация производиться во время работы.
```golang
var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
gopath = os.Getenv("GOPATH")
)
```[^](#Оглавление)
### Функция init
[^](#Оглавление)
Каждый исходный код может определить свою первичную функцию `init` для обязательных настройки. (На самом деле файл может иметь несколько функций `init`.)
Функция `init` вызывается после всех объявлений переменных и после всех объявлений переменных всех пакетов.Общее применение функции `init` в проверки или починки состояния программы до начала реального исполнения.
```golang
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath may be overridden by --gopath flag on command line.
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}
```[^](#Оглавление)
## Методы(Methods)
[^](#Оглавление)
### Указатели или Значения
[^](#Оглавление)
Как мы видели в примеры с `ByteSize`, функции может иметь имя типа (кроме указателей или интерфейсов) и приемник не обязательно должен иметь структуры.
Как обсуждалось ранее в срезах, мы написали функцию `Append`.
Мы можем определить функции вместе со срезом. Для этого, мы объявим именованный тип, который мы можем связать с функцией и там самым создать получателя данной функции для значений этого типа.```golang
type ByteSlice []bytefunc (slice ByteSlice) Append(data []byte) []byte {
// Body exactly the same as the Append function defined above.
}
```Данный метод все также возвращает обновленный срез. Для решения этой неуклюжести можно воспользоваться *указателем* на `ByteSize` в получатель, итак можно переписать следующим образом:
```golang
func (p *ByteSlice) Append(data []byte) {
slice := *p
// Body as above, without the return.
*p = slice
}
```На самом деле, мы можем сделать это ещё лучше. Если мы изменим функцию, то она будет выглядеть как стандартная функция `Write`, то есть вот так,
```golang
func (p *ByteSlice) Write(data []byte) (n int, err error) {
slice := *p
// Again as above.
*p = slice
return len(data), nil
}
```тип `*ByteSlice` удовлетворяет стандартному интерфейсу `io.Writer`, что удобно. Например, мы можем напечатать один из них:
```golang
var b ByteSlice
fmt.Fprintf(&b, "This hour has %d days\n", 7)
```Мы передаем адрес `ByteSlice`, поскольку только `*ByteSlice` удовлетворяет интерфейсу `io.Writer`.
Правило получателя *о указателях или значениях*, в том что функции значения могут использоваться для указателей и значений, а функция указателя может только использовать указатель.Это правило возникло потому что функции указателя могут изменять получателя.
Вызывая значение в функции значений получаешь копию значения, поэтому никаких модификаций не произойдет.
Поэтому язык запрещает эту ошибку.
Когда адресуется значение, то язык заботится о подставлении символа адресации автоматически.К примеру, переменная `b` адресованная, поэтому мы можем вызвать функцию `Write` просто вызвав `b.Write`.
Компилятор сам допишет `(&b).Write` за нас.Кстати, идея использования `Write` на срезах байт наиважнейшая для реализации `bytes.Buffer`.
[^](#Оглавление)
## Интерфейсы и другие типы
[^](#Оглавление)
### Интерфейсы
[^](#Оглавление)
Интерфейсы в Go позволяют создать особое поведения для объектов: *Если нечто может делать* **это** *, то это можно использовать* **здесь**. Мы уже это встречали в простых примерах, когда реализовывали функцию `String` для печати, в то время как `Fprintf` может выдавать на печать другое с методом `Write`.
Интерфейсы с одним или двумя функциями свойственны в языке Go, как `io.Writer` реализующий `Write`.Любой тип может реализовывать множество интерфейсов.
К примеру, коллекции могут быть отсортированы с помощью функций из пакета `sort`, если она реализует `sort.Interface`, который состоит из `Len()`, `Less(i, j int) bool`, и `Swap(i, j int)` и это может задать собственный формат.
Рассмотрим пример `Sequence````golang
//{{code "/doc/progs/eff_sequence.go" `/^type/` "$"}}
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package main
import (
"fmt"
"sort"
)func main() {
seq := Sequence{6, 2, -1, 44, 16}
sort.Sort(seq)
fmt.Println(seq)
}type Sequence []int
// Methods required by sort.Interface.
func (s Sequence) Len() int {
return len(s)
}
func (s Sequence) Less(i, j int) bool {
return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}// Method for printing - sorts the elements before printing.
func (s Sequence) String() string {
sort.Sort(s)
str := "["
for i, elem := range s {
if i > 0 {
str += " "
}
str += fmt.Sprint(elem)
}
return str + "]"
}
```[^](#Оглавление)
### Преобразование (Conversions)
[^](#Оглавление)
Функция `String` работает с `Sequence` и `Sprint` уже работает со срезами. Мы может распространить данный эффект, если конвертируем `Sequence` на `[]int` до вызова `Sprint`.
```golang
func (s Sequence) String() string {
sort.Sort(s)
return fmt.Sprint([]int(s))
}
```Это функция другой пример техники конвертирования для вызова `Sprintf` безопасно для функции `String`.
Так как два типа (`Sequence` и `[]int`) одинаковы, то мы можем игнорировать имя типа, это допустимое конвертирование между ними.
При конвертации не происходит создание нового значения, это временная замена существующего значения на новый тип.
(При других допустимых конвертациях, к примеру из целого числа в число с плавающей точкой, происходит создание нового значения.)Это идиоматично в программе Go - конвертация типа позволяет получить доступ к другим функциям. К примеру, мы можем использовать существующий тип `sort.IntSlice`:
```golang
type Sequence []int// Method for printing - sorts the elements before printing
func (s Sequence) String() string {
sort.IntSlice(s).Sort()
return fmt.Sprint([]int(s))
}
```Теперь, наш `Sequence` реализует множество интерфейсов (сортировка и печать), мы можем использовать множество типов (`Sequence`, `sort.IntSlice` и `[]int`), которые выполняют определенную часть работ.
Это не типично в использовании, но эффективно.[^](#Оглавление)
### Конвертация интерфейсов и привязка типов
[^](#Оглавление)
Переключатель типов(Type switches) является одной из форм конвертации: на основе интерфейса и переключателя для каждого элемента, в некотором смысле преобразует тип в элемент переключателя.
Это простой вариант как в коде `fmt.Printf` конвертирует значение в строку, используя переключатель типа.
И если это уже строка, мы хотим чтобы фактическое значение происходило по его интерфейсу, но в случаи если она имеет функцию `String`, то хотим чтобы в результате вызывалась именно она.```golang
type Stringer interface {
String() string
}var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
return str
case Stringer:
return str.String()
}
```В первом случае ищется конкретное значение, во втором случаи происходит преобразование интерфейса в другой интерфейс.
Это хороший подход в преобразовании типов.Что если, мы будем беспокоиться лишь об одном типе? Если мы знаем что значение имеет тип `string` и мы хотим вытащить только его?
Можно сделать переключатель только с одним типом, но это будет *type assertion*.
И *type assertion* берет значение интерфейса и переводит из его значения в его тип.
Заимствование типа из открытия *type switch*, но переводит тип с помощью ключевого слова `type`:```golang
value.(typeName)
```и в результате у нас значение со статическим типом `typeName`.
Этот тип должен быть конкретным типом имеющим интерфейс, или второй тип интерфейса - это тип в который может быть конвертирован.
Если мы знаем что это строка в значении, то мы можем записать:```golang
str := value.(string)
```Но если выясниться, что значение хранит не строку, то программа будет обрушена во время работы в *run-time error*.
Для защиты от этого используется идиома *запятая, ок* *"comma, ok"* для безопасности и проверка является ли значение строкой:```golang
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
```В случаи неудачи, `str` будет всё ещё существовать и будет типом строка, но будет иметь нулевое значение - пустую строку.
Для иллюстрации, используем условие `if`-`else` как эквивалент переключателя типов *type switch* в начале этого раздела.
```golang
if str, ok := value.(string); ok {
return str
} else if str, ok := value.(Stringer); ok {
return str.String()
}
```[^](#Оглавление)
### Общее(Generality)
[^](#Оглавление)
Если тип существует только для реализации интерфейса и никогда не будет экспортироваться за пределы интерфейса, то нет необходимости экспортировать сам тип.
Экспортирование только интерфейса делает более понятным, что значение имеет не так интересно как поведение интерфейса.
Также это позволяет избегать повторения документации для каждого экземпляра общего метода.В таких случаях, конструктор может возвращать значение интерфейса, что лучше чем реализованный тип.
Для примера, в библиотеках хэш *hash* оба конструктора `crc32.NewIEEE` и `adler32.New` возвращают тип интерфейса `hash.Hash32`.
Для подстановки алгоритма CRC-32 для Adler-32 в программе Go требуется только изменить вызов конструктора, а остальная часть кода не зависит от алгоритма.Подобный подход позволяет создать поток шифровальных алгоритмов помимо имеющихся в пакете `crypto`, устанавливаются в цепочку отдельно от блока шифрования.
Интерфейс `Block` в пакете `crypto/cipher` имеющий поведение - шифрование, который обеспечивает шифрование одного блока данных.
Это по аналогии с пакетом `bufio`, пакет шифрования реализует этот интерфейс и может использовать конструктор потока шифрования, представляя интерфейс `Stream` без известных деталей о шифровании.Интерфейсы `crypto/cipher` выглядят следующим образом:
```golang
type Block interface {
BlockSize() int
Encrypt(src, dst []byte)
Decrypt(src, dst []byte)
}type Stream interface {
XORKeyStream(dst, src []byte)
}
```Определение режима счётчика потока *counter mode (CTR) stream*, который превращает блоки шифрования в поток шифрования, обратите внимание, что шифрование блоков абстрагировано:
```golang
// NewCTR returns a Stream that encrypts/decrypts using the given Block in
// counter mode. The length of iv must be the same as the Block's block size.
func NewCTR(block Block, iv []byte) Stream
```Принятое `NewCTR` не только для одного конкретного алгоритма шифрования и исходных данных, но для любой реализации интерфейса `Block` и любой `Stream`.
Так как он возвращает тип интерфейса, замена шифрование CTR с другими режимами шифрования это локальное изменение. Вызов конструктора должен быть отредактирован, и при этом окружающий код не заметит разницы , так как в результате `Stream`.[^](#Оглавление)
### Интерфейсы и методы(функции)
[^](#Оглавление)
Так как метод может иметь почти всё, поэтому все можно удовлетворить интерфейсами.
Один из примеров из пакета `http`, который имеет интерфейс `Handler`. Любой объект реализующий `Handler` может служить для HTTP запросов.```golang
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
```Сам интерфейс `ResponseWriter` обеспечивает функции для возврата ответа клиенту.
Эти функции включают метод `Write`, то `http.ResponseWriter` можно использовать везде как где можно использовать `io.Writer`. `Request(Запрос)` это структура хранящая информацию о запросе от клиента.Для упрощения, давайте игнорировать POSTs и предположим что HTTP запросы всегда используют GETs; Это упрощение не влияет на способ настройки обработчика *handlers*.
К примеру следующий код показывает полный обработчик для подсчета количества раз показа данной страницы.```golang
// Simple counter server.
type Counter struct {
n int
}func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctr.n++
fmt.Fprintf(w, "counter = %d\n", ctr.n)
}
```(Обратите внимание, на то как `Fprintf` печатает в `http.ResponseWriter`.)
Для справки, следующий код показывает как присоединить сервер к узлу в *URL tree*.```golang
import "net/http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)
```Но зачем использовать структуру для `Counter`? Все что нам необходимо - это целое число.
(Для получателя *receiver* необходим указатель, тогда инкремент будет виден для вызывающего)```golang
// Simpler counter server.
type Counter intfunc (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
*ctr++
fmt.Fprintf(w, "counter = %d\n", *ctr)
}
```Что делать если Ваша программа имеет некое внутреннее состояние и необходимо уведомить что страница была посещена? Необходимо связать веб страницы каналом.
```golang
// A channel that sends a notification on each visit.
// (Probably want the channel to be buffered.)
type Chan chan *http.Requestfunc (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ch <- req
fmt.Fprint(w, "notification sent")
}
```Если нам требуется представить на `/args` аргументы использованные для запуска приложения сервера.
Просто необходимо написать функцию для печати аргументов.```golang
func ArgServer() {
fmt.Println(os.Args)
}
```Как превратить это в HTTP сервер? Мы могли бы сделать метод `ArgServer` некоторого типа значение которого мы игнорируем, но есть более простой путь.
Так как мы можем определить метод для любого типа, кроме указателя и интерфейса, то мы можем записать метод для функции.
В пакете `http` есть следующий код:```golang
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(c, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
f(w, req)
}
```Это тип `HandlerFunc` с методом `ServeHTTP`, поэтому значения данного типа может служит для запросов HTTP. Посмотрим на реализацию метода: *receiver* это функция, `f`, и метод называется `f`. Это может показаться странным, но это ничем не отличается от работы с каналами и метод бы отсылал на канал.
Для создания `ArgServer` как HTTP сервера, вначале мы изменим корректную сигнатуру.
```golang
// Argument server.
func ArgServer(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(w, os.Args)
}
```Сейчас, `ArgServer` имеет ту же сигнатуру как `HandlerFunc`, поэтому его можно конвертировать в этот тип для доступа к его методам, просто как сконвертировать `Sequence` в `IntSlice` для доступа к `IntSlice.Sort`.
Код для настройки лаконичен:```golang
http.Handle("/args", http.HandlerFunc(ArgServer))
```Когда кто-то посещает страницу `/args`, обработчик **handler** устанавливает страницу со значением `ArgServer` и типом `HandlerFunc`.
Сервер HTTP будет вызывать метод `ServeHTTP` данного типа с получателем `ArgServer`, который будет вызывать `ArgServer` через вызов `f(c, req)` внутри `HandlerFunc.ServeHTTP`.
Вследствие этого аргументы будут отображены.В этом разделе мы сделали сервер HTTP из структуры, целого числа, канала, и функции, все потому что интерфейсы имеют только набор методов, которые могут быть определены для (почти) любого типа.
[^](#Оглавление)
## Пустой идентификатор (The blank identifier **_**)
[^](#Оглавление)
Мы уже упоминали пустой идентификатор пару раз, в разделах о циклах `for` `range` и картах `maps`.
Пустой идентификатор может быть назначен или объявлен для любого типа, значение при этом отбрасывается.
Это чем то похоже на запись в Unix файл в `/dev/null`: Это значение только на запись, где переменная необходима, но значение не важно.
Есть дополнительные способы использования.[^](#Оглавление)
### Пустой идентификатор в множественном присваивании (**_**)
[^](#Оглавление)
Использование пустого идентификатора в цикле `for` `range` является лишь одним случаем применения в общей картине множественного присваивания.
Если требуется множество значений на левой стороне при присваивании, но одно из значений не будет использоваться программой, то используется пустой идентификатор на левой стороне присвоения для того чтобы избежать необходимости в ненужных переменных и создании понимания что значение отброшенное.
Например, когда вызывается функция возвращающая значение и ошибку, но при этом только ошибка важна, то пустой идентификатор используется для того чтобы отбросить ненужное значение.```golang
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("%s does not exist\n", path)
}
```Иногда Вы увидите код в котором отбрасывается ошибка, это ужасная практика. Всегда проверяйте возвращенную ошибку, так как они предоставляются по некой причине.
```golang
// Bad! This code will crash if path does not exist.
fi, _ := os.Stat(path)
if fi.IsDir() {
fmt.Printf("%s is a directory\n", path)
}
```[^](#Оглавление)
### Неиспользуемое импортирование и значения
[^](#Оглавление)
Ошибкой является неиспользование пакета или объявление переменной без использования.
Неиспользованный импорт увеличивает программу и делает компиляцию медленнее, в то время как переменная инициализированная но не используется, по крайней мере приводит к пустому вычислению или может является индикатором об ошибке.
Однако неиспользуемые импорты и переменные возникают, когда программа на стадии активной разработки и удаление их может раздражать, только лишь для того чтобы прошла компиляция и если они снова понадобятся позже.
Пустые идентификаторы позволяют создать обход(**workaround**).Это полунаписанная программа имеет два неиспользуемых импорта (`fmt` и `io`) и не используемую переменную (`fd`), и она не проходит компиляцию, но было бы хорошо если бы можно было увидеть, что код корректен.
```golang
///{{code "/doc/progs/eff_unused1.go" `/package/` `$`}}
package mainimport (
"fmt"
"io"
"log"
"os"
)func main() {
fd, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
}
```Для того чтобы избежать жалоб о неиспользуемых импортах, необходимо использовать символ пустого идентификатора для обозначения импортирования пакета.
Аналогично, можно поступать с неиспользуемой переменной `fd` при использовании пустого идентификатора, что приведёт к избеганию ошибки о неиспользованной переменной.
Следующая версия программы будет компилироваться.```golang
//{{code "/doc/progs/eff_unused2.go" `/package/` `$`}}
package mainimport (
"fmt"
"io"
"log"
"os"
)var _ = fmt.Printf // For debugging; delete when done.
var _ io.Reader // For debugging; delete when done.func main() {
fd, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
// TODO: use fd.
_ = fd
}
```В соответствии с соглашением, глобальное объявление для замалчивания ошибки импорта должно идти сразу за импортированием и должно быть откомментировано, это сделано для того чтобы легко можно было найти и помнить об отчистки позже.
[^](#Оглавление)
### Импортирование для побочного эффекта (Import for side effect)
[^](#Оглавление)
Неиспользуемые импорты, как например `fmt` и `io` в предыдущем примере, в конечном счете должны быть удалены: пустое задание должно определять что код в процессе разработки.
Но иногда, используется импортирование пакета только для создания побочного влияния, без какого либо явного использования.
К примеру, для функции `init` в пакете [net/http/pprof](https://golang.org/pkg/net/http/pprof/) регистрирует HTTP обработчики для обеспечения отладочной информацией.
Он имеет экспортированный API, но большинству клиентов необходима только регистрация обработчиков и получение доступа к данным через веб-страницу.
Только для импортирования пакета с этим побочным эффектом, переименовывают пакет в пустой идентификатор:```golang
import _ "net/http/pprof"
```Эта форма импортирования означает, что данный пакет импортируется для данного побочного эффекта, потому что нет другой возможности использовать пакет: в этот файл, не имеет имени. (Если же он имеет и мы не используем это имя, то компилятор отменит программу.)
[^](#Оглавление)
### Проверка интерфейса (Interface checks)
[^](#Оглавление)
Как мы видели ранее в разделе об интерфейсах, нет необходимости в объявлении что тип реализует определенный интерфейс.
Вместо этого, тип реализует интерфейс только путем реализации методов интерфейса.
На практике, большинство преобразований интерфейсов статично и поэтому проверяется во время компиляции.К примеру, передавая `*os.File` в функцию ожидающая `io.Reader` не будет скомпилировано, так как `*os.File` не реализует интерфейс `io.Reader`.
Хотя все же некоторые проверки интерфейсов происходят во время выполнения.
Один из примеров в пакете [encoding/json](https://golang.org/pkg/encoding/json/), который определяет интерфейс [Marshaler](https://golang.org/pkg/encoding/json/#Marshaler). Когда **JSON encoder** принимает значение, которое реализует этот интерфейс, *encoder* вызывает функцию упаковщик значений для преобразования в JSON, в отличии от стандартного преобразования.*Encoder* проверяет эти свойства во время работы:
```golang
m, ok := val.(json.Marshaler)
```Если необходимо только запросить тип реализуемого интерфейса без использования самого интерфейса, то это часть проверки ошибок, используйте пустой идентификатор для игнорирования защиты типов:
```golang
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}
```Одна из ситуаций применения это когда необходимо гарантировать в рамках пакета что данный тип реализует интерфейс.
Если взглянуть на пример [json.RawMessage](https://golang.org/pkg/encoding/json/#RawMessage), где необходима пользовательское представление в формате JSON, он должен реализовывать `json.Marshaler`, но отсутствует статическое преобразование для автоматической проверки компилятором.
Если определенный тип не будет реализовывать интерфейс, то *JSON encoder* будет все же работать, но без пользовательской реализации.
Для гарантирования корректной реализации, в пакете можете использовать пустой идентификатор для глобальной декларации:```golang
var _ json.Marshaler = (*RawMessage)(nil)
```в этой деклорации, присвоение с конвертацией `*RawMessage` к `Marshaler` требует чтобы, `*RawMessage` реализовывал `Marshaler` и данная проверка будет производиться во время компиляции.
В случаи если интерфейс `json.Marshaler`, этот пакет не будет компилироваться и мы будем знать об обновлении.Использование пустого идентификатора в данном случае является индикатором о проверки типов, и при этом не создается переменной.
Не используйте этот подход для проверки каждого типа.
В соответствии с соглашением, такое объявление используется только когда отсутствует статическая конвертация уже существующая в коде, и является редким событием.[^](#Оглавление)
## Вложение (Embedding)
[^](#Оглавление)
Язык Go не поддерживает типичное управление типов подклассов, но он имеет возможность "заимствовать" части реализации с помощью типа *вложения* структуры или интерфейса.
Вложение интерфейса необычно простое.
Мы уже упоминали об интерфейсах `io.Reader` and `io.Writer` ранее, вот их определение.```golang
type Reader interface {
Read(p []byte) (n int, err error)
}type Writer interface {
Write(p []byte) (n int, err error)
}
```Пакет `io` также экспортирует несколько других интерфейсов, которые определяют объекты, которые могут реализовывать несколько таких методов.
К примеру, `io.ReadWriter` содержит оба интерфейса `Read` и `Write`.
Мы может указать `io.ReadWriter` перечислением двух методов в явном виде, но проще и более запомяющим будет встраивание двух интерфейсов в одну новую форму, вот так:```golang
// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
Reader
Writer
}
```Это выглядит следующим образом: `ReadWriter` может делать все что делает `Reader` **и** что делает `Writer`. Это объединение встраивания интерфейсов (которые не имеют пересечений в методах).
Только интерфейсы могут встраивать интерфейсы.Аналогичная идея используется для структур, но с большим количеством последствий. Пакет `bufio` имеет две структуры типов - `bufio.Reader` и `bufio.Writer`, каждая из которых реализует аналогичные интерфейсы как в пакете `io`. И `bufio` также реализует буферизованное чтение/запись, которое объединяет чтение и запись в одну структуру с использованием вложения: этот список типов структур, но не давая имена полям.
```golang
// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
*Reader // *bufio.Reader
*Writer // *bufio.Writer
}
```Вложение указателей элементов в структуры и конечно должно быть инициализировано необходимой структурой до его использования.
Структура `ReadWriter` может быть записана так:```golang
type ReadWriter struct {
reader *Reader
writer *Writer
}
```**TODO**
but then to promote the methods of the fields and to
satisfy the `io` interfaces, we would also need
to provide forwarding methods, like this:
**-**```golang
func (rw *ReadWriter) Read(p []byte) (n int, err error) {
return rw.reader.Read(p)
}
```Для непосредственного вложения структур, мы должны избегать эту бухгалтерию.
Метод вложенного типа приходит свободно, что означает что `bufio.ReadWriter` имеет не только его методы `bufio.Reader` и `bufio.Writer`, а также удовлетворяет всем трем интерфейсам:
* `io.Reader`,
* `io.Writer`, и
* `io.ReadWriter`.Это важное отличие вложения от подклассов. Когда мы вкладываем тип, методы этого типа становятся методами внешнего типа, но для получателя они вызываются как встроенные типы, а не внешние.
В нашем примере, когда метод `Read` из `bufio.ReadWriter` вызывается, он и вызываются также как описано выше; получатель поля `reader` из `ReadWriter`, является самим `ReadWriter`.Вложение может быть простым и удобным.
Этот пример показывает вложение поля рядом с именованным полем.```golang
type Job struct {
Command string
*log.Logger
}
```Тип `Job` сейчас имеет `Log`, `Logf` и другие методы `*log.Logger`.
Мы могли бы дать имя для `Logger`, конечно же, но в этом нет необходимости. И сейчас, мы можем логировать `Job`:```golang
job.Log("starting now...")
```Регулярное поле `Logger` в структуре `Job`, поэтому мы можем инициализировать его как обычно внутри конструктора `Job`, вот так:
```golang
func NewJob(command string, logger *log.Logger) *Job {
return &Job{command, logger}
}
```или с помощью составных литералов:
```golang
job := &Job{command, log.New(os.Stderr, "Job: ", log.Ldate)}
```Если нам необходимо обратиться непосредственно к вложенному полю, имени типа поля, игнорируя пакетный классификатор, как к имени поля, как это сделано в методе `Read` в нашей структуре `ReaderWriter`.
При этом нам необходим доступ к ` * log.Logger` в `Job` переменной `job`, мы можем написать `job.Logger`, что полезно если мы хотим уточнить методы `Logger`.```golang
func (job *Job) Logf(format string, args ...interface{}) {
job.Logger.Logf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}
```Вложение типов создает проблему конфликта имен, но правила для их решения просты.
Первое, поля или метод `X` скрывает любой иной элемент `X` в более глубокой части вложенного типа.
Если `log.Logger` содержит поле или метод под названием `Command`, то поле `Command` в `Job` будет преобладать над ним.Во-вторых, если есть одинаковые имена на том же уровне вложенности, это как правило ошибка и было бы ошибочно вставлять `log.Logger`, если структура `Job` имеет другое вложенное поле или метод с названием `Logger`.
Однако, если дублированные имена никогда не встречается в программе вне определённого типа, то это нормально.
Это защищает от изменения типов вложенности за его пределами; и это не проблема, если добавлено поле вступающее в конфликт с другим полем в другом подтипе, если ни одно из полей не используется.[^](#Оглавление)
## Согласованность, параллельная обработка, параллельное выполнение (Concurrency)
[^](#Оглавление)
### Распределение памяти по сообщениям (Share by communicating)
[^](#Оглавление)
Параллельное программирование является большой темой и здесь будет рассматриваться только специфичное для языка Go.
Параллельное программирование во многих средах затруднено для корректной реализации доступа к общим переменным.
В языке Go поддерживается другой подход, в котором общие переменные *shared values* передаются через каналы, по сути, никогда активно не распределяется по исполняемым потокам.
Только одна го-рутина(**goroutine**) имеет доступ к переменной в любой момент.
Перенос данных не происходит по конструкции языка.
Для того чтобы способствовать данному стилю мышления используется лозунг:> Do not communicate by sharing memory; instead, share memory by communicating.
> Не общайтесь с распределением памяти; Вместо того чтобы распределять память по коммуникациям.
Это дальновидный подход. К примеру, наилучшим образом подсчет ссылок можно производить установкой мютексов(**mutex**) вокруг целого переменной.
Но это высокоуровневый подход, использование каналов для контроля доступа является более простым и корректным для программ.Один из способов думать об этой модели как для типичных однопоточных программ запущенных на одном процессоре CPU. И нет необходимости в синхронизации примитивов.
Для запуска следующего экземпляра, нет необходимости в синхронизации. Сейчас рассмотрим два способа коммуникации; Если коммуникация синхронна, то все также не требуется дополнительной синхронизации. К примеру, *Unix pipelines* великолепно используют эту модель. Хотя подход языка Go для организации параллельных процессов берет начало в **Hoare's Communicating Sequential Processes (CSP)**, он также может рассматриваться как обобщение безопасности типов Unix pipes.[^](#Оглавление)
### Го-рутины (Goroutines)
[^](#Оглавление)
Они называются Го-рутины, потому что существующие термины потоки, корутины, процессы и так далее передают неточную коннотацию.
**Го-рутины** имеют простую модель: это функция выполняющаяся параллельно с другими го-рутинами в одном адресном пространстве. Они легковесны стоящие чуть больше чем выделение пространства в стэке. Они дешевы, и растут по мере необходимости путем выделения или освобождения в куче.Горутины распределяются на несколько потоков OS, и если один заблокируются, например из-за ожидания I/O, другие продолжат работу. Их дизайн скрывает много сложностей по создание потоков и их управлению.
Префикс `go` у функции или метода запускает новую горутину.
Когда вызов закончен, горутина выходит, молча. (Этот эффект похож на команду Unix с нотацией `&` означающая запуск команды в фоновом режиме.)```golang
go list.Sort() // run list.Sort concurrently; don't wait for it.
```Встроенные функции могут быть удобны для вызова горутин.
```golang
func Announce(message string, delay time.Duration) {
go func() {
time.Sleep(delay)
fmt.Println(message)
}() // Note the parentheses - must call the function.
}
```В языке Go, встроенные функции закрываемые и их реализация гарантирует что ссылаемые переменные будут жить до тех пор пока функция активна.
Эти примеры не очень практичны, так как функции не имеют сигнализировать о своем завершении. Для этого у нас есть каналы.
[^](#Оглавление)
### Каналы (Channels)
[^](#Оглавление)
Каналы, как и карты(map) выделяются в памяти с помощью `make` и полученное значение является ссылкой на изначальную структуру данных.
Если задан необязательный целый параметр, то он указывает на размер буфера в канале.
По умолчанию, значение нулевое, как для небуферезованного или синхронного канала.```golang
ci := make(chan int) // unbuffered channel of integers
cj := make(chan int, 0) // unbuffered channel of integers
cs := make(chan *os.File, 100) // buffered channel of pointers to Files
```Небуферезованные каналы гарантируют, что обмен значениями будет синхронным между двумя горутинами в известном состоянии.
Есть много хороших идиом использования каналов. Вот один с которого мы начнем.
В предыдущем разделе мы запускали сортировку в фоне. Канал может помочь отследить завершение горутины с сортировкой.```golang
c := make(chan int) // Allocate a channel.
// Start the sort in a goroutine; when it completes, signal on the channel.
go func() {
list.Sort()
c <- 1 // Send a signal; value does not matter.
}()
doSomethingForAWhile()
<-c // Wait for sort to finish; discard sent value.
```Получатель всегда блокируется до тех пор пока данные не получит получатель.
Если канал не буферизованный, отсылающий блокируется до тех пор пока получатель не получит данные.
Если канал буферизованный, то отсылающий блокируется только тогда когда значение копируется в буфер; если буфер полон, то будет ожидать до тех пор пока получатель не получит значение.**TODO**
A buffered channel can be used like a semaphore, for instance to
limit throughput. In this example, incoming requests are passed
to `handle`, which sends a value into the channel, processes
the request, and then receives a value from the channel
to ready the "semaphore" for the next consumer.
The capacity of the channel buffer limits the number of
simultaneous calls to `process`.
**-**```golang
var sem = make(chan int, MaxOutstanding)func handle(r *Request) {
sem <- 1 // Wait for active queue to drain.
process(r) // May take a long time.
<-sem // Done; enable next request to run.
}func Serve(queue chan *Request) {
for {
req := <-queue
go handle(req) // Don't wait for handle to finish.
}
}
```**TODO**
Once `MaxOutstanding` handlers are executing `process`,
any more will block trying to send into the filled channel buffer,
until one of the existing handlers finishes and receives from the buffer.
**-**Данный дизайн имеет проблемы: `Serve` создает новую горутину для каждого входящего запроса, при этом будет запущено не более `MaxOutstanding` в один момент.
Если количество запросов увеличивается слишком быстро, то как результат, программа может потребовать бесконечное количество ресурсов.
Мы можем решить это изменением `Serve` используя изменения количества порождаемых горутин.
Вот очевидное решение, но будьте осторожны, так как оно имеет ошибку, которую позже исправим:```golang
func Serve(queue chan *Request) {
for req := range queue {
sem <- 1
go func() {
process(req) // Buggy; see explanation below.
<-sem
}()
}
}
```Ошибка в том, что в языке Go цикл `for`, цикл переменной повторно используется для каждой итерации, так что переменные `req` разделяется по всем горутинам.
Это не то что мы хотим.
Нам нужно убедиться, что `req` является уникальной для каждой горутиной.
Вот один из способов, передавать значение `req` как в качестве аргумента для закрытии горутины:```golang
func Serve(queue chan *Request) {
for req := range queue {
sem <- 1
go func(req *Request) {
process(req)
<-sem
}(req)
}
}
```Сравнивая эту версию с предыдущей можно увидеть разницу в том как объявляется запуск и закрытие.
Другое решение заключается в том что создается новая переменная с тем же именем, как в примере:```golang
func Serve(queue chan *Request) {
for req := range queue {
req := req // Create new instance of req for the goroutine.
sem <- 1
go func() {
process(req)
<-sem
}()
}
}
```Может кажется странным, писать:
```golang
req := req
```Но это допустимо и идиоматично делать это.
Вы получаете новую переменную с тем же именем, намеренно затеняя переменную цикла локально, но уникальный для каждой горутины.Возвращаясь к общей проблеме написания сервера, иной подход для управления ресурсами начинается с фиксации числа обработчиков `handle` горутин читающих из канала запросов.
Ограничение количества горутин количеством одновременных вызовов к `process`.Функция `Serve` также принимает канал, на который посылается об окончании; после запуска горутины блокируют получающих в этот канал.
```golang
func handle(queue chan *Request) {
for r := range queue {
process(r)
}
}func Serve(clientRequests chan *Request, quit chan bool) {
// Start handlers
for i := 0; i < MaxOutstanding; i++ {
go handle(clientRequests)
}
<-quit // Wait to be told to exit.
}
```[^](#Оглавление)
### Канал каналов (Channels of channels)
[^](#Оглавление)
Одно из важных свойств Go в том что каналы это переменная, а значит аллоцированы и могут передаваться как любой другой элемент. Одно из использований данной свойства в реализации безопасного и **параллельного демультиплексирования**.
В примере из предыдущего раздела, `handle` был идеальным обработчиком для запросов, но он не определял тип обработки. Если тип включен в канал, на который отвечать, то каждый клиент может предоставить собственный путь для ответа. Вот схематичное определение типа `Request`.
```golang
type Request struct {
args []int
f func([]int) int
resultChan chan int
}
```Клиент предоставляет функцию и ее аргументы, а также канал внутри объекта запроса, не который будет получен ответ.
```golang
func sum(a []int) (s int) {
for _, v := range a {
s += v
}
return
}request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
// Send request
clientRequests <- request
// Wait for response.
fmt.Printf("answer: %d\n", <-request.resultChan)
```На стороне сервера, функция обработчик это единственное что меняется.
```golang
func handle(queue chan *Request) {
for req := range queue {
req.resultChan <- req.f(req.args)
}
}
```Этот пример является примером основой для ограничения скорости, параллелизма, неблокирующей RPC системы и без использования мютекса.
[^](#Оглавление)
### Параллелизм (Parallelization)
[^](#Оглавление)
Другой пример использования этих идей в расчёте на нескольких ядрах CPU. Если расчет можно разбить на кусочки выполняющиеся независимо, то это можно распараллелить с каналами сигнализирующие, когда отдельный кусочек закончил свою работу.
К примеру, у нас есть дорогая операция выполнения на векторе элементов и эти операции могут выполнять независимо, то вот идеализированный пример.
```golang
type Vector []float64// Apply the operation to v[i], v[i+1] ... up to v[n-1].
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
for ; i < n; i++ {
v[i] += u.Op(v[i])
}
c <- 1 // signal that this piece is done
}
```Вы выполняем кусочки независимо в цикле, по одному CPU на кусочек.
Они могут закончить в любом порядке, но это не важно; мы только считаем количество сигналов окончания по каналу после запуска всех горутин.```golang
const numCPU = 4 // number of CPU coresfunc (v Vector) DoAll(u Vector) {
c := make(chan int, numCPU) // Buffering optional but sensible.
for i := 0; i < numCPU; i++ {
go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c)
}
// Drain the channel.
for i := 0; i < numCPU; i++ {
<-c // wait for one task to complete
}
// All done.
}
```Вместо того, чтобы создать постоянное значение для numCPU, мы можем задать во время выполнения необходимое значение.
Функция [runtime.NumCPU](https://golang.org/pkg/runtime/#NumCPU) возвращает количество ядер CPU в машине, тогда мы должны записать:```golang
var numCPU = runtime.NumCPU()
```Есть также такая функция [runtime.GOMAXPROCS](https://golang.org/pkg/runtime/#GOMAXPROCS), которая возвращает заданное пользователем количество ядер, которая программа Go может использовать.
По умолчанию значение `runtime.NumCPU`, но может быть переопределен путем установки в среде с тем же именем или вызовом функции с положительным числом.
Вызов с нулевым значением запрашивает значение.
Поэтому если мы хотим выполнить запрос ресурсов пользователя, мы должны написать```golang
var numCPU = runtime.GOMAXPROCS(0)
```Будьте уверены, чтобы не путать идеи параллельно-структурированной(**concurrency—structuring**) программы как независимо исполняемых компонентов и параллельно-выполняемые вычисления(**parallelism—executing**) для эффективности на нескольких процессорах.
Хотя особенности *concurrency* в языке Go могут решить некоторые проблемы легко с использованием структур параллельного вычисления, Go является *concurrent* языком, не параллельным и не все проблемы параллелизма подходят модели Go.
Для обсуждения различий, смотрите [следующий блог](https://blog.golang.org/concurrency-is-not-parallelism).[^](#Оглавление)
### Текущий буфер (A leaky buffer)
[^](#Оглавление)
Инструменты конкарентси программирования позволяют для неконкаренси идей быть нагляднее. Вот пример из пакета RPC. Цикл клиента горутины принимает данные из нескольких источников, возможно из сети. Для того чтобы избежать выделения и освобождения буферов, он пустой список и использует буферизованный канал для его представления. Если канал пуст, то выделяется новый буфер. После того, как буфер готов, он высылает на сервер на `serverChan`.
```golang
var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)func client() {
for {
var b *Buffer
// Grab a buffer if available; allocate if not.
select {
case b = <-freeList:
// Got one; nothing more to do.
default:
// None free, so allocate a new one.
b = new(Buffer)
}
load(b) // Read next message from the net.
serverChan <- b // Send to server.
}
}
```Цикл сервера принимает каждое сообщение из клиента, обрабатывает его и возвращает буфер на пустое список.
```golang
func server() {
for {
b := <-serverChan // Wait for work.
process(b)
// Reuse buffer if there's room.
select {
case freeList <- b:
// Buffer on free list; nothing more to do.
default:
// Free list full, just carry on.
}
}
}
```Клиент пытается получить буфер из `freeList`; если ни один не доступен, он выделяется новые.
Посылка от сервера в `freeList` подставляется назад `b` в свободный список, если список не полон, и в этом случаи буфер сбрасывается, чтобы утилизироваться сборщиком мусора.(Положение `default` в `select` выполняется когда другие условия не готовы, это означает что `selects` никогда не блокируется.)
Эта реализация устроена как утекающее ведро со свободным списком всего в несколько строк, опираясь на буферизованный канал и сборщик мусора.[^](#Оглавление)
## Ошибки (Errors)
[^](#Оглавление)
Библиотеки подпрограмм часто должны возвращать какой-то признак ошибки для вызывающего.
Как уже упоминалось ранее, множественные значения в Go могут легко возвращать подробное описание ошибки вместе с нормальным возвращением значения.
Использование данной особенности Go для возвращения детального описания ошибки является хорошим стилем.
Например, как вы увидите `os.Open` при неудаче не просто возвращает указатель на `nil`, он также возвращает значение ошибки, описывающей что пошло не так.В соответствии с соглашением, ошибки имеют тип `error`, простой встроенный интерфейс.
```golang
type error interface {
Error() string
}
```Библиотека записи может реализовать данный интерфейс с богатой моделью покрытия, что позволяет не только увидеть ошибку, но и также обеспечить некий контекст.
Как уже отмечалось, наряду с обычным `*os.File` возвращением значения, `os.Open` также возвращает значение ошибки.
Если файл будет успешно открыт то значение ошибки будет `nil`, но когда есть проблема, то будет передана `os.PathError`:```golang
// PathError records an error and the operation and
// file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Err error // Returned by the system call.
}func (e * PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
```Ошибка `Error` в `PathError` сгенерирует строку как эта:
```command
open /etc/passwx: no such file or directory
```Такая ошибка, которая включает имя проблемного файла, операции, ошибка операционной системы и т.д., полезная, даже если напечатать далеко от вызова; это гораздо полезнее, что просто запись "файл или папка не найдены".
Если это возможно, то строка ошибки должна определять происхождение, например, при наличии префикса имен операции или пакета, который вызвал ошибку.
Например, в пакете `image` при ошибки декодирования представлена от неизвестного формата: "image: unknown format".Вызывающие, которые заботятся о точности ошибки, могут использовать переключатель типов *type switch* или *type assertion* для того специфицирования ошибок и получения большего количества деталей. Для `PathErrors` это означает включения изучения внутренних полей `Err` для восстановления причины отказа.
```golang
for try := 0; try < 2; try++ {
file, err = os.Create(filename)
if err == nil {
return
}
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
deleteTempFiles() // Recover some space.
continue
}
return
}
```Здесь вторая проверка `if` это ещё другой тип *type assertion*.
Если это не удается, то `ok` будет *false* и значение `e` будет `nil`.
Если это удается, то `ok` будет *true*, который означает, что имеет тип `*os.PathError`, и затем когда `e`, который мы можем рассматривать для более подробной информации об ошибке.[^](#Оглавление)
### Паника (Panic)
[^](#Оглавление)
Обычный способ сообщить об ошибке к абоненту, это вернуть `error`, в качестве дополнительного возвращаемого значения. Канонический метод `Read` является хорошим примером, который возвращает количество байт и `error`. Но что если ошибка невосстановимая? Иногда программа просто не может продолжать работать.
Для этого есть встроенная функция `panic`, которая создаёт ошибку во время выполнения программы, которая остановит программу (но смотрите следующий раздел).
Функция принимает один аргумент произвольного типа, часто используется строка для вывода на печать, так как программа умирает. Это также путь указать, что произошло что-то невозможное, как например выход из бесконечного цикла.```golang
// A toy implementation of cube root using Newton's method.
func CubeRoot(x float64) float64 {
z := x/3 // Arbitrary initial value
for i := 0; i < 1e6; i++ {
prevz := z
z -= (z*z*z-x) / (3*z*z)
if veryClose(z, prevz) {
return z
}
}
// A million iterations has not converged; something is wrong.
panic(fmt.Sprintf("CubeRoot(%g) did not converge", x))
}
```Это всего лишь пример и в реальных библиотечных функциях следует избегать `panic`. Если проблема может быть замаскирована или работать по другому алгоритму, то это всегда лучше, чтобы программа продолжала работать, а не выключать её. Один из возможных примеров: если библиотека действительно не может это сделать, то это причина паниковать.
```golang
var user = os.Getenv("USER")func init() {
if user == "" {
panic("no value for $USER")
}
}
```[^](#Оглавление)
### Восстановление (Recover)
[^](#Оглавление)
Когда вызывается `panic`, в том числе не явно при наличии ошибок во время выполнения программы, к примеру когда происходит обращение к срезу за его пределами или при некорректной работы с типами, происходит немедленное прекращение работы функции и начинается раскручивание стека горутин, запуск всех отсроченных функций *defer*.
Если раскручивание достигает вершины стека, то программа умирает. Тем не менее, можно использовать встроенную функцию `recover`, чтобы восстановить контроль над горутинами и возобновить нормальное выполнение.Вызов `recover` останавливает раскручивание и возвращает аргументы в `panic`. Поскольку только код, который работает во время раскручивания внутри отложенных функций, `recover` полезно устанавливать внутри отложенных функций.
Одно `recover` приложение выключает недопустимые горутины изнутри, то сервер без выключения других запущенных горутин.
```golang
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
```В этом примере, если будет вызвана паника в `do(work)`, то результат будет залогирован и горутина закончит работу без препятствия выполнения для других. Там нет необходимости делать что то дополнительно при отсроченном выполнении; вызывание `recover` обрабатывает состояние полностью.
Так как `recover` всегда возвращает `nil`, если вызывалась из отложенной функции, отложенный код может вызывать библиотеку функций, которые сами используют `panic` и `recover` без сбоя.
К примеру, отложенная функция в `safelyDo` может вызвать функцию логирования до вызова `recover`, и этот код логирования будет работать не зависимо от состоянии паники.С помощью данного шаблона восстановления , функция `do` (и все что он вызывает) может выйти из любой ситуации вызовом `panic`.
Мы можем использовать данную идею для простой обработки ошибок в сложной программе. Давайте взглянем на идеализированную версию пакета `regexp`, которая сообщает об ошибке с помощью `panic` с типом локальной ошибки. Это определение `Error`, в методе `error` и функции `Compile`.```golang
// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
return string(e)
}// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
panic(Error(err))
}// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
regexp = new(Regexp)
// doParse will panic if there is a parse error.
defer func() {
if e := recover(); e != nil {
regexp = nil // Clear return value.
err = e.(Error) // Will re-panic if not a parse error.
}
}()
return regexp.doParse(str), nil
}
```Если происходит паника в `doParse`, то блок восстановления будет устанавливать значение `nil` отложенная функция может модифицировать имя возвращаемых значений.
Затем он проверяет, значение `err`, синтаксическая ошибка имеет локальный тип `Error`. Если этого не произойдет, то это приведет к ошибке во время выполнения и будет раскручивать стек.
Эта проверка означает что если происходит что-то неожиданное, как выход за пределы индексирования, код будет прерван даже при использовании `panic` и `recover` для обработки ошибок.При наличии обработчика ошибок, метод `error` (потому его метод связан с типом, это хорошо, так как он имеет то же имя что встроенный тип `error`) позволяет легко сообщить о наличии синтаксической ошибки, не беспокоясь о разматывания стек вручную:
```golang
if pos == 0 {
re.error("'*' illegal at start of expression")
}
```Данный шаблон полезный в рамках только одного пакета. Превращение `Parse` внутреннего вызова `panic` в значение `error`, что позволяет на выставлять `panics` для клиента. Это хорошее правило, чтобы ему следовать.
Данный подход, меняет идиому паник на значение паники если произошла ошибка.
Тем не менее, как оригинальная, так и новые сбои будут представлены в отчёте сбоев, поэтому основная причина этой проблемы не будет видна.
Если Вы хотите увидеть только оригинальные значения, Вам необходимо немного больше кода для фильтрации неожиданных проблем и повторно паниковать с оригинальной ошибкой.[^](#Оглавление)
## Веб-сервер
[^](#Оглавление)
Давайте закончим разработкой веб-сервера на Go.
Google предоставлен сервис по адресу [http://chart.apis.google.com](http://chart.apis.google.com) с автоматическим форматированием данных графиков и диаграмм.
Это трудно использовать в интерактивном режиме, но Вам необходимо добавить URL в качестве запроса.
Здесь программа использует приятный простой интерфейс с одной формой для данных: для небольшого кусочка текста, который вызывает сервер диаграмм для создания QR кода, кодируя текст в матрицу пиксел.
Эта картинка можно быть сфотографирована с помощью камеры телефона и интерпретирована, к примеру, как URL, экономя тем самым его набор на маленькой клавиатуре телефона.Вот программа полностью с последующими пояснениями.
```golang
//{{code "/doc/progs/eff_qr.go" `/package/` `$`}}
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package main
import (
"flag"
"html/template"
"log"
"net/http"
)var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18
var templ = template.Must(template.New("qr").Parse(templateStr))
func main() {
flag.Parse()
http.Handle("/", http.HandlerFunc(QR))
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe:", err)
}
}func QR(w http.ResponseWriter, req *http.Request) {
templ.Execute(w, req.FormValue("s"))
}const templateStr = `
QR Link Generator
{{if .}}
{{.}}
{{end}}`
```Легко понять, что происходит в `main`.
Один флаг устанавливает HTTP сервер по умолчания для нашего сервера.
В значении шаблона `templ`, происходит самое интересное. Он конструирует шаблон HTML, который будет выполнен сервером для показа страницы. Давайте опишем, что происходит в этот момент.Функция `main` разбирает флаги и использует механизм о котором мы говорили выше, связывает функцию `QR` для корневого пути для сервера.
Когда вызывается `http.ListenAndServe` для старта сервера, он блокируется пока сервер запущен.Функция `QR` только получает запрос, который содержит дынные формы, и выполняет шаблон на данных в форме с именем переменной `s`.
Пакет шаблонов `html/template` мощный; данная программа лишь слегка затрагивает его возможности.
По сути, он переписывает часть текста HTML на лету, заменяя элементы на элементы данных, передаваемые в `templ.Execute`, в данном случаи переменной формы.
В тексте шаблона (`templateStr`), имеются *двойные скобки разделители* обозначающие действия шаблона.
Участок от `{{html "{{if .}}"}}` до `{{html "{{end}}"}}` выполняются только если значения текущей элемента данных, вызывают `.` (точка) не пустая. То есть, если строка пуста, то данный участок шаблона игнорируется.Два примере кода `{{html "{{.}}"}}` предназначены для показа существующих данных в запросе шаблона на веб странице.
Пакет шаблонов HTML автоматически обеспечивает соответствие, поэтому текст является безопасным для отображения.Остальные строки шаблона, просто строки HTML , которые показываются при загрузки страницы.
Если это слишком быстрое объяснение, то смотрите [документацию](https://golang.org/pkg/html/template/) о пакете шаблонов для большего понимания.В результате у Вас есть: полезный пример веб сервера из нескольких строк кода с управлением данных текста HTML.
Язык Go достаточно мощный для создание много чего интересного за несколько строк.[^](#Оглавление)
------
**Список дополнительных материалов:**
* [Руководство сотрудничества](https://github.com/Konstantin8105/Contribution_Guide_RU)
* [Эффективный Go](https://github.com/Konstantin8105/Effective_Go_RU)------