Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: 1 2 [Все]   Вниз
  Печать  
Автор Тема: Обработка исключений на языке C  (Прочитано 87794 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« : 02-02-2011 09:15 » 

Обсуждение статьи: «Обработка исключений на языке C»

* TriangleArea.zip (8.69 Кб - загружено 1350 раз.)
« Последнее редактирование: 27-07-2011 19:15 от RXL » Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #1 : 02-02-2011 12:33 » 

Статья готова.

Обсуждаем, публикуем.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #2 : 02-02-2011 13:16 » 

Интересная статья. Не видел до этого ни одного логичного применения longjmp.

Добавлено через 1 день, 4 часа, 38 минут и 17 секунд:
Интересно, каковы лимиты вложенности блоков таких try-catch?
« Последнее редактирование: 03-02-2011 18:12 от RXL » Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 03-02-2011 18:38 » 

Если я правильно понял, предложенное решение ловит только те исключительные ситуации, порождение которых прописано программистом. В случаях же ошибок типа деления на ноль данный механизм не сработает.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #4 : 03-02-2011 19:25 » 

Интересно, каковы лимиты вложенности блоков таких try-catch?

Документацией не лимитируется. Похоже, в разумных пределах вложенность не ограничена.

Добавлено через 4 минуты и 36 секунд:
Если я правильно понял, предложенное решение ловит только те исключительные ситуации, порождение которых прописано программистом. В случаях же ошибок типа деления на ноль данный механизм не сработает.

Конечно. Штатного-то механизма обработки исключений в C нет, здесь приведена лишь его довольно искусная имитация при помощи longjmp и макросов. Поэтому перехватывать ошибки runtime при помощи CException не получится, равно как и ошибки подпрограмм стандартной библиотеки. Ну и, само собой, не удастся выбрасывать ничего более информативного, чем беззнаковое целое.
« Последнее редактирование: 03-02-2011 19:30 от Dale » Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #5 : 04-02-2011 07:28 » 

Если ориентироваться именно на микроконтроллеры, то не помню, чтобы там было деление (с системой команд AVR знаком давно, поверхностно и все уже забыл, а в 8051 и PIC деления нет как класса, в AD21xx исключения не полагалось, а с другими м/к не сталкивался). Исходя из этого предположу, что нужно внедрить в код функции деления соотв. Throw.
При отсутствии защиты памяти (обычное дело в м/к и мобильных процессорах) других ошибок не будет.
Не забываем, что ресурсов мало - тут не винда с дровами от неведомого индусского производителя. Т.е. код максимально контролируется разработчиком.
Вывод: предложенный в статье механизм покрывает все потребности в обработке исключений.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #6 : 04-02-2011 07:32 » 

Цитата
а в 8051 и ... деления нет как класса,
ай-ай-ай Улыбаюсь Верни слова взад.

Команда "div AB" выполняет деление содержимого аккумулятора на содержимое регистра "B". Частное помещается в А, остаток - в В
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 04-02-2011 07:41 » 

Что-то с sourceforge? У меня не получается посмотреть исходники на сайте проекта - при попытке открыть ветку исходников страничка сваливается куда-то налево и ничего не кажет. Было любопытно посмотреть, как реализованы макросы. В статье на это, к сожалению особых намёков нет, а это ведь самое интересное Улыбаюсь

Честно говоря, longjmp никогда не пользовался - как-то в голову не приходило даже, совмещать C и обработку исключений: в моём представлении, код на C должен быть или дубово надёжным, или не быть Улыбаюсь Правда, я на C по-крупному только видеокодек и обвязку к нему пилил, там ничего такого и не требовалось.
Записан
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #8 : 04-02-2011 07:44 » 

Каюсь, Слав. 13 лет прошло. Да и не использовал я эту команду. Сейчас вспомнил - 4 машинных цикла выполнялась.

Добавлено через 2 минуты и 22 секунды:
Вад, прикрепил к посту.

* cexception_1_2.zip (418.06 Кб - загружено 633 раз.)
« Последнее редактирование: 04-02-2011 07:47 от RXL » Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #9 : 04-02-2011 08:02 » 

Не понял фразу:
...как-то в голову не приходило даже, совмещать C и обработку исключений: в моём представлении, код на C должен быть или дубово надёжным, или не быть Улыбаюсь

Обработка исключений для того и введена, чтобы гарантированно реагировать на ошибочные ситуации. Наоборот, именно еe отсутствие очень затрудняет написание надежного кода на C (или же делает его чрезмерно громоздким, что и продемонстрировано в статье).

Было любопытно посмотреть, как реализованы макросы. В статье на это, к сожалению особых намёков нет, а это ведь самое интересное Улыбаюсь
Вад, прикрепил к посту.

К статье приложен рабочий демо-проект, там включено все необходимое, включая исходники CException.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 04-02-2011 10:19 » 

Не понял фразу:
...как-то в голову не приходило даже, совмещать C и обработку исключений: в моём представлении, код на C должен быть или дубово надёжным, или не быть Улыбаюсь

Обработка исключений для того и введена, чтобы гарантированно реагировать на ошибочные ситуации. Наоборот, именно еe отсутствие очень затрудняет написание надежного кода на C (или же делает его чрезмерно громоздким, что и продемонстрировано в статье).
Считаю последнюю фразу преувеличением Улыбаюсь В статье приведён не слишком удачный пример, когда проводится много проверок (к тому же, будто бы не к месту), которые к тому же непонятны -- в том смысле, что код элементарно плохо читается из-за всех этих проверок.  Почему a <= fabs(b-c), а не c <= fabs(a - b)?
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #11 : 04-02-2011 11:29 » 

Почему a <= fabs(b-c), а не c <= fabs(a - b)?

Не любые три отрезка могут образовать треугольник. Если взять два отрезка произвольной длины, то третий должен быть не больше их суммы и одновременно не меньше разности. Если угодно, можно взять условие c <= fabs(a - b), но тогда одновременно нужно проверить c >= a + b. Я просто не стал загромождать статью такими элементарными вещами, поскольку тема все-таки исключения в C, а не введение в геометрию.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 04-02-2011 12:07 » 

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

Вот если вынести всё в предикат isValidTriangle -- то я бы сразу понял: вот, тут мы проверяем, правильные ли нам стороны подсунули, может ли это быть треугольником. Но когда есть такая проверка - вроде бы, нет нужды уже городить что-то насчёт проверки площади - просто возвращать -1 и задокументировать и в errno записать ошибку INVALID_ARG, раз нам такие плохие данные дают. А то и вовсе считать то, что дают, а предикат пусть клиент сам проверяет.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #13 : 04-02-2011 12:40 » 

Так речь ведь о том и идет, чтобы забыть про -1 и errno. У нас ведь функция может участвовать в более сложном выражении, причем вызываться несколько раз. Можно, конечно, завести несколько временных переменных для сохранения результатов вызовов, проверять их значения на код ошибки, потом генерировать новый код и передавать вызывающей функции и т.д., но вариант с исключениями импонирует мне куда больше.

Добавлено через 14 минут и 40 секунд:
Вот простой пример.

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

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

А вот это:
А то и вовсе считать то, что дают, а предикат пусть клиент сам проверяет.
плохо вяжется с
в моём представлении, код на C должен быть или дубово надёжным, или не быть Улыбаюсь

Это скорее подход в стиле: "сами нарвались, так получите же по полной".
« Последнее редактирование: 04-02-2011 12:56 от Dale » Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #14 : 04-02-2011 14:36 » 

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

Это скорее подход в стиле: "сами нарвались, так получите же по полной".
Это подход "что дали, с тем и посчитали" - нормальный подход, в такой задаче ничем не хуже прочих. С тем же успехом подход с CException можно назвать "сами нарвались на исключение - сами и ловите".
Это лишь вопрос контракта: кто должен отвечать за корректные значения, подпрограмма или клиент. И каким образом клиент будет справляться с некорректным поведением.

Если вы видели java-код с развесистым набором catch-ей, то не понимаю, как можно находить этот подход "лучше" и "красивее", чем обработка errno и возвращаемых значений. Исключения нужно очень рано перехватывать, иначе что число вариантов исключений, что число возможных некорректных поведений программы, будет расти, и неважно, чего будет много: веток с проверкой, какой код ошибки в errno, или веток catch, проверяющих, какое, всё-таки, исключение из возможных 5 было сгенерировано.

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

ru
Offline Offline
Пол: Мужской

« Ответ #15 : 04-02-2011 16:27 » 

Это, конечно, вопрос вкуса. Но лично мне нравится раздельно описывать основной и альтернативные сценарии какой-то деятельности. Единственно, что при помощи данного механизма, который требует написания if и вызова throw, делать такое разделение не очень удобно. Если только каждую проблемную функцию оборачивать в другую функцию, которая делает нужные проверки и генерирует соответствующие исключения. Однако, количества кода это не сокращает.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #16 : 04-02-2011 17:55 » 

Ещё один минус - работа с памятью. Поскольку C - язык не так чтоб совсем объектный, подчищать динамически выделенные ресурсы после throw будет просто некому, и в таких случаях будут течи. Или будет неаппетитная очистка всех ресурсов перед каждым throw, что несколько испортит раздельные сценарии для Димки.

Теоретически, сюда можно прикрутить какой-нибудь Symbian-подобный механизм со специальным порядком инициализации на куче, но это тоже будет костыль.
« Последнее редактирование: 04-02-2011 17:59 от Вад » Записан
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #17 : 04-02-2011 18:46 » 

Вад, этот вопрос я тоже хотел задать, но у меня БОЛЬШАЯ проблема со знанием С.
поэтому библиотеку посмотрел вскольз.
например в Паскале есть такой оператор mark (если память не изменяет), который запоминает состояние кучи на момент вызова.) а затем можно вернуть все назад, вызвав соответствующий антиоператор, который все это освободит.
И мне показалось, что это реализовано в библиотеке CException.
Но при работе с микроконтроллерами работа с кучей это ВРЕДНО - памяти там для этого нет.
Я не говорю о монстрах с 64к на борту, а всего лишь 16К, ну или около того.
Offtopic:

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

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #18 : 04-02-2011 19:31 » 

