Данная заметка является развернутым комментарием к одному моменту в споре об исключениях, завязавшемся в обсуждении моей предыдущей заметки о языках D и Go.
Вкратце напомню, в чем дело. Ув.тов.Left и Gaperton высказывались в том ключе, что раз в Go нет исключений, то и ничего плохого в этом нет. Что можно выделять действия, которые могут завершаться ошибкой (например, делением на ноль или попыткой вычислить квадратный корень из отрицательного числа), в отдельные goroutine. Тогда при возникновении проблемы прибивается только эта goroutine, а запустивший ее код сможет понять, что произошел сбой и что-либо предпримет.
В защиту этой точки зрения приводился подход к разработке ПО на языке Erlang. Там операции распределяются по легковесным Erlang-овским процессам, а сами процессы выстраиваются в деревья, где процесс-родитель информируется о сбоях в дочерних процессах и может либо перезапустить дочерний процесс, либо завершиться самому, либо сделать что-нибудь еще (коротко прочитать об этом можно здесь). При этом сами процессы в Erlang принято разрабатывать в соответствии с принципом let-it-crash. Тут я лучше процитирую ув.тов.Gaperton:
Там рулит подход let-it-crash, при котором ты вообще не пишешь обработки ошибок, и закладываешься только на успешный случай. А когда оно упадет - как бы ни упало - обрабатывается падение процесса, и все.
Собственно говоря, подход к разработке софта очень симпатичный и привлекательный. Все просто – ты пишешь код, который в обычных условиях должен завершаться успешно. Если же условия необычные (то бишь ненормальные), то код просто обваливается (и уже не важно, по сигналу, исключению или еще как-то). Сбой процесса обнаруживается каким-нибудь супервизором и супервизор предпринимает действия по исправлению ситуации. В простейшем случае рестартует процесс.
Отличный подход. Понятное дело, что я не мог его не попробовать. Попробовал. Мне очень понравилось. Код получается простой. Кода получается не много. Поведение программ получается намного более предсказуемым. Так что с точки зрения программиста все просто замечательно.
Но в ряде случаев этот подход почему-то не нравится пользователям софта. А когда пользователям не нравится и они настоятельно просят сделать что-то, что мне, как программисту не нравится и кажется странным и абсолютно не нужным, то приходится наступать на горло своей песни. Т.е. забивать на let-it-crash, чтобы воплотить в жизнь принцип let-it-live :/
Ну, например. Есть программулина, которая время от времени лезет в БД за каким-то значением. Поскольку программулина работает долго, а БД лежит на другом сервере, то неизбежно возникают ситуации, когда связь с БД рвется. И обнаруживается это как раз при очередном обращении к БД.
Казалось бы, все просто – мы в БД с select-ом, а нам вместо ответа exception. Ну мы, как приверженцы let-it-crash, описание проблемы в log, а сами зовем abort. После чего нас рестартуют, мы заново подключаемся к БД, выполняем select и продолжаем работать как будто этого сбоя и не было.
Но вот пользователям эта простота не нравится. А у нас, говорят, в логах слишком много таких ошибок появляется, временами. С ними разбираться приходиться. А у нас, говорят, системы мониторинга начинаю кричать, когда какой-то процесс аварийно завершается. А еще у нас, признаются, временами супервизоры “подвисают” и забывают упавшие процессы поднимать на автомате. Поэтому не могли бы вы, программисты вы наши всесильные, сделать так, чтобы при возникновении ошибки “нет связи с сервером БД” при select-е вы не звали abort, а просто пытались заново к БД подключиться? Раза эдак два, три. А еще лучше, чтобы параметры восстановления подключения мы бы сами в конфиге прописывали. И только если все попытки обломились, либо же если на эти попытки было затрачено слишком много времени, то тогда уже abort. Ну очень надо!
Так что let-it-crash – это очень хорошо для программиста, гораздо лучше, чем let-it-live. Только не всегда допустимо. А там где недопустимо, нужны какие-то средства, которые не позволят прозевать неожиданную проблему (вроде забытой проверки кода возврата функции). И которые позволяют проблему вовремя диагностировать и исправить. Исключения, например.
PS. Для C++ных программ, работающих в режиме 24x7, подход let-it-crash, наверное, еще более важен, чем для Erlang-а. Поскольку уронить C++ную программу очень просто. Иногда для этого достаточно просто на новую версию Visual C++ перейти :)