Skip to content

Go in Practice: Includes 70 Techniques - Matt Butcher, Matt Farina

Refs:

CHAPTER 1 Getting into Go

CHAPTER 2 A solid foundation

2.1 Working with CLI applications, the Go way

  • Оконные приложения
    Go, как системный язык, не имеет встроенной поддержки для создания оконных приложений, подобных тем, что используются в Microsoft Windows или Mac OS X. В Go-сообществе велись эксперименты по их разработке, но они не имели четкой направленности.

2.1.1 Command-line flags

TIP: Для флагов каждого типа существует своя функция. Ознакомиться с ними можно в описании пакета flag (http://golang.org/pkg/flag).

TIP: Аргументы командной строки не регистрируются пакетом flag, но к ним можно получить доступ с помощью функций Args или Arg из пакета flag.

2.1.2 Command-line frameworks

TIP: Если не определить значение свойства Name, фреймворк cli.go присвоит ему имя приложения. Такое поведение по умолчанию может пригодиться, если позднее приложение может быть переименовано.

TIP: В теле функции Action также имеется доступ к аргументам, список которых можно получить вызовом c.Args. Например, первый аргумент доступен как c.Args()[0].

2.2 Handling configuration

  • Ansible, Chef и Puppet
    Ansible, Chef и Puppet – популярные инструменты управления компьютерами и их конфигурациями. Они обычно используются для управления программным обеспечением кластеров и настройками работающих в них приложений. Например, возьмем приложение, выполняющее подключение к базе данных. База данных может находиться на том же сервере, на другом сервере или в кластере серверов. С помощью инструментов, перечисленных выше, можно управлять установкой и настройками этого приложения, определяя правильные параметры подключения к базе данных.

  • etcd
    Подобно Apache ZooKeeper и Doozer, etcd реализует распределенное хранилище конфигураций для совместного использования и обнаружения служб. Это высокодоступное хранилище использует алгоритм консенсуса Рафта (Raft) для управления репликациями и написано на языке Go. Более подробную информацию о нем можно найти на странице: https://github.com/coreos/etcd

  • Twelve-factor apps
    Это популярная и широко используемая методика создания веб-приложений и служб. Подобные приложения создаются с учетом следующих 12 факторов.

Twelve-factor apps

  1. Использование единой базы кода с контролем версий, которая может развертываться многократно.
  2. Явное объявление зависимостей и изолированность от других приложений.
  3. Хранение конфигурации приложения в переменных окружения.
  4. Подключение поддерживающих служб.
  5. Разделение этапов сборки и запуска.
  6. Выполнение приложения как одного или нескольких процессов без состояния.
  7. Экспорт служб через TCP-порт.
  8. Горизонтальное масштабирование путем добавления процессов.
  9. Повышение живучести приложений за счет быстрого запуска и штатного завершения.
  10. Версии для разработки, тестирования и эксплуатации должны быть как можно более схожими.
  11. Управление журналированием как потоками событий.
  12. Запуск административных задач в отдельных процессах.

Более подробные сведения об этих факторах можно найти на странице http://12factor.net

2.3 Working with real-world web servers

2.3.1 Starting up and shutting down a server

Команды для запуска и остановки сервера в операционной системе должны выполняться демоном инициализации. Команду go run удобно использовать во время разработки, и ее можно задействовать в некоторых системах, опирающихся на двенадцать факторов, но это не стандартный и не рекомендуемый способ. Запуск приложений вручную прост, но он не предназначен для интеграции с функциональными инструментами и не может применяться для решения таких проблем, как неожиданная перезагрузка. Для таких ситуаций специально были созданы *1 (демоны инициализации*), и они хорошо с ними справляются.

Большинство систем по умолчанию имеет набор инструментов для инициализации. Например, systemd (https://freedesktop.org/wiki/Software/systemd/) часто встречается в дистрибутивах Linux, таких, как Debian и Ubuntu. В сценариях для systemd можно использовать следующие команды.

systemctl start myapp.service
systemctl stop myapp.service

NOTE: Мы не рекомендуем писать приложения, выполняющиеся как демоны. Используйте лучше демоны инициализации для управления запуском/остановкой вашего приложения.

A COMMON ANTIPATTERN: A CALLBACK URL

Существует простой шаблон (точнее, антишаблон), часто используемый на этапе разработки, реализующий остановку сервера при переходе по определенному URL-адресу, например /kill или /shutdown.

Мы не рекомендуем этот метод и приводим только потому, что его легко найти в Интернете. Он прост, что определенно является его преимуществом, но имеет целый ряд недостатков, включая следующие:

  • при передаче в промышленную эксплуатацию URL-адрес должен быть заблокирован или, что предпочтительнее, удален. Необходимость иметь разный код для разработки и эксплуатации чревата появлением ошибок. Если оставить этот URL-адрес в коде для промышленной эксплуатации кто угодно легко сможет отключить службу;
  • при переходе по URL-адресу обработчик запроса немедленно завершает работу сервера. При этом любые действия будут сразу же немедленно остановлены. Любые несохраненные данные будут потеряны, поскольку отсутствует возможность их сохранения перед выходом;
  • использование URL-адреса происходит в обход обычных функциональных инструментов, таких как Ansible, Chef и Puppet, или других инструментов инициализации. Следует отдавать предпочтение более подходящим инструментам, управляющим обновлениями и запущенными приложениями.

Мы не рекомендуем использовать этот метод. Он удобен при разработке, когда процесс выполняется в фоновом режиме или действует как демон. Go-приложения, как правило, не должны быть демонами, и существуют более совершенные методы запуска и остановки сервера, даже в процессе разработки.

TIP: Перед завершением работы сервер ждет только завершения обработки запросов. Если приложение запускает сопрограммы и должно дождаться их завершения перед выходом, вам придется реализовать свою версию WaitGroup.

2.3.2 Routing web requests

TIP: В пакете net/url, содержащем тип URL, имеется множество функций для работы с URL-адресами.

NOTE: Определить использованный HTTP-метод можно с помощью свойства http.Request.Method. Оно содержит имя метода (например, GET, POST и так далее).

TIP: В пакете http имеется функция Error(), которую можно использовать для установки кода любой ошибки HTTP и возврата соответствующего сообщения. Функция NotFound() предназначена только для возврата ошибки 404.

  • Sinatra web application library
    Sinatra – это фреймворк с открытым исходным кодом для разработки веб-приложений, написанный на языке Ruby. Используется множеством организаций и послужил прообразом для более чем 50 аналогичных фреймворков на разных языках, в том числе Go.

CHAPTER 3 Concurrency in Go

В отличие от множества современных процедурных и объектно-ориентированных языков, Go не использует threading model for concurrency (модели потоков выполнения) – ее заменяют goroutines (сопрограммы/go-подпрограммы) и channels (каналы). Модель, используемая в Go, имеет меньшие накладные расходы (за счет лучшего распоряжения ресурсами) и гораздо проще в управлении, чем пулы традиционных потоков.

3.1 Understanding Go’s concurrency model

Грубо говоря, concurrency (параллельные вычисления) – это способность программы решать несколько задач одновременно. На практике, когда речь заходит о параллельных программах, имеются в виду программы, в течение примерно одного и того же промежутка времени решающие две или более задач, не зависящих друг от друга, но остающихся частями одной программы.

Многие популярные языки программирования, такие как Java и Python, реализуют параллельные вычисления с помощью потоков выполнения. В Go выбран другой подход. В нем используется модель Communicating Sequential Processes (CSP, взаимодействующих последовательных процессов), предложенная известным ученым Тони Хоаром (Tony Hoare). В этой главе затрагиваются лишь практические аспекты работы с моделью параллельных вычислений в Go, более полное изложение теории CSP можно найти на сайте https://golang.org

Параллельные вычисления в Go основываются на двух важных понятиях:

  • goroutines (сопрограммы/go-подпрограммы)
    goroutines – это функции, выполняющиеся независимо от вызвавшей их функции. Go-разработчики рассматривают go-подпрограммы как функции, выполняющиеся в собственном потоке;
  • channels (каналы)
    channels – это конвейеры для отправки и получения данных. Их можно считать сокетами, действующими внутри программы. Каналы обеспечивают передачу структурированных данных из одной сопрограммы в другую.

3.2 Working with goroutines

In Go, functions are first-class. They can be created inline, passed into other functions, and assigned as values to a variable. You can even declare an anonymous function and call it as a goroutine, all in a compact syntax

Wait group (группа ожидания) – это механизм передачи сообщений, оповещающих ожидающую сопрограмму, что она может продолжить работу. Чтобы воспользоваться им, нужно сообщить группе ожидания, что требуется дождаться завершения каких-то действий, а затем послать ей сигнал об их завершении. Группе ожидания не нужно знать ничего больше, кроме:

  • количества ожидаемых действий
  • момента их завершения

Количество ожидаемых действий увеличивается вызовом wg.Add, а момент завершения задания обозначается вызовом wg.Done. Функция wg.Wait блокирует выполнение, пока не будут завершены все задания, добавленные в группу.

  • Не занимайтесь самостоятельной реализацией блокировок
    Некоторое время назад прошел слух, что блокировки реализованы настолько плохо, что лучше реализовать собственные. Это подвигло многих разработчиков на создание собственных библиотеки блокировок. В лучшем случае они проделали никому не нужную работу. В худшем – получились ненадежные или медленные системы блокировок. Не имея доказательств ненадежности или кода, чрезвычайно чувствительного к производительности, воздержитесь от разработки собственных блокировок. Встроенный пакет прост в использовании, прошел все испытания и соответствует требованиям к производительности большинства приложений.

  • Go включает встроенные средства обнаружения состояния гонки
    Многие инструменты Go, в том числе go run и go test, при передаче флага --race включают в работу механизм обнаружения состояния гонки. Этот механизм существенно замедляет выполнение, но может пригодиться для выявления состояний гонки в процессе разработки.

3.3 Working with channels

  • Не злоупотребляйте каналами
    Channels (Каналы) – фантастический инструмент связи между сопрограммами. Они просты в использовании и значительно упрощают параллельное программирование, по сравнению с threading models (моделью потоков выполнения) в других популярных языках. Но их использованием не стоит увлекаться. Каналы вносят дополнительные затраты и могут негативно влиять на производительность. Они усложняют программу. И самое главное, каналы – единственный источник проблем, связанных с управлением памятью в программах на языке Go. Как любой другой инструмент, каналы следует использовать, только когда в них возникает необходимость, но не при каждом кажущимся удобным случае.

Summary

  • Go’s CSP-based concurrency model
  • Concurrent processing with goroutines
  • Using the sync package for waiting and locking
  • Communicating between goroutines with channels
  • Closing channels properly

CHAPTER 4 Handling errors and panics

Язык Go различает errors (ошибки) и panics (аварии) – два вида неприятностей, возникающих в ходе выполнения программы. Error указывает, что конкретная задача не может быть успешно завершена. Panic соответствует попаданию в тяжелую ситуацию, вероятнее всего, возникшую в результате ошибки программиста.

4.1 Error handling

Обработка ошибок – одна из идиом языка Go, которая часто сбивает с толку новичков. Многие популярные языки, в том числе Python, Java и Ruby, следуют теории обработки исключений, предусматривающей возбуждение и перехват специальных объектов исключений. Другие языки, такие как C, обычно используют для обработки ошибок возвращаемое значение, а управление изменением данных осуществляют с помощью указателей.

Создатели языка Go заменили обработчики исключений возможностью возвращать несколько значений. Наиболее часто для передачи информации об ошибках в языке Go используется последнее возвращаемое значение.

  • Списки аргументов переменной длины
    Как демонстрирует функция Concat, язык Go поддерживает списки аргументов переменной длины. Префикс ... перед типом данных указывает, что функция принимает произвольное количество аргументов этого типа. Язык Go свернет их в массив этого типа. В листинге 4.1 аргумент parts интерпретируется как []string.

TIP: В языке Go имеются две функции, которые удобно использовать для создания ошибок. Функция errors.New из пакета errors хорошо подходит для создания простых ошибок. Функция fmt.Errorf из пакета fmt дает возможность использовать строку формата при конструировании сообщения об ошибке. Go-разработчики часто используют эти две функции.

  • Не совсем случайное
    Одной из интересных деталей примера в листинге 4.7 является генератор случайных чисел. Поскольку генератор случайных чисел инициализируется фиксированным значением, он всегда будет возвращать одну и ту же последовательность «случайных» чисел. В данном случае это удобно, поскольку позволяет проиллюстрировать работу примера. Но в промышленных приложениях не следует использовать фиксированные целые числа для инициализации источника. Простой альтернативой является использование в этом качестве time.Now.

4.2 The panic system

4.2.1 Differentiating panics from errors

Первое, что следует знать об panics (авариях), – чем они отличаются от errors (ошибок). Error сообщает, что произошло событие, не соответствующее нашим ожиданиям. Panic же говорит о наличии таких неполадок, из-за которых система (или конкретная подсистема) не может продолжать функционировать.

Среда выполнения Go предполагает, что ошибки обрабатываются программистом. Если он решит проигнорировать error, среда выполнения никак ему не воспрепятствует. Иначе обстоит дело с panic. В случае panic среда выполнения Go просматривает стек, пытаясь найти для нее обработчика. Если обработчик не найден, среда Go остановит программу. Необработанные panic прекращают работу приложения.

С практической точки зрения, errors – это некие проблемы, появление которых было предусмотрено разработчиками. Они уже задокументированы в коде. Хотя errors представляют собой нечто, выходящее за пределы нормы, но неожиданными их назвать нельзя.

Panic, напротив, всегда происходят неожиданно. Они возникают, когда непредсказуемым образом нарушено правило или превышен предел. Что касается объявления panic в коде: их принято использовать, только когда нет другого способа обработать возникшую ситуацию в данном контексте. Всегда, где только возможно, следует возвращать error.

4.2.2 Working with panics

  • Размышления, выходящие за рамки panic
    Мы только что заявили, что лучше всего в аварии передавать error. Но panic не ограничиваются только типами error, и это придает системе panic дополнительную гибкость. В некотором смысле это напоминает возможность возбуждать исключения в Java с наследниками класса Trowable, которые необязательно являются исключениями. Аварии в Go являются одним из способов избежать раскручивания стека.

4.2.3 Recovering from panics

Любое обсуждение panic будет неполным без упоминания восстановления после panic. Восстановление после panic в Go основывается на одной из особенностей языка, называемой deferred functions (отложенными вызовами функций). Среда выполнения Go имеет возможность гарантировать выполнение функции в момент возврата из родительской функции. Это происходит независимо от того, что является причиной возврата – выполнение оператора return, достижение конца функции или panic.

В заключение описания этого приема дадим несколько полезных рекомендаций по работе с отложенными функциями:

  • объявляйте отложенные функции как можно ближе к началу вмещающей функции;
  • простые объявления, такие как foo:=1, часто помещаются перед отложенными функциями;
  • более сложные переменные объявляются перед отложенными функциями (var myFileio.Reader), но инициализируются после них;
  • внутри функции допускается объявлять несколько отложенных функций, но такая практика не приветствуется;
  • закрывайте файлы, сетевые подключения и другие подобные ресурсы в отложенных замыканиях. Это гарантирует освобождение системных ресурсов даже при возникновении ошибок и аварий.

4.2.4 Panics and goroutines

  • Внутреннее устройство сопрограмм
    Реализация механизма goroutines несколько сложнее, чем просто запуск функции в отдельном потоке. На вики-странице (https://github.com/golang/go/wiki/LearnConcurrency), посвященной параллельным вычислениям в языке Go, приводится длинный список статей, подробно описывающих различные аспекты параллельных вычислений в языке Go, основанных на модели взаимодействующих последовательных процессов.

  • Выгоды от использования замыканий
    Вместо именованных функций, здесь можно использовать closure (замыкание). Closure позволяет получить доступ к переменным, находящимся в области видимости, и решить проблему отсутствия параметров в функции. Но при этом следует позаботиться о состоянии гонки и других проблемах параллельной обработки!