Re[57]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: maxkar  
Дата: 04.03.13 17:18
Оценка: +1
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, ambel-vlad, Вы писали:


E>Ну, как бы, в нормальных программах принято скрывать подробности реализации от клиентского кода.

E>То есть если тебе функция fopen вернёт что-то вроде ("список в таком-то блоке исчерпан") вместо "нет файла" или там "нет доступа"...

В принципе — нормальная ошибка. Я не знаю, правда, как там в C++ с отловом наследников но в иерархии ESomeFSListExhausted extends ENoSpaceLeftOnDevice extends IOException смотрелось бы нормально. Как минимум пользователю выводить стоит разные ошибки. Я бы, например, увидев ESomeFSListExhausted, мог бы подумать, сделать backup и пересоздать новую файловую систему с увеличенным размером какого-то блока. Ситуация реальная, я tmpfs подстраивал, на которой в какой-то момент inodes закончились.

E>То есть, если в случае кодов возврата, мы явно везде должны продумать что будет происходить, если случится такой облом или сякой.

В контексте error prone как раз на код ошибки гораздо проще забить (не проверять возвращаемое значение), чем в случае с исключениями (которое полетит мимо и дальше). Даже в man'е по close написано . Код с исключениями, конечно, тоже требует правильного использования управляемых оберток или try/finally (try/catch{...;throw;} для C++). Но для однотипных блоков на исключениях получается меньше писанины (одна обретка вместо if (error) через строку).
Re[7]: Tizen 2.0
От: Erop Россия  
Дата: 04.03.13 18:22
Оценка:
Здравствуйте, enji, Вы писали:

E>ну дык — что симбиановский самсунг жил 2-3 дня, что андроидный...


Про самсунг ничего не знаю. Оригинальные телефоны с симбиан бывало и подольше жили...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[60]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: Erop Россия  
Дата: 04.03.13 18:36
Оценка:
Здравствуйте, maxkar, Вы писали:

M>В случае с исключениями все так же прекрасно свернется в то же предусловие "профиль доступен" для той же функции read_profile.

Продемонстрируй, пожалуйста, как это происходит, на каком-нибудь примере.
Ещё лучше доказать, что всегда свернётся, конечно же, но это у тебя вряд ли получится

Я согласен, что если ты все исключения с нижнего уровня отловиш и оттранслируешь в какие-то исключения внешнего уровня, то всё свернётся, только это проще эффективнее и безопаснее сделать на том, что ты называешь "коды возврата", но можно и на исключениях, конечно, только не понтно в чём профит

А если речь идёт таки о том, что со всей иерархиии стека вызовов все абы что кидают, и снаружи это всё вылетает, то что-то как-то не верится, что "само свернётся", таки

M>А вот как вы будете гарантировать ваше предусловие я плохо представляю.

Я это уже понял. Я только не понимаю, как ты вообще можешь при этом гарантировать хоть какие-то постусловия или инварианты кода... В том смысле что эти постусловия ничем от других не отличаются вообще-то


M>Только вот в 95% случаев мы в конце концов упираемся во взаимодействие со внешним миром (файлы/сеть/пользователь), оттуда и растет большинство исключений.

IMHO, это зависит от специфики задачи. Кроме того, код, который нкепосредственно с этим миром работает должен воспринимать отказ мира работать, как штатную ситуацию, и никаких исключений по этому поводу не кидать. Ну кроме там ситуаций, когда нарушины внутренние инварианты, ну там типа драйвер неадекватно работает или ещё что фатальное происходит. Но это не внешний мир чудит, а наш комп рушится просто.
А если внешний мир, то адекватно это всё как-то обрабатывать и возвращать клиентскому коду. А уж клиентский код снова обрабаьывает и возвращает выше и так пока на каком-то уровне мы не решим, что это типа всё, приплыли, значит. Ну и там уже можно и исключение кинуть...
Для того клиентского кода, который уже уверен, что отказ в обслуживании — это фатальная ошибка, и при этом ему пофиг какое исключение кидать, сожно предоставить API, которое кидает исключения.
Но это будет выбор вызывающей стороны кидающее API звать или нет...


M>Нарушение внутренних инвариантов и прочее — как раз особые ситуации, которые нужно рассматривать в каждом отдельном случае, там не будет общих решений.


Не, нарушение внутренних инвариантов -- это assert, их не рассматривать в каждом случае надо и не обрабатывать, а исправлять код. В тмо я абсолютно согласен с Дворкиным...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[62]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: Erop Россия  
Дата: 04.03.13 18:51
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Аналогично с дескрипторами файлов и т.п. Я предлагаю только такую форму анализировать как try-finally (обработку исключений завернуть внутри в другой блок). Вот по этому коду можно доказать, что delete x будет вызвано всегда, когда X успешно создался (в силу семантики try/catch). На кодах возврата такое не очень хорошо записывается и доказывается.

Ну в С++ на эту тему есть аки RAII там всякие... Но, в целом и finlly и деструкторы автоматических объектов -- это всё про одно и то же. И к обработке ошибок это всё непосредственного отношения не имеет.
Скажем мы насоздавали каких-то структур и что-то с их помощью ищем. Как нашли -- прямо из недр цикла выходим. Как бы никаких ошибок и исключений тут и близк нет. а возврат из середины функции есть...

