Без паники

Короткая заметка про panic() в языке Go.

Часто можно услышать критику языка Go за отказ от исключений (Exceptions). Ведь это же такой хороший способ обработки ошибок. Да, способ хороший, особенно для ООП.

Итак, как же обрабатывают ошибки в Go.

os.Error

Этот тип используется почти во всех библиотечных вызовах. Благодаря возможности возвращать несколько значений в функциях теперь можно возвращать не только значение, но и саму ошибку (nil в случае успеха). Это своеобразный аналог errno в C, только локальный, а не глобальный. Удобно ли это?

// C
fd = open("myfile.txt")
if (fd < 0 && errno == ENOENT) {
  printf("No such file\n");
  return
}

// Go
fd, err := os.Open("myfile.txt")
if err == os.ENOENT {
  println("No such file")
  return
}

Т.е. использование C и Go мало чем отличается в таком случае. Однако, если мы вызываем подряд 5 функций, после вызова каждой будем проверять значение err, то это сильно запутает код. Зачем же нужно возвращать os.Error? Он нужен только для того, чтобы восстановить последствия отдельной конкретной ошибки. Если вы пишете библиотечную функцию, то возвращайте os.Error, потому что приложение может захотеть обрабатывать особые ошибки по-особому.

Кстати, еще в os.Error меня огорчает, что для этого типа необходимо импортировать модуль os. Так, кстати сложилось чисто исторически, и в Go 1 вместо os.Error будет built-in тип error. Но об этом будет совсем другая статья.

И все же, как быть, если приложению плевать на ошибку — ее никак не восстановить, и нужно просто прекратить работу функции? Тогда нам понадобится аналог исключений.

panic

`panic()` вызывает run-time ошибку и прекращает работу программы.

Фактически глобальный exit. Как правило.

Однако, в Go существуют отложенные функции (defer). Так вот, они будут вызваны даже если сработал panic(). Т.е. если вы открыли файл, указали defer f.Close(), то файл будет закрыт даже если в процессе работы будет критическая ошибка.

А значит, можно установить defer-функцию, которая будет реагировать на ошибки panic. Это функция recover(). Например:

func f() {
   ...
   if ... {
      panic("Failed to open file")
   }
   ...
}

func g() {
   ...
   if ... {
      panic("Failed to parse file")
   }
   ...
}

func h() {
   ...
}

func main() {
   // set panic handler
   defer func() {
      if err := recover(); err != nil {
         fmt.Println("Fatal error: ", err)
      }
   }

   // run functions that can fail, like:
   // try {
   //    f();
   //    g();
   //    h();
   // } catch(Exception e) {
   //    ...
   // }
   f()
   g()
   h()
}

Если в любой из функций f(), g() или h() случится, то выполнение main() прервется, и будет вызвана только отложенная функция. В этой отложенной функции будет выведено сообщение «Fatal error: » и текст ошибки. Аналог catch(). Только наоборот. Впрочем, как и многое в Go — наоборот.

Ловить panic-ошибки можно в любой функции на любом уровне вложенности. Единственное, что нужно помнить — panic() только для ошибок, которые невозможно (или не хотите) восстанавливать.

Выводы

По-моему достаточно удобный механизм. Идея не нова, сразу вспоминается старый сишный `atexit()`.

Учитывая, что с помощью panic() можно передавать произвольные типы, то это аналог исключений. При этом эти исключения не обязательно должны наследоваться от базового класса Exception. Весьма простое и гибкое решение.

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s