Обработка ошибок в Go

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

Одна горутина  —  несколько ошибок

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

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

  • При использовании go-multierror от HashiCorp, ошибки можно совместить в одну стандартную:

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

  • В случае использования multierr от Uber реализация будет аналогична, но вывод получится следующий:

Ошибки конкатенированы через точку с запятой без дополнительного форматирования.

Что касается производительности каждого из этих пакетов, то вот показатели бенчмарка при тестировании той же программы с увеличенным количеством сбоев:

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

Еще одним преимуществом данного пакета является то, что нам больше не нужно беспокоиться об ожидающей группе, добавляя и размечая горутины по завершению  —  пакет делает это за нас, нам же только нужно сообщить, когда мы готовы к завершению процесса.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Vincent Blanchon: Go: Multiple Errors Management