M>Если говорить про внешний мир, то разницы с обработкой кодов ошибок нет. А это 95% случаев использования исключений (в нормальной программе, конечно). Ну не покроете вы внешний мир предикатами. Так что в процедурном стиле будет аналогия исключений, либо гораздо больше UB, чем в программе на исключениях. Естественно, я говорю только про программы с наличием архитектуры и некоторых соглашений по исключениям (навроде упомянутого — "источник — внешний мир").


Ну это всё равно всё сводит к тому, что исклчения можно использовать весьма ограниченно...
Это и есть предмет спора, на самом деле, стоит ли использовать исклбчения для любых "альтернативных" сценариев, или всё-таки только для настолько редких, что мы не хотим в них работать, а хотим в них получить информацию для отладки только

M>Меня как раз стэк интересовал, из следующего вопроса. Могу еще, конечно, подкинуть вопрос. На вызов close(fd) вернул -1, errno == EIO. Вопрос — в каком же теперь состоянии находится файл?

Это всё сильно выходит за рамки темы "обработка ошибок", это скорее про API тех или иных языков.
В целом авторы API должны бы были подумать, что обозначает тот или иной результат вызова их функции и опубликовать это. На мой вкус провал функции освобождения ресурса может обозначать одно из двух.
1) Мы освобождаем то, что не держали
2) Всё уже умерло и мы находимся в условиях расстрелянной памяти
В (2) единственное разумное -- отгрузка с фиксацией чего получитя.
В (1), с большой вероятностью, тоже самое, но могут быть стратегии и похитрее...

M>Так не всегда же получается. Вот я XML в виде DOM из файла считал. Дом оказался большой, но считался. Кто-нибудь мне гарантирует, что у него потом деструктор нормально отработает, а не исчерпает стек в самый неподходящий момент в результате обхода дочерних узлов? Ну и еще можно чего-нибудь именно со внешним миром придумать (и древовидной структурой, чтобы не линеаризовалось).


Ну в целом это не единственный, но один из понятных аргуентов за то, что вещи вроде DOM надо грузить, создавая ноды и все их данные на отдельном аллокаторе, а потом грохать аллокатор, а не модель поэлементно...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[58]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: Erop Россия  
Дата: 04.03.13 18:54
Оценка:
Здравствуйте, maxkar, Вы писали:

M>В контексте error prone как раз на код ошибки гораздо проще забить (не проверять возвращаемое значение), чем в случае с исключениями (которое полетит мимо и дальше).


Ну просто на коммит вешаешь статиеский анализатор с проверкой того, что коды ошибок обрабатываются и всё...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[60]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: Erop Россия  
Дата: 04.03.13 21:43
Оценка:
Здравствуйте, maxkar, Вы писали:

M>В случае с исключениями все так же прекрасно свернется в то же предусловие "профиль доступен" для той же функции read_profile.

Продемонстрируй, пожалуйста, как это происходит, на каком-нибудь примере.
Ещё лучше доказать, что всегда свернётся, конечно же, но это у тебя вряд ли получится

Я согласен, что если ты все исключения с нижнего уровня отловиш и оттранслируешь в какие-то исключения внешнего уровня, то всё свернётся, только это проще эффективнее и безопаснее сделать на том, что ты называешь "коды возврата", но можно и на исключениях, конечно, только не понтно в чём профит

А если речь идёт таки о том, что со всей иерархиии стека вызовов все абы что кидают, и снаружи это всё вылетает, то что-то как-то не верится, что "само свернётся", таки

M>А вот как вы будете гарантировать ваше предусловие я плохо представляю.

Я это уже понял. Я только не понимаю, как ты вообще можешь при этом гарантировать хоть какие-то постусловия или инварианты кода... В том смысле что эти постусловия ничем от других не отличаются вообще-то


M>Только вот в 95% случаев мы в конце концов упираемся во взаимодействие со внешним миром (файлы/сеть/пользователь), оттуда и растет большинство исключений.

IMHO, это зависит от специфики задачи. Кроме того, код, который нкепосредственно с этим миром работает должен воспринимать отказ мира работать, как штатную ситуацию, и никаких исключений по этому поводу не кидать. Ну кроме там ситуаций, когда нарушины внутренние инварианты, ну там типа драйвер неадекватно работает или ещё что фатальное происходит. Но это не внешний мир чудит, а наш комп рушится просто.
А если внешний мир, то адекватно это всё как-то обрабатывать и возвращать клиентскому коду. А уж клиентский код снова обрабаьывает и возвращает выше и так пока на каком-то уровне мы не решим, что это типа всё, приплыли, значит. Ну и там уже можно и исключение кинуть...
Для того клиентского кода, который уже уверен, что отказ в обслуживании — это фатальная ошибка, и при этом ему пофиг какое исключение кидать, сожно предоставить API, которое кидает исключения.
Но это будет выбор вызывающей стороны кидающее API звать или нет...


M>Нарушение внутренних инвариантов и прочее — как раз особые ситуации, которые нужно рассматривать в каждом отдельном случае, там не будет общих решений.


Не, нарушение внутренних инвариантов -- это assert, их не рассматривать в каждом случае надо и не обрабатывать, а исправлять код. В этом я абсолютно согласен с Дворкиным...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[63]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: ambel-vlad Беларусь  
Дата: 04.03.13 22:39
Оценка: +1
Здравствуйте, Erop, Вы писали:

AV>>А может и из места, которое читает очредной кусок данных. И что делаем с ошибкой? Ни в функции чтения байта мы не знаем что с ней делать, в месте вызова этой функции тоже не знаем что с ней делать.


E>Обычно бывает низкоуровневая функция типа "считать блок" через которую реализована более высокоуровневая "считать буфер"...

