Обработка ошибок в Go постоянно вызывает споры и возникает среди тем ежегодного опроса о сложнейших проблемах, с которыми встречаются разработчики, пишущие на этом языке. Тем не менее, когда дело доходит до работы с ошибками в конкурентной среде или объединении ошибок одной горутины, Go предлагает отличные пакеты, которые упрощают их обработку. Давайте посмотрим, как объединять несколько ошибок, созданных одной горутиной.
Одна горутина — несколько ошибок
Объединение ряда ошибок в одну может оказаться весьма кстати при работе, к примеру, над кодом, использующим политику пересмотра. Вот базовый пример, где нам нужно собрать сгенерированные ошибки:

Эта программа считывает и анализирует CSV текст, отображая найденные ошибки. Для получения полного отчета будет удобнее сгруппировать их в одну с помощью одного из двух пакетов:
- При использовании
go-multierror
от HashiCorp, ошибки можно совместить в одну стандартную:

После чего вывести отчет:


Ошибки конкатенированы через точку с запятой без дополнительного форматирования.
Что касается производительности каждого из этих пакетов, то вот показатели бенчмарка при тестировании той же программы с увеличенным количеством сбоев:
name time/op alloc/op allocs/op
HashiCorpMultiErrors-4 6.01µs ± 1% 6.78kB ± 0% 77.0 ± 0%
UberMultiErrors-4 9.26µs ± 1% 10.3kB ± 0% 126 ± 0%
Реализация от Uber немного медленнее и потребляет больше памяти. Этот пакет спроектирован для объединения ошибок сразу после их сбора, а не путем поочередного присоединения. При группировании ошибок результаты схожи, но код уже выглядит менее эстетично, поскольку ему требуется выполнить дополнительный шаг. Вот новые результаты:
name time/op alloc/op allocs/op
HashiCorpMultiErrors-4 6.01µs ± 1% 6.78kB ± 0% 77.0 ± 0%
UberMultiErrors-4 6.02µs ± 1% 7.06kB ± 0% 77.0 ± 0%
Оба пакета задействуют Go-интерфейс error
с реализацией функции Error() string
в их пользовательском варианте.
Одна ошибка — несколько горутин
При использовании нескольких горутин для выполнения задачи важно корректно обрабатывать результаты и агрегирование ошибок, чтобы обеспечить верность программы.
Начнем с реализации, использующей несколько горутин для выполнения серии действий, каждое из которых будет продолжаться одну секунду:

Чтобы отобразить распространение ошибки, первое действие третьей горутины будет давать сбой. Вот, что происходит:

Как и ожидалось, программе требуется примерно три секунды, поскольку большинству горутин необходимо пройти через три действия, которые занимают по одной секунде:
go run . 0.30s user 0.19s system 14% cpu 3.274 total
Однако нам может потребоваться сделать горутины зависимыми друг от друга и отменить все при провале одной. Чтобы не выполнять лишнюю работу, можно просто добавить контекст, который при провале горутины будет отменяться:

Именно это и позволяет сделать errgroup
— распространить ошибку и контекст при работе с группой горутин. Вот новый код, применяющий этот пакет:

Теперь программа выполняется быстрее, так как распространяет отмененный ошибкой контекст:
go run . 0.30s user 0.19s system 38% cpu 1.269 total
Еще одним преимуществом данного пакета является то, что нам больше не нужно беспокоиться об ожидающей группе, добавляя и размечая горутины по завершению — пакет делает это за нас, нам же только нужно сообщить, когда мы готовы к завершению процесса.
Читайте также:
- Как работает функция Defer в Golang
- Удалённые вызовы процедур в Golang
- Полиморфизм с интерфейсами в Golang
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Vincent Blanchon: Go: Multiple Errors Management