Пример неудачный. Если есть корректная призма - для чего проверять корректность треугольного основания?

Если бы данные всегда были гарантированно корректными, вопрос дефектов ПО вообще утратил бы актуальность. Но мы все еще живем в реальном мире. Где их брать, эти идеальные призмы?

А если нет - чего ради нам считать площадь этого основания, если призма уже некорректная?

У нас в програмном мире нет никаких призм. Есть набор параметров, полученных от клиента, и у нас нет оснований слепо им доверять. Нам нужно написать код, который ведет себя предсказуемо в любых условиях. Не забываем, что речь идет о встроенном софте, а не о студенческой лабораторной.

Это скорее подход в стиле: "сами нарвались, так получите же по полной".
Это подход "что дали, с тем и посчитали" - нормальный подход, в такой задаче ничем не хуже прочих. С тем же успехом подход с CException можно назвать "сами нарвались на исключение - сами и ловите".
Это лишь вопрос контракта: кто должен отвечать за корректные значения, подпрограмма или клиент. И каким образом клиент будет справляться с некорректным поведением.

Не совсем так. При опросе нерадивый клиент может не проверить код ошибки и продолжить выполнение с некорректным результатом. Исключение принудительно прервет поток выполнения.

Если вы видели java-код с развесистым набором catch-ей, то не понимаю, как можно находить этот подход "лучше" и "красивее", чем обработка errno и возвращаемых значений.

