Как исправлять ошибки в Go

В отличие от традиционных методов, применяемых в других основных языках программирования, таких как JavaScript (где используется оператор try... catch) или Python (с его блоком try... except), работа с ошибками в Go требует иного подхода. Почему? Потому что его возможности по обработке ошибок часто используются неправильно.

В этой статье мы рассмотрим лучшие практики, которые можно использовать для обработки ошибок в Go-приложениях. Для восприятия этой статьи достаточно базового понимания принципов работы Go - если в какой-то момент вы почувствуете, что застряли, можно потратить немного времени и изучить незнакомые концепции.

Пустой идентификатор

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

Как исправлять ошибки в Go

Возможно, причина в том, что вы не ожидаете ошибки от функции (или какой-либо другой ошибки), но это может создать каскадные эффекты в вашей программе. Лучше всего обрабатывать ошибку всегда, когда это возможно.

Обработка ошибок с помощью нескольких возвратных значений

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

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

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

Как исправлять ошибки в Go

Обычно возврат ошибки означает наличие проблемы, а возврат nil - отсутствие ошибок:

Как исправлять ошибки в Go

Таким образом, всякий раз, когда вызывается функция iterate и err не равна nil, возвращаемая ошибка должна быть обработана соответствующим образом - как вариант, можно создать экземпляр механизма повторного выполнения или очистки. Единственным недостатком такой обработки ошибок является отсутствие принудительного вмешательства компилятора Go - вы сами должны решить, как созданная вами функция возвращает ошибку. Вы можете определить структуру ошибки и поместить ее в позицию возвращаемых значений. Один из способов сделать это - использовать встроенную структуру errorString (этот код также можно найти в исходном коде Go):

Как исправлять ошибки в Go

В приведенном примере кода errorString представляет собой строку, возвращаемую методом Error. Для создания пользовательской ошибки необходимо определить свою структуру ошибки и использовать наборы методов для привязки функции к структуре:

Как исправлять ошибки в Go

Вновь созданная пользовательская ошибка может быть затем реструктурирована для использования встроенной структуры ошибки:

Как исправлять ошибки в Go

Одним из ограничений встроенной структуры ошибок является то, что она не содержит трассировки стека. Это затрудняет поиск места возникновения ошибки. Ошибка может пройти через множество функций, прежде чем будет выведена на экран. Для решения этой проблемы можно установить пакет pkg/errors, который предоставляет базовые примитивы обработки ошибок, такие как запись трассировки стека, обертывание, разворачивание и форматирование ошибок. Чтобы установить этот пакет, выполните в терминале следующую команду:

Как исправлять ошибки в Go

Если необходимо добавить к ошибкам трассировку стека или любую другую информацию, облегчающую отладку, используйте функции New или Errorf для создания ошибок с записью трассировки стека. Errorf реализует интерфейс fmt.Formatter, позволяющий форматировать ошибки с использованием рун пакета fmt (%s, %v, %+v и т.д.):

Как исправлять ошибки в Go

Чтобы вывести трассировку стека вместо обычного сообщения об ошибке, нужно использовать в шаблоне формата %+v вместо %v, и трассировка стека будет выглядеть так, как показано в примере кода ниже:

Как исправлять ошибки в Go

Отсрочка, паника и восстановление

Хотя в Go нет исключений, в нем есть похожий механизм, известный как "отложить, запаниковать и восстановиться". Идеология Go заключается в том, что добавление исключений, подобных оператору try/catch/finally в JavaScript, приведет к усложнению кода и побудит программистов обозначать слишком много базовых ошибок, таких как невозможность открыть файл, как исключительные. Не следует использовать defer/panic/recover так же, как throw/catch/finally; только в случаях неожиданного, неустранимого сбоя.
Defer - это языковой механизм, который помещает вызов функции в стек. Каждая отложенная функция выполняется в обратном порядке при завершении основной функции, независимо от того, вызвана ли паника или нет. Механизм откладывания очень полезен для очистки ресурсов:

Как исправлять ошибки в Go

В результате компиляция будет выглядеть так:

Как исправлять ошибки в Go

Паника - это встроенная функция, которая останавливает нормальный ход выполнения. Когда вы вызываете функцию panic в своем коде, это означает, что вы решили, что вызывающая функция не может решить проблему. Таким образом, panic следует использовать только в редких случаях, когда продолжение выполнения кода небезопасно ни для вас, ни для тех, кто интегрирует ваш код в данный момент. Приведем пример кода, показывающий, как работает panic:

Как исправлять ошибки в Go

Приведенный выше пример будет компилироваться как:

Как исправлять ошибки в Go

Как показано выше, если паника используется и не обрабатывается, поток выполнения останавливается, все отложенные функции выполняются в обратном порядке и печатаются трассировки стека.
Для обработки паники и возврата значений, перешедших после вызова паники, можно использовать встроенную функцию recover. recover всегда должна вызываться в отложенной функции, иначе она вернет nil:

Как исправлять ошибки в Go

Как видно из приведенного примера кода, recovery предотвращает остановку всего потока выполнения из-за того, что мы бросили функцию panic и компилятор бы вернулся:

Как исправлять ошибки в Go

Чтобы сообщить об ошибке в качестве возвращаемого значения, необходимо вызвать функцию recover в той же Go рутине, в которой была вызвана функция panic, получить из функции recover структуру ошибки и передать ее в переменную:

Как исправлять ошибки в Go

Каждая отложенная функция будет выполняться после вызова функции, но до оператора возврата. Таким образом, вы можете установить возвращаемую переменную до выполнения оператора возврата. Приведенный выше пример кода будет компилироваться как:

Как исправлять ошибки в Go

Выводы

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

11
Начать дискуссию