Сергей Губанов wrote:
> S>Такими, которые обеспечивают ACID. Т.е. снятие/положение денег — > некая сложная, но атомарная операция. Не обязательно приводить их > полный код. Можно пользоваться ими как черными ящиками. > Программа, я так понимаю, работает в банке со счета которого деньги > снимаются. > > this.журнал.РапортОбУдачномОсуществленииДенежногоПеревода(this, счёт, сумма); > >
А если в этот момент выдернут сетевой кабель, и email не отправится? Или
запись в журнал из-за переполненого диска не произойдет?
Здравствуйте, Сергей Губанов, Вы писали: СГ>Программа, я так понимаю, работает в банке со счета которого деньги снимаются.
Прекрасно. Великолепный пример. Что мы из него видим?
1. Все функции обязаны возвращать булевый признак успешности выполнения.
2. Все функции обязаны передавать информацию о причине неудачи.
3. Если нам надо что-то большее, чем текст, в качестве информации о причине неудачи, то мы встряли. Потому что сигнатуры всех функций, имевших несчастье попасть в стек вызовов между тем местом, где мы обрабатываем ситуацию, и тем местом, где она произошла, должны быть модифицированы для передачи этой информации. Что-то более-менее общее мы использовать не сможем.
Более того, у нас нет никакого способа комбинировать обработку, и с ростом количества потенциальных неудач у нас нарастает объем кода, посвященного обработке. Если бы мы обрабатывали исключения, то все было бы сделано одной строчкой.
Вот в приведенном примере — что делать, если журнал не смог записать успешную запись в файл? О да, в данном случае он отправит письмо "внутри". Потому, что он инкапсулирует как запись протокола, так и сообщения об ошибках. А если бы мы разделили эту функциональность (что, в общем-то, более правильно, т.к. она ортогональная), то в коде пришлось бы отдельно проверять, удалось ли в провести запись в файл.
1.1.4 stable rev. 510
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, eao197, Вы писали:
E>>>А вообще, кроме классика Н.Вирта, есть еще и классик Б.Страуструп. В его книге "Язык программирования С++. 3-е издание" есть целая глава #14, которая посвящена обработке исключений. Тебе бы не мешало прочитать из нее хотя бы 14.1 (Обработка ошибок [except.error]) и 14.1.1 (Альтернативный взгляд на исключения [except.views]). Там очень сжато и точно описываются разные стратегии диагностики ошибок и роль в этом деле исключений. E>>>Note. Названия разделов я привел из англиского варианта книги, т.к. русский вариант у меня на работе. iZEN>>Ещё есть один классик т.н. "контрактоного программирования" (кстати, последователь Н.Вирта, сейчас работает в ETH) — Бертран Мейер, автор языка Eiffel. iZEN>>Стоит почитать и его. E>Стоит, безусловно. А что именно? Если можно URL, т.к. у нас достать печатное издание крайне проблематично.
"Объектно-ориентированное конструирование программных систем", Бертран Мейер, изд. Русская редакция, 2005, 1232 стр., ISBN: 5-7502-0255-0
Несколько определений и правил из главы "Когда контракт нарушается: обработка исключений".
Определения
Успех, Отказ. Вызов программы успешен, если он завершается в состоянии, удовлетворяющем контракту. Вызов считается отказом, если он не успешен. Исключение — событие периода выполнения, которое может стать причиной отказа. Отказы и исключения. Отказ программы — причина появления исключения в вызывающей программе. Случаи отказа. Вызов программы приводит к отказу, если и только если встретилось исключение, и программа не смогла с ним справиться.
Далее приводятся примеры. Как не следует обрабатывать исключения в языках Си (сигналы в UNIX) и Ада — рассмотрены тривиальные ошибки программиста.
<...>
Принципы дисциплинированной обработки исключений
Повторение (Retrying) — попытка изменить условия, приведшие к исключению, и выполнить программу повторно, начиная всё сначала. Отказ (Failure) — известный также как Организованная Паника (organized panic): чистка стэка и других ресурсов, завершение вызова и отчёт об отказе перед вызывающей программой.
Ключевые концепции
Обработка исключений — это механизм, позволяющий справится с неожиданными условиями, возникшими в период выполнения.
Отказ — это невозможность во время выполнения программы выполнить свой контракт.
Программа получает исключение в результате: отказа вызванной ею программы, нарушения утверждений (например, assert. — Прим. моё), сигналов аппаратуры или операционной системы об аномалиях, возникших в ходе их работы.
Программная система может включать также исключения, спроектированные разработчиком.
Программа имеет два способа справиться с исключениями — Повторение вычислений (Retry) и Организованная Паника. При Повторении тело программы выполняется заново. Организованная Паника означает готказ и формирование исключения у вызывающей программы.
Формальная роль обработчика исключений, не заканчивающегося retry, состоит в восстановлении инварианта, но не в обеспечении контракта программы. Последнее всегда является делом тела программы. <...> Формальная роль ветви, заканчивающейся retry, состоит в восстановлении инварианта и предусловия так, чтобы тело программы могло попытаться в новой попытке выполнить контракт.
Базисный механизм обработки исключений, включаемый в язык, должен оставаться простым, если только поощрять прямую цель обработки исключений — Повторение или Организованную Панику. Для приложений, нуждающихся в более тонком контроле над исключениями, введён класс EXCEPTIONS, позволяющий добраться до свойств каждого вида исключений и провести их обработку. Этот класс позволяет создавать и обрабатывать исключения разработчика (в языке Eiffel. — Прим. моё).
E>Я упомянул разделы 14.1 и 14.1.1 книги Страуструпа потому, что там описываются следующие стратегии поведения в случае возникновения ошибок: E>
E> аварийное завершение программы; E> возврат значения, определяющего наличие ошибки; E> возврат корректного значения, но оставление программы в некорректном состоянии; E> вызов специально предоставленных функций для реакции на ошибки E>E>и приводится сравнение этих вариантов с вариантом использования исключений.
E>Ну и еще одно соображение. Исключения, в отличии от того же контрактного программирования, стали широко призанным и адаптированным механизмом. C++, Java, C#, Python, Ruby, Ada, Smalltalk -- везде есть исключения. Конечно, это не панацея от всех болезней, и применять их нужно осторожно. Тем не менее, вряд ли идея исключений получила бы такое широкое распространение, если бы она не была настолько удачной.
Как видите, Бертран Мейер имеет ясное представление об исключениях, и эта тема — одна из важнейших в контрактном проектировании/программировании. Он ни в коей мере не отрицает возможность и нужность исключений, наоборот — чётко формализует и внедряет в процесс проектирования ПО в виде неотъемлемой части.
Поэтому исключения нужно применять не "осторожно", как вы предлагаете, а просто-напросто ГРАМОТНО работать с ними. Это целая "субархитектура" (по аналогии с суб-культурой) в основании любой программной системы, и игнорировать её нельзя.
А в Хаскеле — например, самый, наверное, простейший вариант:
module Main where
import IO
main :: IO () -- Это комментарий
main = let processError e = -- Обработчик ошибки I/O. Типизируется как :: IOError -> IO ()
putStrLn . show e
in doIO "Who the hell are you? " `catch` processError -- Передача action'a и обработчика конструтору catch
where
doIO out = do -- Обернутый I/O action
putStrLn out >> hFlush stdout
str <- getLine
putStrLn ("Nice to meet you, " ++ str)
Я постарался оформить это все попонятнее с точки зрения демонстрации именно CP-подхода, но вообще конечно в хаскеле все эти синтаксические условности ничего не значат. Вот, что на самом деле происходит (делает то же самое, но выглядит ближе к лямбда-нотации, может быть менее, или, наоборот — более понятно):
main :: IO ()
main = catch (do putStrLn "Who the hell are you? " >> hFlush stdout
getLine >>= putStrLn . (++) "Nice to meet you, ")
(\e -> putStrLn $ show e)
Функция-конструктор catch оборачивает экшены ввода-вывода в вариантный тип и в зависимости от его значения вызывается соответствущий обработчик.
Это в принципе более всего похоже на обычные иксепшены (за тем исключением, что обернутый action и обработчик могут общаться друг с другом напрямую за спиной catch, т.к сами — обычные функции и стек остается целым) и наверное самый простой способ отделить обработчик от основной логики.
+ несмотря на то, что ничто не мешает создать самому сколь угодно навороченную систему обработки, модуль Control.Exception определяет огромное количество готовых примитивов: http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html
Здравствуйте, Cyberax, Вы писали:
C>Такие идеи пробовались в 70-80х годах. Оказалось, что корректно C>обрабатывать retry'и достаточно сложно, и почти никогда не нужно. А C>когда все же действительно нужно, то проще явный while поставить.
например, у меня несколько раз возникала такая необходимость: если операция не удалась, то вызвать функцию, которая запрашивает юзера "retry, abort, skip?". Ну и далее действовать по результатам ответа.
Действительно — в С++ остается только завернуть операцию в цикл, который прерывается по ответу "skip" и по благополучному завершению операции, или кидается исключением по ответу "abort".
Проблема в том, что таких мест в программе десятки, а код цикла с проверками получается довольно-таки объемистый — нужно каким-то образом использовать его повторно. Вопрос только, как?
Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя.
Ваши варианты, как решить эту проблему? (лучшее, до чего я тогда додумался — это написать большой страшнючий макрос, которому нужная операция передается как аргумент)
Дарней,
> Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя. Ваши варианты, как решить эту проблему?
Boost -- не единственное, и даже не первое место, где предоставлена функциональность аналогов boost::function на C++. Более того, для этой частной задачи реализовать это добро "ручками" совершенно несложно и недолго.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, iZEN, Вы писали:
E>>Стоит, безусловно. А что именно? Если можно URL, т.к. у нас достать печатное издание крайне проблематично. ZEN>"Объектно-ориентированное конструирование программных систем", Бертран Мейер, изд. Русская редакция, 2005, 1232 стр., ISBN: 5-7502-0255-0
ZEN>Несколько определений и правил из главы "Когда контракт нарушается: обработка исключений".
Бегло просмотрел эту главу. Мне показалось, что у Страуструпа про исключения написано более лаконично и практично.
E>>Ну и еще одно соображение. Исключения, в отличии от того же контрактного программирования, стали широко призанным и адаптированным механизмом. C++, Java, C#, Python, Ruby, Ada, Smalltalk -- везде есть исключения. Конечно, это не панацея от всех болезней, и применять их нужно осторожно. Тем не менее, вряд ли идея исключений получила бы такое широкое распространение, если бы она не была настолько удачной. ZEN>Как видите, Бертран Мейер имеет ясное представление об исключениях,
и, как мне видится, собственное представление. Которое он и выразил в Eiffel
ZEN> и эта тема — одна из важнейших в контрактном проектировании/программировании. Он ни в коей мере не отрицает возможность и нужность исключений, наоборот — чётко формализует и внедряет в процесс проектирования ПО в виде неотъемлемой части. ZEN>Поэтому исключения нужно применять не "осторожно", как вы предлагаете, а просто-напросто ГРАМОТНО работать с ними. Это целая "субархитектура" (по аналогии с суб-культурой) в основании любой программной системы, и игнорировать её нельзя.
Грамотно, по отношению к исключениям, имхо, это и есть осторожно. При проектировании библиотек во многих случаях есть выбор: как реализовать информирование пользователя библиотеки об ошибках -- с помощью исключений или кодов возврата. Иногда использование исключений может привести к сильнейшему геморрою при использовании библиотеки, т.к. придется каждый вызов заворачивать в try...catch (begin...rescue, do...requie...rescue и пр.). Иногда -- наоборот. Здесь важнейшую роль играют опыт, здравый смысл, интуиция и чувство меры проектировщика. ИМХО, конечно.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, Сергей Губанов, Вы писали:
PD>Я не собираюсь предлагать что-то вместо эксепшн, но вот идею их я бы расширил. Сейчас — это просто уведомление о чем-то. Я бы добавил в них возможность исправления, если, конечно, возможно.
PD>Происходит исключение. Выбрасывется экземпляр класса исключения. Это экземпляр получает доступ к классу/методу, где произошло исключение, анализирует причину и изменяет состояние класса, после чего, возможно, повторяет операцию, вызвавшую исключение.
Это будет круче множественного наследования! Имхо повысит энтропию системы не в разы, а на порядки!
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, Cyberax, Вы писали:
C>>Такие идеи пробовались в 70-80х годах. Оказалось, что корректно C>>обрабатывать retry'и достаточно сложно, и почти никогда не нужно. А C>>когда все же действительно нужно, то проще явный while поставить.
Д>например, у меня несколько раз возникала такая необходимость: если операция не удалась, то вызвать функцию, которая запрашивает юзера "retry, abort, skip?". Ну и далее действовать по результатам ответа. Д>Действительно — в С++ остается только завернуть операцию в цикл, который прерывается по ответу "skip" и по благополучному завершению операции, или кидается исключением по ответу "abort". Д>Проблема в том, что таких мест в программе десятки, а код цикла с проверками получается довольно-таки объемистый — нужно каким-то образом использовать его повторно. Вопрос только, как?
С помощью декомпозиции и какой-то матери...
Д>Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя.
Здравствуйте, Joker6413, Вы писали:
J>С помощью декомпозиции и какой-то матери...
точно. особенно с помощью второго пункта
J>Делегат — это ведь тот же самый callback.
верно. просто в данном случае неизвестно заранее количество и тип аргументов, которые нужно передавать
можно конечно использовать функтор, но это большой гемор с дополнительным кодированием
В общем, что я хотел сказать? Сама идея continuations кажется мне вполне интересной, хотя с помощью такой-то матери можно обойтись и без них
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Сергей Губанов, Вы писали:
СГ>>Я делю ошибки на две категории:
CS>Изначальный посыл неправильный.
CS>исключительная ситуация это есть нечто ожидаемое но исключительное.
Точно, и в обекте эксепшена может содержаться дополнительная информация о ситуации.
Например, в текстовом редакторе вы переводите каретку на строку вверх. Допустим, этим занимается отдельный метод, который возвращает номер новой текущей строки. А строчка вверху короче и поэтому каретка также будет смещена по колонкам. В этом случае метод бросает исключение, в котором содержатся новые координаты каретки.
То есть, мы хотели изменить Y-координату каретки, но также изменилась и X-координата — это не ошибка, это исключительная ситуация.
Здравствуйте, Дарней, Вы писали:
Д>например, у меня несколько раз возникала такая необходимость: если операция не удалась, то вызвать функцию, которая запрашивает юзера "retry, abort, skip?". Ну и далее действовать по результатам ответа. Д>Действительно — в С++ остается только завернуть операцию в цикл, который прерывается по ответу "skip" и по благополучному завершению операции, или кидается исключением по ответу "abort". Д>Проблема в том, что таких мест в программе десятки, а код цикла с проверками получается довольно-таки объемистый — нужно каким-то образом использовать его повторно. Вопрос только, как?
Примерно как у меня:
typedef boost::function<SomeResultType ()> SomeResultFunctor;
...
//Обернем функтор в графическую оболочку
SomeResultFunctor res = wrap_with_gui<SomeResultType>(m_hWnd,boost::bind(&MyClass::func,this),std_exception_translator();
SomeResultType val = res(); //Вызовет появление окна если все будет плохо
У меня такой же подход используется для организации future-переменных.
Д>Делегатов в С++ нет, а каждой операции нужно передавать набор аргументов, и получать обратно результат. Использовать boost по некоторым причинам низзя.
Ну так в простом варианте делегаты за вечер пишутся. Я бы просто взял Boost и художественно спер бы оттуда код
Здравствуйте, Cyberax, Вы писали:
C>А если в этот момент выдернут сетевой кабель, и email не отправится? Или C>запись в журнал из-за переполненого диска не произойдет?
Объекту "ЛокальныйСчёт" нет до этого ни какого дела, он свою задачу выполнил на 100%. Всё остальное находится в компетенции объекта "Журнал".
Сергей Губанов wrote:
> C>А если в этот момент выдернут сетевой кабель, и email не отправится? > Или > C>запись в журнал из-за переполненого диска не произойдет? > Объекту "ЛокальныйСчёт" нет до этого ни какого дела, он свою задачу > выполнил на 100%. Всё остальное находится в компетенции объекта "Журнал".
А как тогда реагировать на его ошибки? А они вполне воможны в данном случае.
Ваше решение — просто на них плюнуть, так как функиця
"РапортОбУдачномОсуществленииДенежногоПеревода" никак не может повлять
(кроме полного останова программы) на ход вызывающей функции.
СГ>Объекту "ЛокальныйСчёт" нет до этого ни какого дела, он свою задачу выполнил на 100%. Всё остальное находится в компетенции объекта "Журнал".
Так ведь если запись в журнал транзакций не прошла, состояние объекта "ЛокальныйСчет" восстановить требуется. Кто этим займется? Кстати, в действительности там все намного сложнее.
Отвечу в обратном порядке.
S>Вот в приведенном примере — что делать, если журнал не смог записать успешную запись в файл?
Что делать кому? Объекту "ЛокальныйСчёт" по этому поводу точно делать ни чего не надо, он свою задачу выполнил на 100%, а уж как там внутри объекта "Журнал" чего-то стряслось — не его дело.
S>Прекрасно. Великолепный пример. Что мы из него видим? S>1. Все функции обязаны возвращать булевый признак успешности выполнения. S>2. Все функции обязаны передавать информацию о причине неудачи. S>3. Если нам надо что-то большее, чем текст, в качестве информации о причине неудачи, то мы встряли. Потому что сигнатуры всех функций, имевших несчастье попасть в стек вызовов между тем местом, где мы обрабатываем ситуацию, и тем местом, где она произошла, должны быть модифицированы для передачи этой информации. Что-то более-менее общее мы использовать не сможем. S>Более того, у нас нет никакого способа комбинировать обработку, и с ростом количества потенциальных неудач у нас нарастает объем кода, посвященного обработке. Если бы мы обрабатывали исключения, то все было бы сделано одной строчкой.
Ничего подобного.
1) Не все процедуры обязаны возвращать булевый признак успешности. Например, операция рапорта о чём-то произошедшем, ничего не должна возвращать — это никому не интересно кроме неё самой.
2) Не все процедуры обязаны возвращать информацию о причине неудачи. Ну не получилось, что-то сделать — иногда интересно узнать почему, а иногда — причина по барабану.
3) С чего Вы взяли, что информация о неудаче есть обязательно текст? Можно возвращать текст, можно число, можно указатель на абстрактный объект, и т.д.
Вот, пример полиморфной информации об ошибке:
MODULE Errors;
TYPE
Error* = POINTER TO EXTENSIBLE RECORD
msg*: ARRAY 256 OF CHAR;
END;
List* = POINTER TO RECORD
head-: Error;
tail-: List
END;
PROCEDURE NewError* (msg: ARRAY OF CHAR): Error;
VAR e: Error;
BEGIN
NEW(e);
e.msg := msg$;
RETURN e
END NewError;
PROCEDURE NewList* (head: Error; tail: List): List;
VAR list: List;
BEGIN
NEW(list);
list.head := head;
list.tail := tail;
RETURN list
END NewList;
END Errors.
Пользовательский модуль:
MODULE MyModule;
IMPORT Errors;
TYPE
MyError* = POINTER TO EXTENSIBLE RECORD (Errors.Error)
extendedParam: MyExtendedParam;
END;
PROCEDURE NewMyError* (s: ARRAY OF CHAR; p: MyExtendedParam): Error;
VAR e: MyError;
BEGIN
NEW(e);
e.msg := s$;
e.extendedParam := p;
RETURN e
END NewError;
- - - - - - - - - - - - - - - - - - - - - - -
PROCEDURE TryDoSmth* (...params.., VAR err: Errors.List): BOOLEAN;
BEGIN
...
IF обычнаяХерняСлучилась THEN
err := Errors.NewList(Errors.NewError("обычнаяХерняСлучилась"), err);
RETURN FALSE
END;
...
IF мояХерняСлучилась THEN
err := Errors.NewList(NewMyError("мояХерняСлучилась"), err);
RETURN FALSE
END;
...
RETURN TRUE
END TryDoSmth;
- - - - - - - - - - - - - - - - - - - - - - -
END MyModule.
Здравствуйте, Privalov, Вы писали:
P>Так ведь если запись в журнал транзакций не прошла, состояние объекта "ЛокальныйСчет" восстановить требуется. Кто этим займется?
Ага, а удалённый банк надо попросить: "Пожалуйста, верните деньги обратно"...
P>Кстати, в действительности там все намного сложнее.