Скажем так, я не фанат Java, мне ближе C#, но в данном случае это большой роли не играет, наверное.

Исключения нужно очень рано перехватывать

Кому нужно и зачем? Я предпочитаю перехватывать их в том месте, где их можно осмысленно обработать, а не просто передать код завершения дальше по цепочке вызовов в надежде, что им кто-то заинтересуется. И это совсем не обязательно "очень рано".

иначе что число вариантов исключений, что число возможных некорректных поведений программы, будет расти, и неважно, чего будет много: веток с проверкой, какой код ошибки в errno, или веток catch, проверяющих, какое, всё-таки, исключение из возможных 5 было сгенерировано.

Опять же совсем не факт. В отличие от CException, в Java и C# исключение - это объект, производный от базового класса исключений, и если построить дерево подклассов исключений с умом, можно единообразно обрабатывать целые семейства исключений, а не охотиться за каждым по отдельности.

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

Примерно то же можно при желании сказать об объектно-ориентированном программировании по отношению к структурному. А также о структурном программировании по отношению к GOTO, пожалуй. Можно писать отвратительные объектные программы и отвратительные структурные. Сам по себе инструмент совершенно ничего не значит, если его использование не следует определенной дисциплине.

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

Мне кажется, что можно провести такую параллель с миром оборудования: исключение - это прерывание, а проверка кода ошибки - это опрос (поллинг). Какая система ведет себя логичнее: та, которая реагирует на события по прерываниям, или та, которая постоянно проверяет, не случилось ли чего?
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #19 : 04-02-2011 19:36 » 

Цитата
Мне кажется, что можно провести такую параллель с миром оборудования: исключение - это прерывание, а проверка кода ошибки - это опрос (поллинг). Какая система ведет себя логичнее: та, которая реагирует на события по прерываниям, или та, которая постоянно проверяет, не случилось ли чего?
Сбалансированная. С точки зрения работы приложения(девайса)
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #20 : 04-02-2011 19:37 » 

например в Паскале есть такой оператор mark (если память не изменяет), который запоминает состояние кучи на момент вызова.) а затем можно вернуть все назад, вызвав соответствующий антиоператор, который все это освободит.
И мне показалось, что это реализовано в библиотеке CException.
Не знаю точно, как реализована куча в Паскале, но явно на более высоком уровне, чем в С. Насколько я понимаю документацию на setjmp/longjmp, ничего такого оно не умеет, сохраняя лишь позицию указателя на стек, точку в коде и ещё некоторые регистры. Закрывать дескрипторы и освобождать память это не поможет.

Цитата
Но при работе с микроконтроллерами работа с кучей это ВРЕДНО - памяти там для этого нет.
Я не говорю о монстрах с 64к на борту, а всего лишь 16К, ну или около того.
Нет, ну для микроконтролеров я ничего и не говорю. Там, может, и не надо.
Хотя, тут можно и про те же дескрипторы вспомнить или взять другие случаи, когда надо будет сброс состояния какого-то делать...
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #21 : 04-02-2011 19:50 » 

например в Паскале есть такой оператор mark (если память не изменяет), который запоминает состояние кучи на момент вызова.) а затем можно вернуть все назад, вызвав соответствующий антиоператор, который все это освободит.
И мне показалось, что это реализовано в библиотеке CException.

Да, был такой в младших версиях Pascal. Там куча распределялась практически как стек, только навстречу ему - снизу вверх. Оператор mark попросту запоминал текущую верхушку кучи, а парный к нему release восстанавливал ее обратно, уничтожая все, что накопилось в куче между вызовами mark и  release. Для упрощенных компиляторов это было проще, чем следить за кучей.

В  CException ничего подобного нет, куча здесь не задействована. Используется другой механизм setjmp/longjmp, который относится к потоку управления, а не к данным.

Но при работе с микроконтроллерами работа с кучей это ВРЕДНО - памяти там для этого нет.

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

Я не говорю о монстрах с 64к на борту, а всего лишь 16К, ну или около того.

Это еще смотря какое семейство МК. У AVR, например, 16К - это как раз монстры, старшие модели. А у середнячков и килобайт не всегда наскребется. И у PIC вроде бы не лучше с этим.

Добавлено через 7 минут:
Ещё один минус - работа с памятью. Поскольку C - язык не так чтоб совсем объектный, подчищать динамически выделенные ресурсы после throw будет просто некому, и в таких случаях будут течи. Или будет неаппетитная очистка всех ресурсов перед каждым throw, что несколько испортит раздельные сценарии для Димки.

