Почему в Go нет Try-Catch?
go намеренно не поддерживает try-catch, а поддерживает только синтаксис panic-recover. Это вызывает недовольство у многих разработчиков, привыкших к обработке ошибок с помощью try-catch. Так почему же try-catch не включен? Потому что try-catch имеет ряд проблем.
try-catch-finally
Конструкция try-catch предназначена для обработки ошибок и исключительных ситуаций, которые могут возникнуть во время выполнения программы (runtime). Кроме того, в блоке finally записывается код, который должен быть выполнен независимо от того, возникло исключение или нет.
Обработка ошибок и ответственность
В 80-х и 90-х годах обработка ошибок была очень простой. Сообщения об ошибках ограничивались фразами типа «гибкий диск заполнен», «в дисководе нет гибкого диска», «нет прав на запись на гибкий диск», и разработчики того времени, когда возникала ошибка, передавали ее (throw) до точки обработки ошибок для унифицированной обработки. В таких условиях конструкция try-catch работала эффективно.
Однако со временем ситуация изменилась. Бизнес-логика усложнилась, появились базы данных и транзакции, приходилось вызывать множество API через сеть и интерпретировать множество сообщений запросов, и даже с появлением параллельного программирования ошибки приходилось обрабатывать в других потоках, а не в основном.
Ошибки стали настолько сложными, что их больше невозможно было обрабатывать в одном месте, и ни одно место не могло нести ответственность за все ошибки. Здесь возникает серьезная проблема с try-catch.
Перекладывание ответственности в try-catch
try-catch, одним словом, это способ, с помощью которого субъект, вызвавший ошибку, перекладывает ответственность (за последующие действия) за ее возникновение на кого-то другого. Этим «кем-то» может быть блок catch, или его родительский метод, или родитель родителя родителя родителя... кто угодно. Другими словами, в мире, где обработка ошибок становится все более многочисленной и сложной, try-catch выбирает подход «кто-нибудь это сделает». Рассмотрим следующий код.
1try {
2 data = readFile("hello.txt");
3 structuredData = parseData(data);
4 insertDBStatus(structuredData[1]);
5 startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7 e.printStackTrace();
8}
Проблема вышеуказанного кода заключается в том, что неясно, кто обрабатывает printStackTrace, и невозможно узнать, в каком коде произошла ошибка. Более того, чем больше логики появляется внутри оператора try, тем ужаснее становится проблема. Но парадоксально, чем сложнее становилась разработка, тем больше разработчики «подсаживались» на try-catch, перекладывающий ответственность, не задумывались об обработке ошибок, их чувство ответственности ослабевало, и в конечном итоге они забывали о сути обработки ошибок и исключений. Так как же golang решил эту проблему?
panic, recover
Одним из преимуществ go является наличие различных систем, предназначенных для того, чтобы не дать разработчикам пойти по плохому пути и сделать их хорошими разработчиками. panic-recover также можно привести в качестве примера. Panic и recover на первый взгляд кажутся ничем не отличающимися от try-catch, но они отличаются тем, что при возникновении проблемы они не перекладывают ответственность на внешнюю сторону. Когда в go происходит panic, вместо того чтобы передавать это значение наружу, проблему следует решать в том месте, где произошел panic. Это возлагает на разработчика ответственность за обработку ошибки, побуждая его более глубоко задуматься о том, где, кто и как должна быть обработана эта ошибка. Это обеспечивает автономию пользователя, оставляя при этом пространство для размышлений.
Кроме того, выбор слова «panic» также можно считать очень удачным: в отличие от try-recover, panic-recover уже одним словом накладывает на разработчика давление, указывая, что его следует использовать только в явных ошибочных ситуациях, а не в исключительных. Естественно, разработчик не злоупотребляет recover и использует его только там, где это необходимо. Это значительно помогает писать лаконичный код в go.