Здравствуйте, Erop, Вы писали:
AV>>да, чтобы к моменту return все было корректно. Как ты это собираешься обеспечивать в случае кодов ошибок?
E>Ну так же, как и остальные постусловия.
То есть в случае ошибки ты будешь либо откатывать проделанное назад либо получить новый инвариант. Так?
E>Программирование там, тестирование модульное и функциональное и т. д... E>Кодирование, короче, типа
Это все отменяется при исключениях? Нет, не отменяется.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[57]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, alex_public, Вы писали:
AV>>То что он выглядит не сильно приятно не отменяет того, что это он переписан в процедурном подходе. Это раз. И покажи не ужас, когда ошибку можно обработать на 4-5 уровней выше нежели ее обнаружение? И чтобы два раза не переписывать, то добавим, что верхняя фунция внутри себя вызывает не одну функцию, а три-пять. И те внутри себя тоже вызывают что-то. И при обработке ошибок на верхнем уровне хотелось бы знать что за ошибка произошла и в какой именно функции. А чтобы еще более весело стало, то функции, в которых возникают ошибки пишутся другими командами. Посему их изменить ты не можешь. А коды ошибок они используют одинаковые.
_>Это же уже обсуждалось прямо в этой теме. Вы предлагаете Егору написать говнокод на кодах возврата и показать на нём что аналогичный говнокод на исключениях будет проще. Очень сомнительная идея.
Предложи как обрабатывать ошибку чтения байти из сокета. В месте возникновения ты не знаешь является это ошибкой или нет. А знаешь лишь несколькими уровнями выше.
Здравствуйте, alex_public, Вы писали:
AV>>Кто тебе сказал что не можем? Еще как можем. Исключения можно вкладывать друг в друга. А то при помощи кодов ошибок сообщить это гораздо сложнее.
_>Естественно можно вкладывать но тогда соответствующие try/catch/throw расползаются по стеку вызова ничуть не слабее тех самых пробросов кодов возврата.
Гораздо меньше. Просто не надо каждый вызов функции обрамлять try/catch. Если не знаешь что делать с исключением и ничего полезного не можешь добавить, то не лови его.
_>И соответственно такой код начинает требовать больших (не забываем про классы исключений и т.п.) усилий, чем код с кодами возврата.
try/catch использовать как if/else, то усилий будет больше. Никто не отрицает этого. Если бензопилу использовать как обычную ножовку, то тоже моментально проклянешь ее.
Здравствуйте, ambel-vlad, Вы писали:
AV>Гораздо меньше. Просто не надо каждый вызов функции обрамлять try/catch. Если не знаешь что делать с исключением и ничего полезного не можешь добавить, то не лови его.
Определение классов исключений тоже забывать не надо. )
AV>try/catch использовать как if/else, то усилий будет больше. Никто не отрицает этого. Если бензопилу использовать как обычную ножовку, то тоже моментально проклянешь ее.
Ну собственно о широте области правильного применения мы тут и спорим. Вряд ли тут есть сторонники полного запрета исключений или сторонники всё всё делать через исключения.
Re[58]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, ambel-vlad, Вы писали:
AV>Предложи как обрабатывать ошибку чтения байти из сокета. В месте возникновения ты не знаешь является это ошибкой или нет. А знаешь лишь несколькими уровнями выше.
Как это не знаем? На уровне приложения мы всегда знаем должно ли оно завершаться в подобном случае (кстати довольно редкий сценарий по идее). Другое дело если мы пишем библиотеку — тут логичнее предусмотреть оба возможных варианта. Как собственно и сделано в Boost'е.
Re[59]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, alex_public, Вы писали:
AV>>Предложи как обрабатывать ошибку чтения байти из сокета. В месте возникновения ты не знаешь является это ошибкой или нет. А знаешь лишь несколькими уровнями выше.
_>Как это не знаем? На уровне приложения мы всегда знаем должно ли оно завершаться в подобном случае (кстати довольно редкий сценарий по идее).
На уровне приложения — да. Но это не значит что мы об этом знаем в месте где возникла проблема. Кстати, что там было про уровни и прочее? Что ты будешь делать на уровне, где осуществляется чтение данных из коммуникационного канала, при проблеме с чтением данных из сокета?
Здравствуйте, alex_public, Вы писали:
AV>>Гораздо меньше. Просто не надо каждый вызов функции обрамлять try/catch. Если не знаешь что делать с исключением и ничего полезного не можешь добавить, то не лови его.
_>Определение классов исключений тоже забывать не надо. )
Да, я помню это. Но кроме кол-ва строк есть и другие моменты. Например, что ты будешь делать с пропущенным кодом ошибки?
AV>>try/catch использовать как if/else, то усилий будет больше. Никто не отрицает этого. Если бензопилу использовать как обычную ножовку, то тоже моментально проклянешь ее.
_>Ну собственно о широте области правильного применения мы тут и спорим.
в этом топике уже не раз говорили по этому поводу. Например, не знаешь что делать с исключением — не лови его.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[60]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, ambel-vlad, Вы писали:
E>>Вернёшь ошибку, тот, кто позвал -- разберётся... AV>А тот кто позвал тоже не знает что делать с этой ошибкой. Кидаем код ошибки еще выше?
Что значит "кидаем"?
Функция "считать байт" может позваться много из откуда. Может из какого-то места, ну там, скажем ждём команды пользователя, а может просто вводим строчку.
Если второе, то возвращаем, что строчка закончилась аварийно, если первое, что ответа не будет по утере связи и т. д...
AV>Простой пример. Была функция. Не тобой написанная. Но ты ее юзаешь. Написал тесты. Все хорошо. Потом пришла новая версия этой функции. С новым кодом. О котором ты не знал или забыл. Как твои старые тесты это отловят?
А вдруг она не с новым кодом, а вообще другое делает? Ну там в некоторых случаях не сортирует, а фильтрует жанные, скажем? Откуда ты про это узнаешь?
С кодом проще, напиши assert, что обработал все варианты...
AV>Может стоит думать над полезной функциональностью, а не как бы не пропустить какую-либо ошибку. А то если ее забыть, то где и когда она всплывет неизвестно.
Почему "ошибку"? Просто предусмотреть все расклады, а не только самый основной...
AV>Каким образом?
Должна быть обработка. Формально то, что обработка есть легко проверяется.
E>>Ну это любой обычный код так выглядит. Скажем никогда не видел std::cout и сервисы из stdio в одной программе? AV>Видел. И не такое видел. И что?
Ну обычное же дело? Ну значит так можно программировать выходит...
AV>Я просил более чем конкретный код. И пишется он не так уж и долго. Но на протяжении нескольких сообщений это вызывает затруднения у тебя. Или какая-то другая причина не дает тебе это сделать?
Не понятно что конкретно надо написать. Что-то вроде:
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, ambel-vlad, Вы писали:
AV>То есть в случае ошибки ты будешь либо откатывать проделанное назад либо получить новый инвариант. Так?
Или изменю инвариант...
В том то и сила,, что мы всегда более или менее знаем как можно ослабить уловия...
AV>Это все отменяется при исключениях? Нет, не отменяется.
Так я с этого же и начал?
Как на практике обеспечить тестироване обеспечения базовой гарантии и обеспечить покрытие этими тестами всех сценариев?..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
AV>>То есть в случае ошибки ты будешь либо откатывать проделанное назад либо получить новый инвариант. Так?
E>Или изменю инвариант...
А если не изменяешь, то чем тебе исключение в этом вопросе мешают?
AV>>Это все отменяется при исключениях? Нет, не отменяется. E>Так я с этого же и начал? E>Как на практике обеспечить тестироване обеспечения базовой гарантии и обеспечить покрытие этими тестами всех сценариев?..
Так же как и в случае кодов ошибок. Почему в этой части исключения выделяются чем-то особенным?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[61]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, Erop, Вы писали:
E>>>Вернёшь ошибку, тот, кто позвал -- разберётся... AV>>А тот кто позвал тоже не знает что делать с этой ошибкой. Кидаем код ошибки еще выше? E>Что значит "кидаем"?
С данном случае означает "возвращаем".
E>Функция "считать байт" может позваться много из откуда. Может из какого-то места, ну там, скажем ждём команды пользователя, а может просто вводим строчку.
А может и из места, которое читает очредной кусок данных. И что делаем с ошибкой? Ни в функции чтения байта мы не знаем что с ней делать, в месте вызова этой функции тоже не знаем что с ней делать.
E>Если второе, то возвращаем, что строчка закончилась аварийно, если первое, что ответа не будет по утере связи и т. д...
Строчка аварийно может закончится по разным причинам. Поэтому мы можем спрятать исходную проблему и затруднить ее поиск в будушем.
AV>>Простой пример. Была функция. Не тобой написанная. Но ты ее юзаешь. Написал тесты. Все хорошо. Потом пришла новая версия этой функции. С новым кодом. О котором ты не знал или забыл. Как твои старые тесты это отловят?
E>А вдруг она не с новым кодом, а вообще другое делает? Ну там в некоторых случаях не сортирует, а фильтрует жанные, скажем? Откуда ты про это узнаешь?
E>С кодом проще, напиши assert, что обработал все варианты...
Какие все варианты? Как ты гарантируешь, что не пропустил какой-нибудь вариант?
AV>>Может стоит думать над полезной функциональностью, а не как бы не пропустить какую-либо ошибку. А то если ее забыть, то где и когда она всплывет неизвестно.
E>Почему "ошибку"? Просто предусмотреть все расклады, а не только самый основной...
Как не забыть эти все? И как потом обнаружить что именно мы забыли?
AV>>Каким образом? E>Должна быть обработка. Формально то, что обработка есть легко проверяется.
Что обработка есть — да. Что она полная нифига не легко.
E>>>Ну это любой обычный код так выглядит. Скажем никогда не видел std::cout и сервисы из stdio в одной программе? AV>>Видел. И не такое видел. И что? E>Ну обычное же дело? Ну значит так можно программировать выходит...
С этим не спорю. Можно все. Например, в цирке ходят на руках.
AV>>Я просил более чем конкретный код. И пишется он не так уж и долго. Но на протяжении нескольких сообщений это вызывает затруднения у тебя. Или какая-то другая причина не дает тебе это сделать?
E>Не понятно что конкретно надо написать. Что-то вроде:
Покажи не ужас, когда ошибку можно обработать на 4-5 уровней выше нежели ее обнаружение? И чтобы два раза не переписывать, то добавим, что верхняя фунция внутри себя вызывает не одну функцию, а три-пять. И те внутри себя тоже вызывают что-то. И при обработке ошибок на верхнем уровне хотелось бы знать что за ошибка произошла и в какой именно функции. А чтобы еще более весело стало, то функции, в которых возникают ошибки пишутся другими командами. Посему их изменить ты не можешь. А коды ошибок они используют одинаковые.
AV>Так же как и в случае кодов ошибок. Почему в этой части исключения выделяются чем-то особенным?
Потому, что в типичных программах return'ов раз в 100 меньше, чем мест, откуда может вылететь исключение...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
AV>>Так же как и в случае кодов ошибок. Почему в этой части исключения выделяются чем-то особенным?
E>Потому, что в типичных программах return'ов раз в 100 меньше, чем мест, откуда может вылететь исключение...
Да? Мест из которых может вылететь исключение столько сколько же и мест откуда возвращается код ошибки. Так что не выходит твои 100.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[62]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, ambel-vlad, Вы писали:
AV>А может и из места, которое читает очредной кусок данных. И что делаем с ошибкой? Ни в функции чтения байта мы не знаем что с ней делать, в месте вызова этой функции тоже не знаем что с ней делать.
Обычно бывает низкоуровневая функция типа "считать блок" через которую реализована более высокоуровневая "считать буфер"...
"Считать блок" обычно имеет кучу раскладов, тое низкоуровневых.
"считать буфер" уже имеет более высокоуровневые причины отказов.
Выше обычно уже лежит считывание каких-то конкретных структур и там уже будет своя интерпретация причин провалов, так, постепенно, уровень за уровнем мы получаем всё более обобщённую интерпретацию событий.
На каждом уровне мы при этом всё знаем, что нам с чем делать. Бывают, конечно, ошибки, которые не надо реинтерпретировать, то есть это фатальный крэш с точки зрения любого уровня кода, например конец памяти. Ну тогда да, можно и исключение бросить...
AV>Строчка аварийно может закончится по разным причинам. Поэтому мы можем спрятать исходную проблему и затруднить ее поиск в будушем.
В смысле "спрятать"? Ну если с т. з. внешнего кода аварийное завершение строки неприемлемо, то он тоже вернёт ошибку и т. д...
AV>Какие все варианты? Как ты гарантируешь, что не пропустил какой-нибудь вариант?
Ну, если программа нормально написана, то по уровню понятно что он может вернуть чисто формально даже. Но если таки не понятно, то можно написать assert( false ) в ветке, в которую попадём, если ничего из предусмотренного, например...
AV>>>Может стоит думать над полезной функциональностью, а не как бы не пропустить какую-либо ошибку. А то если ее забыть, то где и когда она всплывет неизвестно.
Ну это же уже тоже обсуждалось. Если надёжность и вообще поддержка пользователей нас мало парят, то та и нада. Только тогда не понятно зачем вообще на какие-то базовые гарантии тратиться, и вообще зачем на плюсах это делать...
AV>Как не забыть эти все? И как потом обнаружить что именно мы забыли?
Ну так же, как и при остальном программировании не?
AV>Что обработка есть — да. Что она полная нифига не легко.
Перднамеренная неполная -- саботаж, за это можно наказать. Непреднамеренная неполная -- редкость.
AV>Покажи не ужас, когда ошибку можно обработать на 4-5 уровней выше нежели ее обнаружение? И чтобы два раза не переписывать, то добавим, что верхняя фунция внутри себя вызывает не одну функцию, а три-пять. И те внутри себя тоже вызывают что-то. И при обработке ошибок на верхнем уровне хотелось бы знать что за ошибка произошла и в какой именно функции. А чтобы еще более весело стало, то функции, в которых возникают ошибки пишутся другими командами. Посему их изменить ты не можешь. А коды ошибок они используют одинаковые.
Тв хочешь, что бы я тебе написал пару тысяч строк кода? Или что такое "уровень", на каждом из которых три-пять функций, а уровней 4-5 штук.
Это даёт нам примерно 1000 функций, вообще-то.
И во всём этом коде нельзя как-то интерпретировать отказ в обеспечении основной функциональности?
Мне трудно понять, что это за код такой. Но есть сильное подозрение, что там чего-то неопроектировали.
Приведи понятный какой-то стек вызовов и описания окружения, что бы можно было понять что это за 4-5 уровней...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>и да, на мой взглаяд симбиан для телефона намногоболее удачная платформа, чем андроид. E>может на андроид легче перенести какое-нибудь большое приложение, но, зато, на симбиане телефоны хорошие получались, а не глюкавое УГ с рывками в интерфейсе и батарейкой на полдня...
Был у меня самсунг на симбиане, недешевый. Сейчас самсунг на андроиде, тоже из верхнего ряда. Андроид как-то поудобнее, программ больше всяких разных. Маркет — кульная штука. Под симбиан оно вроде до РФ не добралось...
Батарейки хватает дня на три (если в режиме телефона + немного смартфона). Рывков нет, глюков — тоже.
Проги писать ни под симбиан, ни под андроид не пробовал, так что сравнить не могу.
Здравствуйте, enji, Вы писали:
E>Был у меня самсунг на симбиане, недешевый. Сейчас самсунг на андроиде, тоже из верхнего ряда. Андроид как-то поудобнее, программ больше всяких разных. Маркет — кульная штука. Под симбиан оно вроде до РФ не добралось...
Ну так то до РФ...
Ну и вообще под симиан программ тоже много было.
Но суть тут вообще не в этом. Телефон в первую очередь должен хорошо основную функциональнсть выполнять, как бы...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Но суть тут вообще не в этом. Телефон в первую очередь должен хорошо основную функциональнсть выполнять, как бы...
ну дык — что симбиановский самсунг жил 2-3 дня, что андроидный... Звонилка в андроиде поудобнее. С другой стороны, между ними 3 года, может и симбиановская бы допилилась
Здравствуйте, alex_public, Вы писали:
_>Однако многие программисты расширяют значение термина "ошибка" до чего-то типа "возврат неудачи функции", которое совершенно не обязательно должно прерывать нормальное исполнение программы, а всего лишь направляет по другому сценарию. И в такой ситуации у исключений уже нет никаких особых преимуществ автоматизма, т.к. по нормальному обработка (пускай это и переупаковка исключения всего лишь) такой ошибки происходит на локальном уровне (в отличие от настоящих ошибок, обработчик которых скорее всего один глобальный). Соответственно применяя исключения в такой ситуации мы выполняем лишнюю работу (классы исключений, сам код больше и т.п.) не получая от этого никаких реальных преимуществ.
Нормально расширяют. Это удобно. Основной критерий — обработка ошибки может быть не в той функции, которая вызвала функцию с ошибкой. Т.е. не в direct caller. А это встречается достаточно часто. А уж для библиотеки так вообще must have — откуда там известно, как программа у пользователя библиотеки устроена и где на самом деле находится обработка "неудачи".
Более подробно ряд сценариев распишу.
Первый. Возврат из слоя абстракции, который внутри разделен на несколько процедур. Возврат нелокальный и производится всегда наружу этого слоя (т.е. обрабатываться внутри не будет). Ручками каждый вывод разбирать и оборачивать в коды ошибок — муторно, долго и приводит к ошибкам. Тот же разбор заголовка — неверный формат внутри заголовка обрабатываться не будет, поэтому переход нелокальный (за границу слоя "конфиг файл"). Ошибки более низкого слоя абстракции (IO error) — тоже на этом уровне мы обычно не можем обработать. Это вообще переход ошибки "через слой". Это нормально. Все-таки "нельзя считать профиль потому что там что-то не то" и "нельзя считать файл, потому что диск не читается" — разные ошибки даже с точки зрения пользователя. И решать проблемы нужно по-разному. В первом случае, вероятно, файл не тот. Во втором — либо файл совсем не тот, либо пора обратиться к бэкапу.
Второй. Управление "уникальностью" кодов ошибок между различными библиотеками. Числовые коды ошибок могут пересекаться. Исключения (при должном именовании) — нет. Поэтому большой зоопарк поддерживать проще. И разбираться в нем проще. Тот же вывод разных кодов ошибок будет разный (см. выше). Вообще, хорошее исключение (не важно, на каком уровне) дает достаточно хорошее описание причины ошибки. Поэтому не нужно спрашивать пользователя "а что там у вас вообще происходит?" а ситуация становится понятна с первого же исключения.
Третий. Предоставление дополнительной информации об ошибках. Например, при чтении профиля из нескольких файлов один из файлов не найден. Описания "профиль не прочитался" для решения пользователем ну совсем никак не достаточно. Описания "мы не смогли прочитать файл" — тоже. А вот "мы не смогли прочитать файл abc.xml" — вполне нормальное. Вдруг пользователь вспомнит, что только вчера его антивирус нашел что-то подозрительное в файле abc.xml и этот файл теперь живет в карантине? Или пользователь папочку резервно копировал. Но вместо копирования куда-то переместил. Ну и так далее и тому подобное (ссылки на файлы из конфигов, ссылки в интернет, etc, etc...). Сюда же можно отнести и локализацию сообщений об ошибках (а скажите в случае кодов возврата, в какой строке у пользователя конфиг неверен...)
Четвертый. Грамотно спроектированные исключения образуют отношения master-detail. Я могу обрабатывать (не важно на каком) уровне ошибки "по шаблону". Например, типичная обработка — FileNotFoundException — одна обработка, остальные IOException — другая. Или там станадртный EBADF — это вообще внутренняя ошибка приложения, при которой пора все убивать (потому что у нас инварианты поехали). А вот ECONNRESET можно и пользователю показать, сказав, что у него с сетью проблемы. Ну или там в зависимости от ошибки будем пытаться запрос к базе через некоторое время повторить (ошибка связи/транзакция накрылась) или не будем (синтаксис запроса неверен, какой смысл еще раз получать ту же ошибку?).
Пятый. Проброс ошибки "насквозь" слоя абстракции. При грамотном дизайне это, как ни странно, достаточно частая ситуация. У нас выделен какой-то алгоритм/класс и т.п. Этот класс имеет зависимости, получаемые в конструкторах (аргументах метода и т.п.). Возможность ошибок там мы предусматриваем (соответствующие типы исключений). Но вот с самими ошибками мы ничего сделать не можем — интерфейс слишком общий. Т.е. мы можем обнаружить ошибку, вернуть инварианты, затем передать ошибку выше. А вот "внешний" слой с этой ошибкой может что-то разумное и сделать. Он ведь знает, что конкретно передавал в зависимости, знает ошибки этого слоя и т.п. Что характерно, "на уровне ниже" (в той самой переданной зависимости) мы не можем обработать ошибку. Ну как мы там можем ошибку сети обработать, например? При повторной закачке все может быть уже по-другому. Поэтому нужно возвращаться через несколько слоев абстракции. Пример — банальные read/write, которые с любым файловым дескриптором работают (и возвращать нужно через несколько слоев, на тот уровень, где дескрипторы были созданы). Но read/write исключения не кидают по историческим причинам, так что у меня к ним претензий нет. Можно еще вспомнить про п. 2 и то, что зависимостей может быть несколько и в них могут использоваться различные библиотеки.
Вот все это на "кодах возврата" делается не очень хорошо...
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, maxkar, Вы писали:
M>>А что мешает то? В catch мы попадаем, если есть ошибка. Задача catch — восстановить все варианты блока try{}catch{}. Если мы имеем некий формализм по описанию исключений, мы знаем, в какой строке выше какие исключения могут происходить. В чем проблема то? Если ошибки нет, мы в catch не попадем. Если ошибку не обработали, то мы вообще в весь остаток функции не попадем.
E>Проблема в структуре получающихся предикатов. E>Их либо вообще не получается написать, либо получается, что всё зависит от всего. E>Ну то есть в НОРМАЛЬНОЙ программе кроме того, что у нас будет функция, read_profile, которая спрячем внутри себя подробноти как там и откуда итается профиль, за одно и предусловие этой функции свернётся в "профиль доступен", А в программе которая написана, так что у нас стек вызовов глубиной а пять функций. шириной в 10 и наружу торчат 100500 исключений, то там получится предусловие вида " на таком-то смещее в таком-то файле стоиит цифра, а не буква" и так 100500 раз... E>Собственно полезная логика в этом шуме просто утонет бесследно и всё. А так пролем никаких нет
В случае с исключениями все так же прекрасно свернется в то же предусловие "профиль доступен" для той же функции read_profile. В качестве бонуса, я получаю постусловие "а если вдруг предусловие не выполнено, после выполнения функции мы имеем какое-то исключение". При более формальном подходе мы будем даже знать, что у нас есть IOException в случае нарушения предусловия. А вот как вы будете гарантировать ваше предусловие я плохо представляю. Там же на нижнем уровне read может с EIO завершиться, например. Если везде коды ошибок проверять, то вы получите if'ы через строчку. Т.е. треть программы у вас будут if'ы, треть — return'ы от этих if'ов и треть — логика программы. Можно все это в одну функцию заинлайнить, сильно легче от этого не станет. А количество функций здесь определяется не столько подходом к архитектуре, сколько самой задачей. Какая есть структура данных в задаче, такую и придется читать. Где-то в теме про сложность подобное называли "essential complexity". Так что ваш предикат "профиль доступен" точно так же собирается по остальным функциям (из их предусловий) и имеет настолько же ужасный вид. Ну и постусловие "если профиль не доступен, мы получаем в результатет вызова функции ошибку доказывается одинаково в процедурном стиле и на исключениях.
В общем, с неидеальным внешним миром программа на кодах ошибок примерно такая же будет. И предикаты такие же . Точнее, на исключениях предусловие будет "профиль доступен или профиль не доступен". А постусловие "профиль таки доступен и у нас есть результат или профиль не доступен и у нас есть исключение". У вас после разборки с неидеальным миром (от которого далеко не всегда можно уйте) — то же самое. Если же вы вздумаете делать прогон по файлу с целью гарантии предусловия, то на ровном месте получите уязвимость, когда файл изменяется между проверкой и чтением. В общем, после поправки на неидеальность мира — для доказательства не будет разницы, исключения там или куча return в теле функций.
Если что, я не предлагаю какую-то "гарантированную" логику делать на исключениях. Там как раз все в процедурный или даже функциональный стиль переписывается (в функциональном предикаты еще проще ведь, там только применения функций и есть). Только вот в 95% случаев мы в конце концов упираемся во взаимодействие со внешним миром (файлы/сеть/пользователь), оттуда и растет большинство исключений. Нарушение внутренних инвариантов и прочее — как раз особые ситуации, которые нужно рассматривать в каждом отдельном случае, там не будет общих решений.
Re[61]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
Здравствуйте, Erop, Вы писали:
E>Ну мы же обсуждаем РЕАЛЬНЫЕ практики, а не гипотетические?.. E>И с самого начала эта тема была в С++/С, так что плюсовые к тому же. E>В той же Яве всё-таки есть GC, что очень упрощает жизнь программам с исключениями. E>Гарантии безопасности проще намного выглядят и обеспечить их тоже проще...
Вот с этим согласен. С памятью руками неудобно работать. Но те же c++ как раз исключениями дают какую-никакую, а доказуемость тех же освобождений памяти.
X *x = new X();
try {
//...
} catch {
delete x;
throw;
}
Аналогично с дескрипторами файлов и т.п. Я предлагаю только такую форму анализировать как try-finally (обработку исключений завернуть внутри в другой блок). Вот по этому коду можно доказать, что delete x будет вызвано всегда, когда X успешно создался (в силу семантики try/catch). На кодах возврата такое не очень хорошо записывается и доказывается. Там или выходы сразу отсюда (и копипаста деинициализации), или куча if'ов в конце, чтобы проверить, на каком этапе что сломалось. Я помню, как оно на C выглядит. GC, естественно, помогает, так как 90% ресурсов — память, поэтому в этих случаях можно try/finally или обертки с деструкторами не писать.
E>Собственно это главный недостаток исключений -- понижение надёжности С++ программ непредсказуемым образом. Второй недостаток -- более запутанная семантика кода, в принципе, лечится правильной архитектурой, только правильносто архитектуры так же будет обеспечиваться, как и бащовая гарантия, то есть с дырами.
Если говорить про внешний мир, то разницы с обработкой кодов ошибок нет. А это 95% случаев использования исключений (в нормальной программе, конечно). Ну не покроете вы внешний мир предикатами. Так что в процедурном стиле будет аналогия исключений, либо гораздо больше UB, чем в программе на исключениях. Естественно, я говорю только про программы с наличием архитектуры и некоторых соглашений по исключениям (навроде упомянутого — "источник — внешний мир").
E>Не очень понятно, что такое "какая-нибудь ошибка"?..
Меня как раз стэк интересовал, из следующего вопроса. Могу еще, конечно, подкинуть вопрос. На вызов close(fd) вернул -1, errno == EIO. Вопрос — в каком же теперь состоянии находится файл? Нужно ли делать еще раз close (и если да, то сколько) или файловый дескриптор уже помечен как доступный и у меня в дальнейшем не исчерпаются эти дескрипторы? Мой man на этот вопрос ответа не дает.
M>>Вопрос: как доказать, что мы не поймаем переполнение стека в процессе очистки уже выделенных ресурсов?
E>Обычно проблему переполнения стека считают фатальной. Так же как и другие версии нехватки памяти... В целом есть много способов с этим бороться, но самый хороший -- не иметь риска, что она вообще возниктнет.
Так не всегда же получается. Вот я XML в виде DOM из файла считал. Дом оказался большой, но считался. Кто-нибудь мне гарантирует, что у него потом деструктор нормально отработает, а не исчерпает стек в самый неподходящий момент в результате обхода дочерних узлов? Ну и еще можно чего-нибудь именно со внешним миром придумать (и древовидной структурой, чтобы не линеаризовалось).
Я бы и нехватку памяти пообрабатывал. Например, что-нибудь в редактор/просмотрщик из сети загружаем. Но, к сожалению, она, в отличие от Stack Overflow, нелокальна (если где-то произошла, велика вероятность, что произойдет и в других (системных) потоках).
E>Я бы на С++ намеренно переполнять стек не стал бы.
Ну вот там пример с DOM'ом. Можно, конечно, переписать самому библиотеку парсинга с использованием гаранированного размера. Я сомневаюсь, что готовые библиотеки ограничивают стек (хотя нехватку памяти проверять как раз могут). Ну и не совсем понятно, как в ней деструктор писать. Наверное, как-то сложно модифицировать уже существующие структуры. Память выделять нельзя — а вдруг она уже кончилась и у нас деструктор не отработает. Стек, так и быть, мы предположим примерно на том же месте, где и инициализировался DOM. Поэтому я и предполагал деструкцию на стеке (при условии, что конструкция на том же уровне прошла).
E>Можно пример алгоритма, о котором речь, кстати?
Когда вопрос задавал, что-то вроде XML DOM, описанного выше, имел в виду. Ну и любая древовидная структура из внешнего источника (задаваемого пользователем). А вот теперь вспомнил, что совсем недавно писал библиотечку топологической сортировки. Не нашел нигде неинтрузивной топосортировки с описанием циклов в случае ошибок. Все кидают неинформативное "у нас есть цикл", а я хочу пользователю написать, где же у него цикл. Вот там используется рекурсия, на ней все коротко и просто. После вашего вопроса понял, что туда стоит поставить fallback и сделать нерекурсивный алгоритм (не хочу по-умолчанию сильно мучать heap). Нужно теперь найти время и все закодить .