Можно следовать такому паттерну:
  • Создаем динамический объект.
  • Работаем с ним в защищенном блоке (Try).
  • Обрабатываем возможные ошибки (Catch).
  • Освобождаем динамический объект.

Вроде бы нет лазеек для утечки. Кто создает, тот и обязуется за собой подчистить кучу.

Ну и см. мой предыдущий ответ Sla: кучей не злоупотребляем без крайней на то необходимости.
« Последнее редактирование: 04-02-2011 19:57 от Dale » Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #22 : 04-02-2011 19:59 » 

У нас в програмном мире нет никаких призм. Есть набор параметров, полученных от клиента, и у нас нет оснований слепо им доверять. Нам нужно написать код, который ведет себя предсказуемо в любых условиях. Не забываем, что речь идет о встроенном софте, а не о студенческой лабораторной.

Я лишь поставил под сомнение, на каком уровне модели должна выполняться проверка входных данных. Речь ведь идёт не о read-only коде.

При опросе нерадивый клиент может не проверить код ошибки и продолжить выполнение с некорректным результатом. Исключение принудительно прервет поток выполнения.
и куда уйдёт longjmp, если забыли сделать setjmp, а ошибка произошла, но не была протестирована? Что помешает устройству сломаться уже в руках покупателя?

Цитата
Механизм исключений позволяет решить на самом деле очень важную задачу - произвести обработку ошибки в той точке программы, где это наиболее уместно, а не в той, где она возникла. Если его использовать как-то иначе, например, пытаясь перехватить исключение "очень рано", толку от них действительно будет немного. Тут я полностью согласен.
Я полностью согласен. И механизм errno тоже позволяет это делать. И баланс между этими двумя соблюдать всё равно придётся, иначе будет Java/C#-быдлокод с кучей catch-ей на верхнем уровне, а при неумении препроцессора следить за "утекающими" из функций исключениями -- ещё и морока по поддержанию актуальной документации на все функции, способные эти исключения генерировать.

Механизм исключений - штука удобная для очень своих целей: когда ошибка может произойти на уровне, некомпетентном для её обработки. Но эта ошибка должна быть поистине фатальной и сложно- или непредсказуемой до погружения на оный уровень (исходя из внутреннего состояния программы). Ошибка ввода может и фатальна, но проста, предсказуема и вполне себе тестируема. Как и деление на ноль, например. Поэтому я не согласен с примером, а не потому, что мне вся идея целиком не нравится.

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

Внимания такая техника требует, на мой взгляд, не меньше. На моей практике, багов с обработкой исключений в командных проектах было не меньше, чем с игнорированием или путаницей в кодах возврата. И баги были подчас куда более серьёзные.
« Последнее редактирование: 04-02-2011 20:05 от Вад » Записан
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #23 : 04-02-2011 20:04 » 

ага..., давно в руки шашечек не брал (даташитов не читал), виноват - про память указанную мной на борту МК эту супермонстр. 

А вот исключения... это как раз и есть безопасный способ проверки входных данных.

Эта статься не рекомендация использования исключений, эта статья показывает как можно использовать исключения.

Честно... не люблю систему исключений... Она затрудняет (мне) чтение и понимание кода
Мне гораздо понятней в работе МК линейный код.
Но такие конструкции типа longjump использовать приходилось при вызове критических функций обработки данных.
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #24 : 04-02-2011 20:09 » 

Честно... не люблю систему исключений... Она затрудняет (мне) чтение и понимание кода
Мне гораздо понятней в работе МК линейный код.
Вот я тоже сегодня, когда пытался Димке объяснить суть моих претензий к идее "исключения на C", прежде всего пытался выразить, что я чувствую, когда подумаю, что строго императивный код вдруг посредине набора инструкций могут прервать и выдернуть на три уровня вверх в точку обработки. Интуитивную тревогу, вот что Улыбаюсь

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

Offtopic:
ЗЫ. В "Мотороле" мне расказывали легенду про чувака, у которого на C-шных макросах была построена система шаблонов (templates) а-ля C++, не говоря уже про ООП. Так что есть ещё горизонты Улыбаюсь
Поставлю в угол.
« Последнее редактирование: 04-02-2011 20:13 от Вад » Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #25 : 04-02-2011 20:23 » 

Ошибка ввода может и фатальна, но проста, предсказуема и вполне себе тестируема. Как и деление на ноль, например. Поэтому я не согласен с примером, а не потому, что мне вся идея целиком не нравится.

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

Идея - отличная. Но я не согласен с тем, что она позволяет писать более простой и надёжный код.

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

Добавлено через 4 минуты и 54 секунды:
Честно... не люблю систему исключений... Она затрудняет (мне) чтение и понимание кода
Мне гораздо понятней в работе МК линейный код.

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

Добавлено через 17 минут и 13 секунд:
Offtopic:
ЗЫ. В "Мотороле" мне расказывали легенду про чувака, у которого на C-шных макросах была построена система шаблонов (templates) а-ля C++, не говоря уже про ООП. Так что есть ещё горизонты Улыбаюсь
Поставлю в угол.

