Здравствуйте, Сергей Губанов, Вы писали:
СГ>то это и делать.
Сергей, ваши представления о программировании все еще катастрофически далеки от реальности.
Вы, судя по вашему ответу, считаете, что
а) программа должна трактовать все случаи как равновероятные
б) вся информация для принятия решения доступна ровно там, где выполняется действие.
И то и другое — заблуждения.
Начнем с последнего. Как правило, с неожиданной ситуацией сталкивается код самого нижнего уровня. Что значит "неожиданная ситуация"? Это выражение означает "невозможно выполнить запрошенное действие". Например, мы пытаемся записать килобайт данных в TCP сокет. Упс! Сокет закрылся — например, оборвали кабель, или другая сторона явно разорвала соединение. Внешнему коду, который позвал метод send, вообще ничего не известно про состояние сети. И не может быть известно в силу инкапсуляции — он пишет в какой-то поток, не более того. Это может быть файл, сокет, или блоб-поле в базе данных.
Даже если клиент умен, и предварительно спросил у сокета, можно ли посылать, вовсе не факт, что проблема не возникнет в процессе работы метода:
if(s.IsConnected)
{
s.Send(myData); /// ошибка где-то в середине передачи
}
Что делать? Ага, проверим код результата:
if(s.IsConnected)
{
switch(s.Send(myData))
{
case SOCKET_DISCONNECTED: ...
case BUFFER_OVERFLOW: ...
case ...: ...
else: // уфф, пронесло, все в порядке.
}
}
Все здорово до тех пор, пока у нас действительно достаточно информации. Но ведь этот клиентский код, в свою очередь, может быть всего лишь низкоуровневой оберткой над отправкой данных. Что ему делать? По ТЗ надо отсылать письмо. Прекрасно. У нас код нижнего уровня должен знать адрес администратора и уметь отправлять почту.
Чудно. Если же мы оставляем эту работу более высокоуровневому коду, то ему надо откуда-то узнать, что именно случилось. Или же все кончается методом
bool SaveChangesToDbAndSendNotifications()
В случае возврата false надо отправить письмо — и пусть админ гадает, что же там упало. То ли с базой связаться не удалось, то ли нотификации не уехали.
Вообще, вас уже отправили читать Страуструпа — не упрямьтесь, поверьте — он про все это лучше меня написал. И уже давно.
Второй момент — вы предлагаете трактовать все, оговоренное в ТЗ как штатную ситуацию. Оставим пока за бортом вопрос полноты ТЗ. Решим пока, что все неописанные случаи могут приводить к undefined behavior (вообще в наши дни софт с UB — это моветон, если что).
С вашей точки зрения, если проектировщик догадался спросить, не нужна ли реакция софта на падение метеорита, и ему сказали что нужна, то код должен быть устроен так:
if(метеорит_упал)
{
выполняем_запасные_действия();
}
else
{
идем_по_основной_ветке();
}
Во-первых, такой код тяжело читать. Он намекает на 50% вероятность попадания метеорита; он содержит ветку основной логики как редкий частный случай и затрудняет понимание собственно решаемой задачи.
Кроме того, зачастую реакция на очень большое количество разнообразных ситуаций более-менее одна. К примеру, асп.нет банально перезапускает повисшее приложение, не вдаваясь в детали — метеорит там, деление на ноль, бесконечный цикл или моль дырку проела. В коде, который действует по методу блондинки, надо предусмотреть реакцию как на отсутствие динозавра, так и на его присутствие.
Во-вторых, это банально влияет на производительность. Если у нас есть частая ветка, то она должна исполняться максимально быстро. Особенно это характерно для исключений — если произошел какой-то сбой, не так уж важно быстро его обработать. Тут бы не совсем не упасть — и то хорошо. Вот, к примеру, процессоры так и делают — предсказывают ветвления и стараются ускорить выполнение частой ветки, в ущерб выполнению редкой, которое приводит к дорогостоящему сбросу конвеера. Но у процессора близорукость сильная, его предсказалка в пределах махоньких циклов хорошо работает. А для более крупных блоков он не поможет.
Код, работающий пессимистично, должен, к примеру, перед снятием денег со счета проверить, а хватит ли их там. Ну и пойти либо снимать деньги, либо писать пользователю отмазку. А поскольку стоимость проверки количества денег примерно такая же, что и стоимость съема денег, мы получаем просад производительности минимум в два раза только за счет пессимизма. (На самом деле все еще намного хуже из-за блокировок и прочих эффектов второго порядка). А вот оптимистичный код пытается выполнить все, как будто все хорошо. И только если вдруг оказалось, что что-то не вышло, выполняется дорогой код раскрутки стека, отката транзакции и т.п.
Вот вам и отличие штатной от нештатной — нештатная ситуация редка, и не позволяет довести начатое до конца. Академизм здесь вреден — не все ситуации равноправны.
Да, использовать исключения для штатной работы — глупо:
// Бросает исключение OopsItsTailsAgainException в 50% случаев
void ThrowCoinAndCheckHeads()
enum CoinSides { Heads, Tails};
...
CoinSides ThrowCoin()
{
try
{
ThrowCOinAndCheckHeads();
return CoinSides.Heads;
catch(OopsItsTailsAgainException)
{
return CoinSides.Tails;
}
}
Но это никак не подрывает саму идею исключений как механизма отделения логики обработки нештатных ситуаций от основной логики.

1.1.4 stable rev. 510