E>"Считать блок" обычно имеет кучу раскладов, тое низкоуровневых.
E>"считать буфер" уже имеет более высокоуровневые причины отказов.

То есть заменяем одни коды ошибок другими кодами.

E>На каждом уровне мы при этом всё знаем, что нам с чем делать.


Переписать код ошибки на другой — это называется знаем что с чем делать?

AV>>Строчка аварийно может закончится по разным причинам. Поэтому мы можем спрятать исходную проблему и затруднить ее поиск в будушем.

E>В смысле "спрятать"?

На более высоком уровне я буду знать только что строчка завалилась аварийно. Какова причина аварии? А фиг его знает. Как будем исправлять проблему? А собственно какую именно проблему? Мы же не знаем что именно произошло.

AV>>Какие все варианты? Как ты гарантируешь, что не пропустил какой-нибудь вариант?

E>Ну, если программа нормально написана, то по уровню понятно что он может вернуть чисто формально даже.

То есть ты посмотрел на уровень и сразу же видишь все коды ошибок. Жаль, что у меня такого дара нет.

AV>>>>Может стоит думать над полезной функциональностью, а не как бы не пропустить какую-либо ошибку. А то если ее забыть, то где и когда она всплывет неизвестно.


E>Ну это же уже тоже обсуждалось. Если надёжность и вообще поддержка пользователей нас мало парят, то та и нада. Только тогда не понятно зачем вообще на какие-то базовые гарантии тратиться, и вообще зачем на плюсах это делать...


При использовании кодов ошибок эти гарантии остаются. Только не в профиль, а в фас.

AV>>Как не забыть эти все? И как потом обнаружить что именно мы забыли?

E>Ну так же, как и при остальном программировании не?

Ты лучше ответь какими действиями ты собираешься гарантировать что ничего не забыл. И как будешь искать что забыл.

AV>>Что обработка есть — да. Что она полная нифига не легко.

E>Перднамеренная неполная -- саботаж, за это можно наказать. Непреднамеренная неполная -- редкость.

Как ты собираешься проверять, что обработка полная?

AV>>Покажи не ужас, когда ошибку можно обработать на 4-5 уровней выше нежели ее обнаружение? И чтобы два раза не переписывать, то добавим, что верхняя фунция внутри себя вызывает не одну функцию, а три-пять. И те внутри себя тоже вызывают что-то. И при обработке ошибок на верхнем уровне хотелось бы знать что за ошибка произошла и в какой именно функции. А чтобы еще более весело стало, то функции, в которых возникают ошибки пишутся другими командами. Посему их изменить ты не можешь. А коды ошибок они используют одинаковые.


E>Тв хочешь, что бы я тебе написал пару тысяч строк кода? Или что такое "уровень", на каждом из которых три-пять функций, а уровней 4-5 штук.

E>Это даёт нам примерно 1000 функций, вообще-то.

Давай пока ограничимся одной веткой. А дальше будет видно.

E>И во всём этом коде нельзя как-то интерпретировать отказ в обеспечении основной функциональности?


На промежуточных уровнях ты можешь и не знать отказ это или нет. Например, при получении удаленного объекта обломалось чтение байта из коммуникационного канала. Это отказ или нет? А фиг его знает. Идет на функцию выше. Которая читает пакет данных. Это отказ или нет? А фиг его знает. Идем еще выше. И только несколькими уровнями выше мы видим, что произошел облом. Значит пробуем прочитать старое, кешированное значение. И если его нет, то тогда считаем, что первоначальная ошибка чтения байта была отказом. А как ты это отработаешь на своих кодах? Тем более, что в процессе получения удаленного объекта таких ситуаций может быть гораздо более одной.

E>Приведи понятный какой-то стек вызовов и описания окружения, что бы можно было понять что это за 4-5 уровней...


Упрощенно:
[ccode]

void F(void) {
F1_1();
F1_2();
F1_3();
F1_4();
F1_5()
}

void F1_1() {
F2_1();
...
}

void F2_1() {
F3_1();
...
}

void F3_1() {
F4_1();
...
}

int F4_1() {
...
return 1; //return error code
...
}

[/ccode]]

В функции F4_1 возникла проблема. Как ее обработать ты знаешь только в функции F. Измени код чтобы было видно как ты будешь это обрабатывать.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[60]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: alex_public  
Дата: 05.03.13 04:02
Оценка:
Здравствуйте, ambel-vlad, Вы писали:

AV>На уровне приложения — да. Но это не значит что мы об этом знаем в месте где возникла проблема. Кстати, что там было про уровни и прочее? Что ты будешь делать на уровне, где осуществляется чтение данных из коммуникационного канала, при проблеме с чтением данных из сокета?


Да не важно на каком уровне код принимает решение о том ошибка это или нет. Мы то (программисты) всё равно знаем это решение, кодируя нижний уровень. У нас же не библиотека, а приложение... Ну а если на этом самом высоком уровне возможны оба варианта решения, то тогда просто использовать какой удобнее в конкретном случае.
Re[52]: Вот еще, или я, кажется, читать разучился
От: alex_public  
Дата: 05.03.13 04:13
Оценка:
Здравствуйте, ambel-vlad, Вы писали:

AV>Да, я помню это. Но кроме кол-ва строк есть и другие моменты. Например, что ты будешь делать с пропущенным кодом ошибки?


А в чём отличие от пропущенного исключения? Всё равно же оно проявится только при тестирование с ошибочными данными. Как и в коде с кодами возврата.

AV>в этом топике уже не раз говорили по этому поводу. Например, не знаешь что делать с исключением — не лови его.


Вопрос скорее в том надо ли кидать. )))
Re[52]: Вот еще, или я, кажется, читать разучился
От: alex_public  
Дата: 05.03.13 04:43
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Нормально расширяют. Это удобно. Основной критерий — обработка ошибки может быть не в той функции, которая вызвала функцию с ошибкой. Т.е. не в direct caller. А это встречается достаточно часто. А уж для библиотеки так вообще must have — откуда там известно, как программа у пользователя библиотеки устроена и где на самом деле находится обработка "неудачи".


Что касается библиотек, то на мой взгляд правильные C++ библиотеки должны реализовывать обе возможности. Что мы и видим например в Boost'е. Хотя бывают и такие ошибки (опять же выделение памяти и т.п.), которые гарантированно лучше исключениями. )))

M>Первый. Возврат из слоя абстракции, который внутри разделен на несколько процедур. Возврат нелокальный и производится всегда наружу этого слоя (т.е. обрабатываться внутри не будет). Ручками каждый вывод разбирать и оборачивать в коды ошибок — муторно, долго и приводит к ошибкам. Тот же разбор заголовка — неверный формат внутри заголовка обрабатываться не будет, поэтому переход нелокальный (за границу слоя "конфиг файл"). Ошибки более низкого слоя абстракции (IO error) — тоже на этом уровне мы обычно не можем обработать. Это вообще переход ошибки "через слой". Это нормально. Все-таки "нельзя считать профиль потому что там что-то не то" и "нельзя считать файл, потому что диск не читается" — разные ошибки даже с точки зрения пользователя. И решать проблемы нужно по-разному. В первом случае, вероятно, файл не тот. Во втором — либо файл совсем не тот, либо пора обратиться к бэкапу.


Это мы же уже обсуждали вроде.) Если приложение в случае кривого конфига должно завершаться, то конечно используем исключения. А если оно просто подставляет дефолтные значения и работает дальше, то зачем они тут?

Ну и соответственно если только при одном виде ошибок с конфигом завершаемся, то тогда только этот вид ошибок и обрабатываем исключениями...

M>Второй. Управление "уникальностью" кодов ошибок между различными библиотеками. Числовые коды ошибок могут пересекаться. Исключения (при должном именовании) — нет. Поэтому большой зоопарк поддерживать проще. И разбираться в нем проще. Тот же вывод разных кодов ошибок будет разный (см. выше). Вообще, хорошее исключение (не важно, на каком уровне) дает достаточно хорошее описание причины ошибки. Поэтому не нужно спрашивать пользователя "а что там у вас вообще происходит?" а ситуация становится понятна с первого же исключения.


Нууу тут можно согласиться, если говорим о Java. В C++ я не видел общепринятой стратегии именования. Там даже потомка общего по сути нет — в каждом приложение/библиотеке может быть свой прародитель. Хотя в последнее время многие стали использовать для этого boost::exception, т.к. это позволяет автоматически делать такие http://www.boost.org/doc/libs/1_53_0/libs/exception/doc/motivation.html вещи. Но это далеко не правило, а только тенденция...

M>Третий. Предоставление дополнительной информации об ошибках. Например, при чтении профиля из нескольких файлов один из файлов не найден. Описания "профиль не прочитался" для решения пользователем ну совсем никак не достаточно. Описания "мы не смогли прочитать файл" — тоже. А вот "мы не смогли прочитать файл abc.xml" — вполне нормальное. Вдруг пользователь вспомнит, что только вчера его антивирус нашел что-то подозрительное в файле abc.xml и этот файл теперь живет в карантине? Или пользователь папочку резервно копировал. Но вместо копирования куда-то переместил. Ну и так далее и тому подобное (ссылки на файлы из конфигов, ссылки в интернет, etc, etc...). Сюда же можно отнести и локализацию сообщений об ошибках (а скажите в случае кодов возврата, в какой строке у пользователя конфиг неверен...)


Вообще то для таких вещей есть классические решения. Типа функций GetLastError/SetLastError с буфером где-нибудь в tls.

M>Четвертый. Грамотно спроектированные исключения образуют отношения master-detail. Я могу обрабатывать (не важно на каком) уровне ошибки "по шаблону". Например, типичная обработка — FileNotFoundException — одна обработка, остальные IOException — другая. Или там станадртный EBADF — это вообще внутренняя ошибка приложения, при которой пора все убивать (потому что у нас инварианты поехали). А вот ECONNRESET можно и пользователю показать, сказав, что у него с сетью проблемы. Ну или там в зависимости от ошибки будем пытаться запрос к базе через некоторое время повторить (ошибка связи/транзакция накрылась) или не будем (синтаксис запроса неверен, какой смысл еще раз получать ту же ошибку?).


Не очень понятно чем в этом исключения лучше. Разве что тем, что catch автоматом это отлавливает и не надо писать лишний if. Ну так и классы исключений тоже писать тогда не надо.

M>Пятый. Проброс ошибки "насквозь" слоя абстракции. При грамотном дизайне это, как ни странно, достаточно частая ситуация. У нас выделен какой-то алгоритм/класс и т.п. Этот класс имеет зависимости, получаемые в конструкторах (аргументах метода и т.п.). Возможность ошибок там мы предусматриваем (соответствующие типы исключений). Но вот с самими ошибками мы ничего сделать не можем — интерфейс слишком общий. Т.е. мы можем обнаружить ошибку, вернуть инварианты, затем передать ошибку выше. А вот "внешний" слой с этой ошибкой может что-то разумное и сделать. Он ведь знает, что конкретно передавал в зависимости, знает ошибки этого слоя и т.п. Что характерно, "на уровне ниже" (в той самой переданной зависимости) мы не можем обработать ошибку. Ну как мы там можем ошибку сети обработать, например? При повторной закачке все может быть уже по-другому. Поэтому нужно возвращаться через несколько слоев абстракции. Пример — банальные read/write, которые с любым файловым дескриптором работают (и возвращать нужно через несколько слоев, на тот уровень, где дескрипторы были созданы). Но read/write исключения не кидают по историческим причинам, так что у меня к ним претензий нет. Можно еще вспомнить про п. 2 и то, что зависимостей может быть несколько и в них могут использоваться различные библиотеки.


Да, я согласен что в случае прямого проброса ошибки на один или несколько уровней выше, исключения становятся удобнее кодов возврата. Но на мой взгляд при правильном проектирование потребность в такой схеме возникает всё же довольно редко.
Re[62]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: alex_public  
Дата: 05.03.13 04:58
Оценка:
Здравствуйте, maxkar, Вы писали:

M>Вот с этим согласен. С памятью руками неудобно работать. Но те же c++ как раз исключениями дают какую-никакую, а доказуемость тех же освобождений памяти.

M>
M>X *x = new X();
M>try {
M>//...
M>} catch {
M>  delete x;
M>  throw;
M>}
M>

M>Аналогично с дескрипторами файлов и т.п. Я предлагаю только такую форму анализировать как try-finally (обработку исключений завернуть внутри в другой блок). Вот по этому коду можно доказать, что delete x будет вызвано всегда, когда X успешно создался (в силу семантики try/catch). На кодах возврата такое не очень хорошо записывается и доказывается. Там или выходы сразу отсюда (и копипаста деинициализации), или куча if'ов в конце, чтобы проверить, на каком этапе что сломалось. Я помню, как оно на C выглядит. GC, естественно, помогает, так как 90% ресурсов — память, поэтому в этих случаях можно try/finally или обертки с деструкторами не писать.

Ээээ, это немного не по теме, но не могу не заметить что ТАК с указателями в C++ не работают. Это какой-то мутантный вариант между чистым C и C++. По нормальному должно быть unique_ptr<X> x(new X()); и никаких delete далее вообще. Ну и соответственно память будет автоматически гарантировано освобождена и при любых исключениях далее и при просто return из любой точки.

M>Если говорить про внешний мир, то разницы с обработкой кодов ошибок нет. А это 95% случаев использования исключений (в нормальной программе, конечно).


Кстати, а вот лично я (Егор может по другому думает, с точки зрения подхода Дейкстры) никогда и не утверждал что у обработки с кодами ошибок есть какие-то преимущества. Я тоже считаю что разницы с исключениями нет. И вот как раз по этому их и не надо (в большинстве случаев, кроме специально оговоренных) использовать! Т.к. разницы в результате нет, а усилий на реализацию исключения требуют всё же чуть больших.
Re[61]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: ambel-vlad Беларусь  
Дата: 05.03.13 06:20
Оценка:
Здравствуйте, alex_public, Вы писали:

AV>>На уровне приложения — да. Но это не значит что мы об этом знаем в месте где возникла проблема. Кстати, что там было про уровни и прочее? Что ты будешь делать на уровне, где осуществляется чтение данных из коммуникационного канала, при проблеме с чтением данных из сокета?


_>Да не важно на каком уровне код принимает решение о том ошибка это или нет. Мы то (программисты) всё равно знаем это решение, кодируя нижний уровень. У нас же не библиотека, а приложение...


И что мы знаем в функции чтения байта из коммуникационного канала?

_>Ну а если на этом самом высоком уровне возможны оба варианта решения, то тогда просто использовать какой удобнее в конкретном случае.


В смылсе "оба варианта"? Что такое в данном случае вариант?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[53]: Вот еще, или я, кажется, читать разучился
От: ambel-vlad Беларусь  
Дата: 05.03.13 06:20
Оценка:
Здравствуйте, alex_public, Вы писали:

AV>>Да, я помню это. Но кроме кол-ва строк есть и другие моменты. Например, что ты будешь делать с пропущенным кодом ошибки?


_>А в чём отличие от пропущенного исключения?


Последствия пропущенного исключения мы видим сразу.

_>Как и в коде с кодами возврата.


А это может вылезти гораздо позже. И совсем не там.

AV>>в этом топике уже не раз говорили по этому поводу. Например, не знаешь что делать с исключением — не лови его.


_>Вопрос скорее в том надо ли кидать. )))


А что делать? Возвращать код ошибки?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[62]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: alex_public  
Дата: 05.03.13 06:43
Оценка:
Здравствуйте, ambel-vlad, Вы писали:

AV>И что мы знаем в функции чтения байта из коммуникационного канала?


Ох, в терминах приложения это будет не чтение байта, а чтение чего-то осмысленного...

AV>В смылсе "оба варианта"? Что такое в данном случае вариант?


В смысле что данная ситуация может интерпретироваться и как ошибка и как нормальное поведение, в зависимости от каких-то сторонних факторов. Но это не так что бы особо часто бывает.
Re[63]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: ambel-vlad Беларусь  
Дата: 05.03.13 07:01
Оценка:
Здравствуйте, alex_public, Вы писали:

AV>>И что мы знаем в функции чтения байта из коммуникационного канала?


_>Ох, в терминах приложения это будет не чтение байта, а чтение чего-то осмысленного...


Функция читает байт. И обламывается. Что будем делать?

AV>>В смылсе "оба варианта"? Что такое в данном случае вариант?


_>В смысле что данная ситуация может интерпретироваться и как ошибка и как нормальное поведение, в зависимости от каких-то сторонних факторов.


ОК. Здесь у нас нет разночтения. Это хорошо. Так что будет делать в точке возникновения проблемы, не зная ошибка это или нет?

_>Но это не так что бы особо часто бывает.


В твоих приложения? Верю.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[64]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: alex_public  
Дата: 05.03.13 15:56
Оценка:
Здравствуйте, ambel-vlad, Вы писали:

AV>Функция читает байт. И обламывается. Что будем делать?


Ну я же уже говорил что это странно иметь такую абстрактную функцию в приложение. Это скорее на библиотеку похоже. А в приложение у нас функции имеют какой-то конкретный смысл. Вот мы обсуждали пример с профилем и там на самом низком уровне была функция чтения одного int'a. По сути это же тоже функция чтения байта (ну или 4-ёх), однако в приложение это именно функция чтения поля конфига, а не чтения байта. Соответственно зная глобальную стратегию приложения на этот счёт (умираем при кривом конфиге или же работает на дефолтных настройках) мы легко можем определить и оптимальное поведение той самой низкоуровневой функции в случае ошибки.

А вообще такие функции действительно чаще всего берут из каких-то библиотек. И если библиотека качественная, то там будет оба варианта реализации. А мы уже выбираем нужный как раз в зависимости от того, какую роль эта функция будет иметь в контексте данного приложения.
Re[65]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: ambel-vlad Беларусь  
Дата: 05.03.13 23:38
Оценка:
Здравствуйте, alex_public, Вы писали:

AV>>Функция читает байт. И обламывается. Что будем делать?


_>Ну я же уже говорил что это странно иметь такую абстрактную функцию в приложение. Это скорее на библиотеку похоже.


библиотека — это что? Отдельная dll?

_>А в приложение у нас функции имеют какой-то конкретный смысл. Вот мы обсуждали пример с профилем и там на самом низком уровне была функция чтения одного int'a. По сути это же тоже функция чтения байта (ну или 4-ёх), однако в приложение это именно функция чтения поля конфига, а не чтения байта. Соответственно зная глобальную стратегию приложения на этот счёт (умираем при кривом конфиге или же работает на дефолтных настройках) мы легко можем определить


А можем и не определить. Например, если надо спросить пользователя хочет ли он работать с дефолтными настройками. А это можно спросить совсем в другом месте. И что же нам делать в функции с обломом при чтении?

_>А вообще такие функции действительно чаще всего берут из каких-то библиотек. И если библиотека качественная, то там будет оба варианта реализации. А мы уже выбираем нужный как раз в зависимости от того, какую роль эта функция будет иметь в контексте данного приложения.


Из каких вариантов мы выбираем?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re[59]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: maxkar  
Дата: 06.03.13 14:55
Оценка: 4 (1)
Здравствуйте, Erop, Вы писали:

E>Ну просто на коммит вешаешь статиеский анализатор с проверкой того, что коды ошибок обрабатываются и всё...


Вы это серьезно? Это же застрелиться можно будет. Для начала нужно будет для каждой функции сказать, возвращает она код ошибки или нет (и что это за код). В том числе и на безобидные тотальные функции вроде всей математики. Далее, нужно будет долго учить анализатор тому, что значит "обработка кодов ошибок". Ошибка "не удалился файл" вполне может игнорироваться для определенных файлов. Или, в зависимости от кода и типа, выдаваться пользователю, например (а вот file not exists выдавать не нужно). Потом у вас встанет еще вопрос о "тотальности" обработки ошибок — нужно ведь убедиться не в том, что "какие-то" ошибки обработаны, а в том, что "обработаны все ошибки, которые нужно было обработать". И ладно, если вы используете числовые коды, а что делать в условиях необходимости предоставить более подробную информацию об ошибке? В случае, если у нас поведение параметризуется внешне? В качестве примера последнего — я делаю парсер языка программирования и очень хочу сделать его "полиморфным" по типу. Т.е. Парсер самого языка получает в качестве параметра "TypeParser". Он достаточно абстрактный и возвращает что-то вроде void *. Там в идеале type parameter (generic и т.п.), но только не времени компиляции, а времени выполнения... Т.е. правильная сигнатура Program<T> readProgram(InputStream, TypeParser<T>), конкретный TypeParser будет передан во время выполнения. Очевидно, ошибки парсера нужно корректно передавать наверх (чтобы пользователю вывести, например). Как вы будете все это на кодах ошибок делать? С глобальной переменной для протаскивания информации об ошибке? Или через "полиморфные" коды ошибок?

Но все, что выше, не является причиной стреляться. Причиной стреляться является печалька в "аппликативном" контексте. Примером объяснить проще всего:
static SomeThing parseSomeThing(JSONObject jsonObject) {
  return new SomeThing(getLong(jsonObject, "id"), getString(jsonObject, "name"), getString(jsonObject, "description"));
}

json в данном случае пришел извне (веб-приложение). Очевидно, что в процессе выполнения метода могут возникнуть ошибки в трех местах. Которые в параметрах передаются. Поэтому для проверки кодов ошибок нужно все это развернуть в переменные и написать еще по несколько if'ов. Как-то многословно получается. На исключениях — запросто. Причем исключения — не завершение приложения. Всего-лишь отправка ответа "что-то у вас не так с форматом" (можно и поле указать). В другом контексте подобное могло быть на pull данных из удаленной системы, в этом случае один из вариантов поведения — подождать и повторить запрос. Пример почти реальный — подобный парсинг у меня на scala и одной inlnie-строкой в разборе запроса. На кодах ошибок (все с внешним миром) все было бы очень и очень длинно.
Re[61]: Слушайте, люди добрые, а правда, есть мат аппарат анализа кода с иключен
От: maxkar  
Дата: 06.03.13 15:24
Оценка: 1 (1)
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, maxkar, Вы писали:


M>>В случае с исключениями все так же прекрасно свернется в то же предусловие "профиль доступен" для той же функции read_profile.

E>Продемонстрируй, пожалуйста, как это происходит, на каком-нибудь примере.
E>Ещё лучше доказать, что всегда свернётся, конечно же, но это у тебя вряд ли получится

Это еще почему? Да пусть на том же парсере конфигов многострадальном.
//* Предусловие: istream not null, istream.read() не бросает BadConfigException, 
  * может бросать любое другое исключение
//* Постусловие: Либо
  *   1) В потоке удалось прочитать последовательность байт, которая не является
  *      началом ни одного валидного конфига => На выходе искюлчение И istream не закрыт
  *   2) Произошла ошибка чтения потока (istream.read выбросил исключение) => 
  *      на выходе то же исключеие И istream не закрыт
  *   3) В потоке удалось прочитать валидный конфиг, но за ним следуют еще данные =>
  *      Ошибка BadConfigException И istream не закрыт
  *   4) Последовательность байт соответствует валидному конфигу И за ними нет
  *      лишних данных => Функция завершилась успешно, вернула Config 
  *      соответствующий байтам И istream не закрыт И istream находится в конце потока
  *//
def parseConfig(istream : Inputtable) : Config {
  val header = readConfigHeader(istream);
  val body = readConfigBody(istream);
  val footer = readConfigFooter(istream);
  if (!istream.atEof())
    throw new BadConfigException("Trailing data");
  return new Config(header, body, footer).
}

Надеюсь, не проблема, что на scala? . Предикаты на read* подобны исходному (без п. 3). Семантика операционная, а не декларативная, так как мы работаем с реальным миром и ограничить его предикатами нельзя. Доказываем:
1. Первая строчка. Либо поток начинается тем, что не является началом заголовка конфига (тогда выход по условию 1). Либо мы не дочитали достаточно данных, чтобы сказать что-то определенное (и тогда у нас летит исключение по п. 2). Либо у нас успешный заголовок конфига в переменной header (и какие-то байты из потока прочитаны).
2. Вторая строчка. К этому моменту прочитан "успешный заголовок". Т.е. пока "прочитанная часть" может быть началом валидного конфига (так как она является заголовком какого-то конфига). Далее по пунктам аналогично. Либо некооректное body и мы обнаружили некорректный префикс, либо ошибка потока (и префикс к тому моменту корректен), либо есть результат body.
3. Третья строчка. Аналогично.
4. Проверка конца файла — это проверка п. 3 из предиката.

Вот как-то так. Очевидно, что не полностью формально (я не настолько извращенец, там слишком много букв). Так что можно проверять и формально, но далеко не всегда это оправдано. Да и совсем все формально записать далеко не всегда получится (особенно в операционной семантике при взаимодействии со внешним миром).

E>Я согласен, что если ты все исключения с нижнего уровня отловиш и оттранслируешь в какие-то исключения внешнего уровня, то всё свернётся, только это проще эффективнее и безопаснее сделать на том, что ты называешь "коды возврата", но можно и на исключениях, конечно, только не понтно в чём профит


Кода писать меньше. Выгодно если у нас много операций в "небезопасной" среде идет рядом друг с другом. Ну и "аппликативный контекст" в параллельной ветке (new SOmeObject(json.getLong(...), json.getString(...), json.getString(...));

E>А если речь идёт таки о том, что со всей иерархиии стека вызовов все абы что кидают, и снаружи это всё вылетает, то что-то как-то не верится, что "само свернётся", таки


Если что угодно и куда угодно летает — не свернется. Но и далеко не в любой валидной программе можно найти хоть что-нибудь, что можно доказать . А в тех программах, в которых хочется доказывать, обычно можно доказывать. Там обычно достаточно дисциплины присутствует, чтобы "частные доказательства" как раз работали.

M>>А вот как вы будете гарантировать ваше предусловие я плохо представляю.

E>Я это уже понял. Я только не понимаю, как ты вообще можешь при этом гарантировать хоть какие-то постусловия или инварианты кода... В том смысле что эти постусловия ничем от других не отличаются вообще-то
Я? В операционной семантике, конечно же. Ну никак без нее. Можно попробовать и формализовать (вроде функция над внешним миром, но там нужно ввести взаимодействия на этот мир, "тики" и т.п.). В общем, я предпочитаю доказывать на не слишком формальной базе, см. выше. Вполне нормально работает во всех случаях, где мне это было нужно.


M>>Только вот в 95% случаев мы в конце концов упираемся во взаимодействие со внешним миром (файлы/сеть/пользователь), оттуда и растет большинство исключений.

E>IMHO, это зависит от специфики задачи. Кроме того, код, который нкепосредственно с этим миром работает должен воспринимать отказ мира работать, как штатную ситуацию, и никаких исключений по этому поводу не кидать. Ну кроме там ситуаций, когда нарушины внутренние инварианты, ну там типа драйвер неадекватно работает или ещё что фатальное происходит. Но это не внешний мир чудит, а наш комп рушится просто.
E>А если внешний мир, то адекватно это всё как-то обрабатывать и возвращать клиентскому коду. А уж клиентский код снова обрабаьывает и возвращает выше и так пока на каком-то уровне мы не решим, что это типа всё, приплыли, значит. Ну и там уже можно и исключение кинуть...

От ситуации зависит. Вполне может оказаться, что проще всего внутри кинуть исключение, поймать его на границе библиотеки и вернуть ошибку "кодом ошибки". Все зависит от внутреннего устройства библиотеки/фрагмента кода. Если там может отказывать почти все, то проще исключением в разумных пределах.
Re[53]: Вот еще, или я, кажется, читать разучился
От: maxkar  
Дата: 06.03.13 15:52
Оценка: 3 (2) +1
Здравствуйте, alex_public, Вы писали:

_>Это мы же уже обсуждали вроде.) Если приложение в случае кривого конфига должно завершаться, то конечно используем исключения. А если оно просто подставляет дефолтные значения и работает дальше, то зачем они тут?

_>Ну и соответственно если только при одном виде ошибок с конфигом завершаемся, то тогда только этот вид ошибок и обрабатываем исключениями...

А проблема в том, что два ваших сценария на практике очень редко встречаются. Гораздо чаще встречаются другие варианты. Например, спросить пользователя о том, что же именно он хотел. В этом случае "локально" решение принять нельзя. Или вывести где-нибудь ошибку (в тех же ide окошко редактора может писать "файл такой-то недоступен" с возможностью refresh). Или, например, попробовать повторить попытку обмена с сервером (потому что пользователь решил, что загружать конфиг по http — это круто) и только после пятой попытки вернуть ошибку наверх. Ну не может ничего "парсер конфига" в этой ситуации логичного предпринять.

Характерные особенности:
1. Обработка ошибки нелокальна. В некоторых случаях (спросить пользователя) еще и способ обработки неизвестен.
2. Ошибка происходит где-то в глубине "опасного" контекста (т.е. в глубине стека вызовов). Поэтому вручную проверять на каждом уровне коды возврата — неудобно.

Сделать ли внешее API с исключениями или нет — другой вопрос. Внутри исключения точно удобны.

Ну и еще одно соображение. Даже если я пишу приложение, я отслеживаю "внутренние" компоненты, их связность друг с другом и т.п. Поэтому обычно предпочитаю откладывать конкретику обработки ошибок до нужного момента (и на более высокие уровни). Да и запрос пользователя откуда-то изнутри читалки конфигов будет не очень хорошо смотреться.

_>В C++ я не видел общепринятой стратегии именования. Там даже потомка общего по сути нет.

Это все не страшно. Главное, есть namespaces. Все-таки вероятность конфликта namespaces не такая большая (да и поправить их при желании, наверное, проще, чем коды ошибок по всему коду). Как-то же библиотеки и сейчас работают. Просто кодов ошибок гораздо меньше и все они в "глобальном" пространстве этих кодов.

_>Вообще то для таких вещей есть классические решения. Типа функций GetLastError/SetLastError с буфером где-нибудь в tls.

И для каждой библиотеки — свой буфер? А потом перебором по буферам искать, чей же это был код возврата и из какого буфера его выдирать? Это я про библиотеки в основном. Ну и запись в глобальный буфер (который элементарно затирается, если предыдущую ошибку проигнорировали) больше на решение от безисходности похоже. С исключениями "затереть" реальную ошибку сложнее. Можно, конечно, но "на ровном месте" сложнее.

M>>Четвертый. Грамотно спроектированные исключения образуют отношения master-detail.


_>Не очень понятно чем в этом исключения лучше. Разве что тем, что catch автоматом это отлавливает и не надо писать лишний if. Ну так и классы исключений тоже писать тогда не надо.

Ну да, не надо писать лишний if. А может, и не один. Под одним исключением может висеть достаточно много различных детей.

_>Да, я согласен что в случае прямого проброса ошибки на один или несколько уровней выше, исключения становятся удобнее кодов возврата. Но на мой взгляд при правильном проектирование потребность в такой схеме возникает всё же довольно редко.


Практически любой ввод/вывод с разбором форматов (там несколько уровней запросто получается). Сложные семантические преобразования моделей (компиляторы какие-нибудь). Можно рассматривать как вариант предыдущего случая (когда модель "читается" в несколько приемов). Некоторые случай runtime-композиции. Когда "как обрабатывать ошибку" зависит от того, какой граф действующих объектов построен и конкретику знает только "построитель графа". Тот же проброс ошибок чтения потока, когда поток у нас абстрактный (IStream, например).

Вот во всех этих случаях нужно смотреть, в каких пределах что удобнее (иногда и trySomething или default value бывают удобнее исключений). В основном на исключениях оказывается удобнее.

А вот во всех остальных случаях нелокального перехода действительно нет. Но я затрудняюсь привести примеры, когда там потребовались бы коды возврата . Т.е. большая часть остального кода (не связанная со вводом/выводом) должна просто работать. И там если что-то не так, нужно глушить всю программу (для чего, опять же, исключения подходят лучше). Может, конечно, я какие-то еще сценарии забыл, но в целом все вроде бы так выходит.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.