Хоть у нас тут и не Motorola, но дальнейшее предполагаемое развитие темы - как раз ООП на C. Потому что firmware подразумевает качественный софт, а качество без тестирования как-то трудно себе представить. Ну а дальше все по накатанной - какое же тестирование без всяких моков/фейков/стабов и т.д. Да и нормальный рефакторинг без объектной модели тоже немыслим. Так что это не оффтоп, а вполне реальная перспектива.
« Последнее редактирование: 04-02-2011 20:45 от Dale » Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #26 : 04-02-2011 20:48 » 

Dale, при написании кода, я не знаю о прерываниях ничего, за исключением критических моментов, это мы проходили в предыдущей статье.
И я даже могу и не знать о существовании таковых.
Прерывания они как демоны, все знают что есть, но никто не видел. Улыбаюсь Улыбаюсь Улыбаюсь
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #27 : 04-02-2011 20:54 » 

А как же без прерываний? На опросе? В этих поллингах недолго и увязнуть с головой...
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #28 : 04-02-2011 21:02 » 

Ты не понял.
Прерывания существуют. Но при описании основного цикла работы контроллера я о них ничего не знаю, за исключением того что данные забираю, вон с той полочки, и при этом я должен эту полочку закрыть амбразурой, чтоб никто-никто туда в этот момент не влез.
Больше я о прерываниях ничего не знаю. И знать не хочу. Задача прерываний - принять данные, в случае критических секций принять решение о прекращении основного цикла, уйти на обработку "аварии" или, но никоим образом не вмешиваться в код выполнения основного цикла.
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #29 : 04-02-2011 21:18 » 

Аналогично исключения позволяют абстрагироваться от ошибок при написании основной программы. Если где-то что-то случилось - это обрабатывается в другом месте и к решаемой задаче непосредственного каательства не имеет.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #30 : 04-02-2011 22:51 » 

Цитата: Вад
Вот я тоже сегодня, когда пытался Димке объяснить суть моих претензий к идее "исключения на C", прежде всего пытался выразить, что я чувствую, когда подумаю, что строго императивный код вдруг посредине набора инструкций могут прервать и выдернуть на три уровня вверх в точку обработки. Интуитивную тревогу, вот что
Я с BASIC начинал, поэтому врождённого отвращения к GOTO у меня нет. Но я знаю, когда GOTO использовать не надо ни при каких обстоятельствах Улыбаюсь Когда при помощи GOTO перемешиваются два и более алгоритма, два и более потока управления, если они не разделены на уровне исходного кода. При наличии разделения потоков любым способом GOTO - это не то, что нормальный, а единственный инструмент при работе с одним процессором.

По этой причине, кстати, меня до того заинтересовали сопрограммы, что я и примеров понаписал, и даже лекцию студентам прочитал, и практические занятия провёл.

Сегодня за сыром ты мне говорил, что в случае ошибок для тебя нормально делать return из функции, и что функция с десятком return - обычное дело. Сие есть тот же самый GOTO, только в профиль Улыбаюсь Поэтому с этой точки зрения я не вижу причин к ломанию копий - и throw, и return (а также break, continue и прочие фичи C/C++) не являются "строго императивным кодом" (точнее, строго структурным).

Тем не менее, одно у тебя не вызывает "интуитивной тревоги", а другое вызывает. И здесь я начинаю подозревать, что это вопрос эстетический, вкуса, привычек; рациональных оснований для тревоги я тут не вижу никаких (а также не чувствую подсознательно).

Возможно дело в том, что ты воспринимаешь написанный императивный код как один поток инструкций. Я (и Dale, похоже, тоже) воспрнимаю это как множество потоков. Именно поэтому здесь часто упоминаются слова "прерывания", "сценарии" и иже с ними. В данном случае GOTO работает как переключатель этих потоков. И в случае прерывания, и в случае исключения, и в случае синхронизации параллельных потоков возникает задача переключить процессор с исполнения одного потока (который в первом и последнем случае временно приостанавливается, а во втором - останавливается навсегда) на какой-то другой альтернативный поток.

В чём для меня прелесть try-catch - это в возможности каждый поток записать в отдельной секции и тем самым визуально разделить их в коде. Это облегчает работу с кодом, но, конечно, чтение кода перестаёт быть похожим на чтение романа и становится похожим на работу со справочником. Возможно, это у меня от плотной работы с GUI пошло - там нет единого потока и единого алгоритма, там сплошные обработчики, из которых нужно собрать работающую систему. Но это, действительно, другой способ мышления программиста, другой способ восприятия программ. А в случае параллельного программирования я вообще не знаю иного способа - ведь в общем случае при реальном параллелизме любая инструкция одного потока может оказаться одновременной с любой инструкцией другого потока. Конечно, это вызывало "интуитивную тревогу" ещё у Дейкстры, но... се ля ви.

Цитата: Dale
Кому нужно и зачем? Я предпочитаю перехватывать их в том месте, где их можно осмысленно обработать, а не просто передать код завершения дальше по цепочке вызовов в надежде, что им кто-то заинтересуется. И это совсем не обязательно "очень рано".
Это несколько другая тема, но тут я согласен с Вадом. Исключения не должны уходить с уровня подсистемы на уровень (над)системы. Если это происходит, возникает явление, которое называется "дырявая абстракция". Если подсистема инкапсулирует в себе особенности своей реализации, то никакой код ошибки, никакое внутреннее исключение, имеющее смысл только в рамках реализации, и ни о чём не говорящее на уровне интерфейса, не должно покинуть подсистему. В этом случае делается перехват всех исключений реализации, а вместо них порождается новое исключение, соответствующее интерфейсу.

Поскольку лично я стараюсь структурировать системы таким образом, чтобы всякая система состояла из малого (обозримого) количества подсистем (и потому она всегда оказывается небольшой на своём уровне абстракции), постольку в рамках вышеизложенных соображений об инкапсуляции перехват исключений чаще всего происходит "как можно раньше".
« Последнее редактирование: 04-02-2011 22:53 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #31 : 05-02-2011 07:09 » 

Исключения не должны уходить с уровня подсистемы на уровень (над)системы. Если это происходит, возникает явление, которое называется "дырявая абстракция". Если подсистема инкапсулирует в себе особенности своей реализации, то никакой код ошибки, никакое внутреннее исключение, имеющее смысл только в рамках реализации, и ни о чём не говорящее на уровне интерфейса, не должно покинуть подсистему.

Де-факто иключения давно стали частью интерфейса. К сожалению, в формальной грамматике того же C# это не нашло отражения (нет никакого аналога инструкции throws, которая предупреждала бы, что метод может выбросить такие-то исключения), но в MSDN это нормальная практика: в документации любого метода наравне с описаниями входных и выходных параметров обязательно присутствует раздел с перечнем всех исключений, которые можно ожидать от него. Поэтому никаких дыр в абстракциях исключения не проделывают при разумном использовании (ну а при неразумном практически любой инструмент лишь калечит горе-мастера), фактически это лишь расширение поведения метода/функции/подпрограммы/....

Возьмем такой пример:

Код:
repeat
  write("Введите значения a, b, c: ")
  read(a, b, c)

  try
  {
    вычисления с глубиной вложенности вызовов > 1, в которых используется наш треугольник
  }
  catch(e)
  {
    if (e - одна из ошибок входных параметров) then
      установить флаг ошибки входных данных
    if (другая причина исключения)
      ...
  }

  if (флаг ошибки входных данных установлен)
  {
    write("Ошибка исходных данных")
    по-хорошему не мешает уточнить ее причину (нулевая или отрицательная сторона, треугольник не строится и т.д., сделав сообщение максимально информативным
  }
until (флаг ошибки входных данных сброшен)

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

Разумеется, исключения должны быть информативными. Получить в главной программе нечто вроде "division by zero" или "invalid handle 0xBC0018A4", когда в стеке 12 уровней вложенности вызовов, невеликое счастье. Но повторюсь: ни один инструмент не способен превратить дурака в гения, и исключения тут - не исключение (в прямом смысле). Понимающий получает в свой арсенал еще одно полезное средство, непонимающий - еще одно приключение на свой многострадальный зад.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #32 : 05-02-2011 09:01 » 

Сегодня за сыром ты мне говорил, что в случае ошибок для тебя нормально делать return из функции, и что функция с десятком return - обычное дело. Сие есть тот же самый GOTO, только в профиль Улыбаюсь Поэтому с этой точки зрения я не вижу причин к ломанию копий - и throw, и return (а также break, continue и прочие фичи C/C++) не являются "строго императивным кодом" (точнее, строго структурным).

Тем не менее, одно у тебя не вызывает "интуитивной тревоги", а другое вызывает. И здесь я начинаю подозревать, что это вопрос эстетический, вкуса, привычек; рациональных оснований для тревоги я тут не вижу никаких (а также не чувствую подсознательно).

Возможно дело в том, что ты воспринимаешь написанный императивный код как один поток инструкций. Я (и Dale, похоже, тоже) воспрнимаю это как множество потоков.

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

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

То есть, все эти формы условного перехода контролируемы, пока они лежат внутри моего логического блока и не пытаются делать что-то более сложное. А именно, то, что ты называешь выходом с уровня подсистемы на уровень надсистемы: изменение не только своего потока выполняемых команд, но и оного потока для надсистемы. И вот когда подсистема начинает влиять на то, как себя надсистема ведёт, в языке, которому обычно это не свойственно, это меня тревожит. Наверное, просто потому, что надо перестраивать мышление и забыть о простоте программирования на C Улыбаюсь Вот только, кажется, в том-то смысл этого языка для меня и был, что не нужно было думать, так сказать, о прерываниях.
« Последнее редактирование: 05-02-2011 09:03 от Вад » Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #33 : 05-02-2011 09:13 » 

Dale, ты не понял.

Если на одном уровне абстракции программа пытается, например, использовать курсы валют для каких-то расчётов, то вся подсистема, отвечающая за курсы валют, должна иметь соглашение о порядке предоставления информации. Например, всегда выдавать самый свежий известный системе курс, а если такогового нет, то брать 1.
Код: (C)
void calc_cost(int goods_count, struct amount price, struct amount *cost) {
  if(cost == NULL) {
    return;
  }
  cost->currency = USD;
  cost->value = goods_count * price.value * get_exchange_rate(price.currency, cost.currency);
}
Тут потенциальной ошибкой может быть только переполнение при умножении, чего (по здравому размышлению) в 99.(9)% случаев не ожидается, а если и просиходит, то это не влечёт нарушение работы функции - проверки избыточны.

По соглашению нужно получить курс валюты из справочника, а если её там нету, то вернуть 1. Предположим, что справочник возвращает либо курс, либо бросает исключения "неизвестная валюта" и "нет данных":
Код: (C)
decimal get_exchange_rate(currency currency_1, currency currency_2) {
  struct search_conditions conditions;
  init_search_conditions(&conditions);
  decimal exchage_rate;
  try {
    conditions.seek = SC_SEEK_LAST;
    exchage_rate = find_exchange_rate(currency_1, currency_2, conditions);
  }
  catch(struct exception *error) {
    exchage_rate = 1.0;
  }
  return exchage_rate;
}
Здесь нам не важна внутренняя причина, по которой find_exchange_rate не смог выполнить запрос, ибо на любую причину у нас только одна реакция. Исключения выше не идут. Здесь возврат 1.0 на любые ошибки - то, о чём говорил Вад про кодеров на Java. Это подозрительное решение, скверно влияющее на предсказуемое поведение системы с точки зрения пользователя. Пользователь никогда не знает, получит ли он правильный результат расчёта или неправильный из-за внутренних ошибок.

Запросы в справочнике обрабатываются следующим образом: если хотя бы одной из указанных валют нет, бросаем исключение "неизвестная валюта"; если валюты есть, то получаем данные по условию выбора, иначе бросаем исключение "нет данных"; если условием выбора была последняя запись, то проверяем её свежесть; если получена более старая запись, чем требуется, пробуем получить с удалённого сервера новые данные.
Код: (C)
decimal find_exchange_rate(currency currency_1, currency currency_2, struct search_conditions conditions) {
  struct exception error;
  struct exchange_rate_record dictionary_record;
  time_t current_time;
  double days_old;
  if(!exists_currency_in_exchange_rate_dictionary(currency_1) ||
     !exists_currency_in_exchange_rate_dictionary(currency_2)) {
    error.id = ERR_UNKNOWN_CURRENCY;
    throw error;
  }
  try {
    select_exchange_rate(currency_1, currency_2, conditions, &dictionary_record);
    time(&current_time);
    days_old = difftime(current_time, dictionary_record.date) / 24 * 60 * 60;
    if(days_old > 1) {
      update_exchange_rate();
      select_exchange_rate(currency_1, currency_2, conditions, &dictionary_record);
    }
  }
  catch(struct exception *error) {
    error.id = ERR_NO_DATA;
    throw error;
  }
  return dictionary_record.exchange_rate;
}
Здесь используются две вспомогательные функции select_exchange_rate и update_exchange_rate. Как они реализованы, совершенно неинтересно ни на этом уровне, ни на вышележащем. Если select_exchange_rate работает с файлом, она может бросать исключения, связанные с открытием, чтением файла, отсутствием прав доступа; если select_exchange_rate работает с базой данных, она может бросать исключения, связанные с невозможностью подключения, отказом в авторизации, внутренними сбоями в работе СУБД; если update_exchange_rate получает данные от внешнего веб-сервиса в интернете, она может бросать целый ворох исключений, связанных с работой в сети, отказами веб-сервиса, а также ошибками в формате получаемых данных.

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

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

Если хорошо подумать, то поскольку функция select_exchange_rate может быть реализована разными способами (работа с файлом, работа с СУБД), то даже она не должна бросать исключения, связанные с обработкой файла или взаимодействием с СУБД - они должны быть инкапсулированы в ней и обобщены до каких-то других исключений. Это и влечёт стратегию перехвата исключений как можно ближе к месту их возникновения. В пределе эта стратегия приводит к тому, что выбор между исключениями и возвратом кода ошибки становится безразличным - никакое исключение не проходит выше 1 уровня вызовов функции по стеку вызовов.
« Последнее редактирование: 05-02-2011 09:25 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #34 : 05-02-2011 11:16 » 

Dale, ты не понял.
...
Речь идёт о том, что если это не перехватить, не упаковать в своё собственное исключение "нет данных", то всю эту "бороду" исключений и связанных с ними catch секций придётся вытаскивать на более высокий уровень, на котором ничего этого не должно быть видно.

Что же тут мной не понято? О том и речь ведется:

Разумеется, исключения должны быть информативными. Получить в главной программе нечто вроде "division by zero" или "invalid handle 0xBC0018A4", когда в стеке 12 уровней вложенности вызовов, невеликое счастье.

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

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

Я бы полностью согласился, если бы "ближе" и "уровень" рассматривались с точки зрения уровней абстракции. Когда же речь идет о близости уровней в физическом плане (глубина стека вызовов), эти доводы становятся менее убедительны. Например, каждое применение рефакторинга "Извлечение метода" добавляет один физический уровень вызова, оставляя абстракцию на том же уровне. Другой пример - сложное математическое выражение, когда вычисляется функция от функции от функции... Глубина стека вызовов большая, но это в данном случае ничего не значит.

Скажем, если мы вычисляем ln(sqrt(x)), функции вычисления логарифма вовсе не обязательно обрабатывать исключение, возникшее при вычислении корня, хотя они и соседствуют в стеке.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #35 : 05-02-2011 11:33 » 

Цитата: Dale
Например, каждое применение рефакторинга "Извлечение метода" добавляет один физический уровень вызова, оставляя абстракцию на том же уровне.
Вот это странно. Какой смысл выделять часть кода в отдельный метод, если при этом не абстрагироваться от его реализации?
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #36 : 05-02-2011 11:41 » 

Вот это странно. Какой смысл выделять часть кода в отдельный метод, если при этом не абстрагироваться от его реализации?

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

Добавлено через 28 минут и 35 секунд:
Вот еще пример ситуации, когда глубина стека вызова никак не связана с уровнями абстракции.

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

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

  • добавляет очередную точку к массиву данных;
  • строит график при помощи соответствующего модуля, получая метафайл графических данных;
  • разворачивает изображение на 90 градусов против часовой стрелки (особенность дисплея, повернутого в ландшафтную ориентацию);
  • масштабирует изображение по осям X и Y, чтобы оно вписалось в рзмеры окошка;
  • осуществляет параллельный перенос изображения, отображая на выделенную для графика область.

Поскольку нас не интересуют промежуточные результаты, мы можем записать результирующее преобразование в виде:

перенести(dx, dy, масштабировать(kx, ky, повернуть(90, построить(dataArray))));

Любая из этих операций в принципе может вызвать исключение (например, выход за пределы канвы). С другой стороны, масштабирование не является абстракцией для переноса, а поворот - для масштабирования. Эти операции представляют один уровень абстракции, а стек вызовов отражает лишь последовательность их применения. Перехват исключений в данном случае на предыдущем уровне, хотя технически легко реализуется, вряд ли имеет реальный смысл - функция масштабирования просто не знает, что делать с ошибкой функции поворота.
« Последнее редактирование: 05-02-2011 14:11 от Dale » Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #37 : 05-02-2011 16:01 » 

Dale, признаться, мне твои примеры непонятны.

Цитата: Dale
если мы вычисляем ln(sqrt(x)), функции вычисления логарифма вовсе не обязательно обрабатывать исключение, возникшее при вычислении корня
Логарифм ничего не знает про корень. Это вызывающая программа знает про обе эти функции. Ты говоришь так, будто в логарифм передаётся указатель на функцию расчёта корня, и логарифм вызывает эту функцию. Здесь же вызывающая программа вызывает расчёт корня, получает результат и только потом вызывает расчёт логарифма, передавая ему этот результат в качестве аргумента. Вложенность вызова всегда 1, данные в стеке не соседствуют - они там сменяют друг друга во времени.

Цитата: Dale
перенести(dx, dy, масштабировать(kx, ky, повернуть(90, построить(dataArray))));
Аналогично.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #38 : 05-02-2011 18:13 » 

Dale, признаться, мне твои примеры непонятны.

Точно, второпях не то написал. Предыдущий пример явно не в тему в случае C. Это я параллельно работаю над своим интерпретатором, там действительно такие вложения функций приводят к вложенным вызовам.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #39 : 01-08-2011 19:02 » 

Интересная особенность использования Throw() конце функций с возвращаемым значением: последнее всегда нужно указывать.

Код: (C)
int func(int x)
{
    if (x > 0)
        return x;

    Throw(E_INVALID_VALUE);
    return 0;
}

Если не указать последний return, то компиляция будет с предупреждением. Например, в gcc: "control reaches end of non-void function".

Не знаю, что на этот счет в VC или в BCB придумано, но если рассчитывать только на gcc, то можно добавить атрибут noreturn в прототип Throw().

Код: (C)
void Throw(CEXCEPTION_T ExceptionID) __attribute__ ((noreturn));

Проверил компиляцией в asm: код становится короче даже без оптимизации, а с оптимизацией (-O2 или -O3) - короче, чем без атрибута.
« Последнее редактирование: 01-08-2011 19:33 от RXL » Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #40 : 01-08-2011 19:13 » 

И фишка, не оговоренная в статье: возможность пересмотра настроечных макросов в файле CExceptionConfig.h. Можно изменить макросы CEXCEPTION_NONE, CEXCEPTION_NUM_ID, CEXCEPTION_GET_ID и CEXCEPTION_T. Особенно полезно для микроконтроллеров - первый и последний.

Код: (C) CExceptionConfig.h
#ifndef CEXCEPTIONCONFIG_H
#define CEXCEPTIONCONFIG_H

#define CEXCEPTION_NONE 0xff
#define CEXCEPTION_T unsigned char

#endif /* CEXCEPTIONCONFIG_H */

Но теперь нужно при компиляции всех модулей, включающих заголовок CException.h, нужно определить макрос CEXCEPTION_USE_CONFIG_FILE.

$
$ gcc -Wall -DCEXCEPTION_USE_CONFIG_FILE -с test_exceptions.c CException.c
$

В принципе, ничего не мешает исправить нужные макросы сразу в CException.h.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #41 : 02-08-2011 21:00 » 

Посмотрел состав контекста в WinAVR: прилично - 23 или 24 байта. Главное не слишком увлекаться этой приятной возможностью.

Код: (C) setjmp.h
/*
   jmp_buf:
        offset  size    description
         0      16      call-saved registers (r2-r17)
        16       2      frame pointer (r29:r28)
        18       2      stack pointer (SPH:SPL)
        20       1      status register (SREG)
        21       2/3    return address (PC) (2 bytes used for <=128Kw flash)
        23/24 = total size
 */
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #42 : 03-08-2011 06:12 » 

Для небольших программ глубины вложения блоков 3-4 уровня вроде должно хватить. Для самых младших моделей многовато, для средних должно потянуть.
Записан

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

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Страниц: 1 2 [Все]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines