Возврат ошибки из функции

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Re: Возврат ошибки из функции

Сообщение А.Н. » 01.08.2010 22:53:22

MageSlayer писал(а):Опять неясно.
Если вы имеете в виду какие-то побочные эффекты в циклах, то надо оборачивать try/except и в except вставлять raise для дальнешего разкручивания стека.

Посмотрите код последней функции, там я так сделал:
viewtopic.php?f=5&t=6065
Проблема в том, что если в цикле возникнет ошибка, после показа главной формы, например, остаётся "выключенная" главная форма, которая не реагирует на действия пользователя.
После корректного завершения цикла, программу, хотя бы закрыть "крестиком" возможно. :-)

Про тонну проверок опять не понял. Может все-таки по-подробнее?

Пример там же: if (form = nil) then continue;
Ну, или в таком стиле:
Код: Выделить всё
        if (blank = nil) then
        // Если такой бланк не учтён, я добавляю его в коллекцию.
          begin
            blank := ins_comp.BlankAdd(entity_id[1], entity_id[2]);
            if (blank = nil) then exit;
...
        ins_comp := TInfInsCompany(CompaniesCollection.GetEntityByID(entity_id));
        if (ins_comp = nil) then exit;
        t_node := trvLeft.Items.FindNodeWithData(ins_comp);
        // 1. Удаляю из дерева.
        if (t_node <> nil) then t_node.Free;
        // 2. Удаляю из коллекции.
        ins_comp.Free;

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

Добавлено спустя 3 минуты 46 секунд:
Короче, я запутался с тем, где исключения должны выбрасываться.
Например, при ошибке во время создания объекта выбрасывается исключение?
Например, если недостаточно ресурсов. И что тогда делать? С исключением - он повесится.
Без исключения хоть как-то может работать. И что получается, для этого нужно загнать его в функцию, обернуть пустым try/except и проверить результат на NULL в вызывающем? Зачем тогда исключения?

Добавлено спустя 1 минуту 27 секунд:
Хм... Ещё, почему-то у меня возникает ощущение, что я порю несусветный бред. :shock:
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Возврат ошибки из функции

Сообщение Odyssey » 01.08.2010 23:25:11

А.Н. писал(а):Например, при ошибке во время создания объекта выбрасывается исключение?
Например, если недостаточно ресурсов. И что тогда делать? С исключением - он повесится.
Без исключения хоть как-то может работать. И что получается, для этого нужно загнать его в функцию, обернуть пустым try/except и проверить результат на NULL в вызывающем? Зачем тогда исключения?

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

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

P.S.
Но опять же, нельзя ловить произвольный Exception (т.е. try .. except без on ..do), потому что его по определению нельзя обработать правильно, ведь неизвестно из-за чего он возник. Т.е. если ловим исключение о нехватке русурсов, в обработке освобждаем ресурсы, если исключение другое -- нужно его снова перегенерировать, чтобы не прошло незамеченным.
Последний раз редактировалось Odyssey 01.08.2010 23:28:59, всего редактировалось 1 раз.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Возврат ошибки из функции

Сообщение MageSlayer » 01.08.2010 23:26:56

А.Н. писал(а):Посмотрите код последней функции, там я так сделал:
viewtopic.php?f=5&t=6065
Проблема в том, что если в цикле возникнет ошибка, после показа главной формы, например, остаётся "выключенная" главная форма, которая не реагирует на действия пользователя.
После корректного завершения цикла, программу, хотя бы закрыть "крестиком" возможно. :-)


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

А.Н. писал(а):
Про тонну проверок опять не понял. Может все-таки по-подробнее?

Пример там же: if (form = nil) then continue;
Ну, или в таком стиле:
Код: Выделить всё
        if (blank = nil) then
        // Если такой бланк не учтён, я добавляю его в коллекцию.
          begin
            blank := ins_comp.BlankAdd(entity_id[1], entity_id[2]);
            if (blank = nil) then exit;
...
        ins_comp := TInfInsCompany(CompaniesCollection.GetEntityByID(entity_id));
        if (ins_comp = nil) then exit;
        t_node := trvLeft.Items.FindNodeWithData(ins_comp);
        // 1. Удаляю из дерева.
        if (t_node <> nil) then t_node.Free;
        // 2. Удаляю из коллекции.
        ins_comp.Free;

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

Обычно это проблема смешения бизнес-логики (жлобское выражение, сорри) и собственно отрисовки/визуализации/...
Тут из небыстрых решений - перейти на VirtualTreeView + интерфейсы.
И исключения или их иерархия, имхо, никак не помогут. Не для того, они созданы.

А.Н. писал(а):Добавлено спустя 3 минуты 46 секунд:
Короче, я запутался с тем, где исключения должны выбрасываться.
Например, при ошибке во время создания объекта выбрасывается исключение?
Например, если недостаточно ресурсов. И что тогда делать? С исключением - он повесится.
Без исключения хоть как-то может работать. И что получается, для этого нужно загнать его в функцию, обернуть пустым try/except и проверить результат на NULL в вызывающем? Зачем тогда исключения?

Если нет ресурсов, то программа должна поймать исключение на самом верхнем уровне. И по дороге корректно разрушить все временных объекты.
Опять же, если визуализация тесно переплетена с "бизнес-логикой", то это тупик. Раньше или позже.
Все должно работать без оборачивания пустыми try-except.

А.Н. писал(а):Добавлено спустя 1 минуту 27 секунд:
Хм... Ещё, почему-то у меня возникает ощущение, что я порю несусветный бред. :shock:

[/quote]
Похоже на то :)
MageSlayer
постоялец
 
Сообщения: 216
Зарегистрирован: 07.09.2006 12:30:44

Re: Возврат ошибки из функции

Сообщение krab » 02.08.2010 01:34:31

Зато, если выкинет в середине цикла, который должен обработать все элементы (и после него ещё должна быть обработка), программа вообще никак не будет работать. Зависнет, например. А так, элемент будет пропущен и обработка продолжится. К тому же, если исключения выбрасываются библиотекой, что мне искать все классы исключений, которые она может выбросить? Но я не спорю - это действительно неправильно. Вопрос в том как с ними работать по-человечески? Или, exceptions - это, из серии "гладко было на бумаге, да забыли про овраги"?
О исключениях. Даже о использовании в цикле обработки упоминается:
If you were doing some batch processing with a Clientdataset, then allowing this exception to bubble up the stack will stop the loop. In this case, you might want to generically trap all the exceptions that are passed into the event handler.
krab
постоялец
 
Сообщения: 108
Зарегистрирован: 17.02.2010 18:23:08

Re: Возврат ошибки из функции

Сообщение А.Н. » 02.08.2010 10:25:53

Odyssey писал(а):Продолжим развитие мысли, вот мы проверили результат на NULL, что делать дальше? Показать сообщение об ошибке и закрыть программу? Если да, то исключение и само это сделает, и незачем его перехватывать и оборачивать чтобы потом сделать то же самим. А вот если из сложившейся ситуации есть разумный выход (например наш второй поток сожрал все ресурсы, мы можем тормознуть его и освободить ресурсы), то да, Exception нужно ловить и делать обработку.

А где его нужно ловить?
В вызываемом методе? Но тогда получается, что за обработку проблем выделения памяти будет отвечать никак не относящийся к этому класс, который ничего не знает о потоках, к примеру.
Тут получается ещё хуже. Более сильное "смешение бизнесс-логики и собственно ...", на что MageSlayer, постом ниже, ругается.
В вызывающем? Так, снова, загромождение кода несвойственной ему функциональностью.
Хотя, это лучше, поскольку обработка ошибок выносится в одно место. Но, опять же, есть у меня, например, добавление элемента в дерево в модуле формы. Откуда он знает про второй поток? И чтобы сделать такую обработку, вероятно придётся совсем нетривиальное что-то придумывать.
В общем обработчике, на всю программу? Ну да. Получается - наиболее приемлемый вариант. Хм... Вот, только вы вспомнили про многопоточность... :- Да и как будет выглядеть этот обработчик? Не слишком сложно? Плюс к тому, надо вернуться из обработки исключения, если она прошла, в точку вызова. Т.е., это не Apllication.OnException, а какая-то функция, которой должен передаваться параметр типа Exception. Множества классов или с множеством параметров, хотя бы для того, чтобы понять в каком месте произошло исключение. Как пример, для недостатка ресурсов: исключение может произойти либо в первом потоке, тогда возможно что-то сделать, либо во втором потоке, тогда нужно выдать сообщение и умереть. Т.е., обработчик должен знать много о структуре программы?

Но опять же, нельзя ловить произвольный Exception (т.е. try .. except без on ..do), потому что его по определению нельзя обработать правильно, ведь неизвестно из-за чего он возник. Т.е. если ловим исключение о нехватке русурсов, в обработке освобждаем ресурсы

Каким образом? См. выше.

Odyssey писал(а):если исключение другое -- нужно его снова перегенерировать, чтобы не прошло незамеченным.

MageSlayer писал(а):Все должно работать без оборачивания пустыми try-except.

И что в итоге? Есть цикл. В результате какой-то ошибки, один из элементов цикла не может быть корректно обработан.
Есть два варианта:
1. Это проблема элемента.
2. Это другая проблема. Какое-то внешнее исключение.
В первом случае, надо продолжить цикл, пропустив элемент. Во-втором - завершить программу или что-то сделать. Нужно определять к какому классу исключений оно относится?

MageSlayer писал(а):Во-первых, с формами, создающимися динамически, вообще работать не будет. Вы будете хранить указатели/объекты, которых уже может не быть "в живых".

Будет.
Во-первых, если форма создаётся через Application.CreateForm.
В принципе, по-другому делать не особо правильно.
Во-вторых, так-то, конечно, да...
Но в каком случае может произойти разрушение формы в свёрнутой программе?

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

Так получается, что для того, чтобы корректно свернуть формы и, затем восстановить, требуется создать промежуточный базовый класс? Ва-ха-ха. Удобства, как в деревне. :lol: Каждую форму унаследовать от нового базового класса? А это, вообще, оправданно? Может, тогда лучше в Tag адрес структуры запихнуть? :-

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

Переопределять обработчики событий? Мда... События и так-то "приятная" вещь. А тут ещё использовать их таким образом? И всё только ради того, чтобы свернуть форму?

(жлобское выражение, сорри)

Ну, лучше, вроде, пока что, ничего не придумали... :mrgreen:

Тут из небыстрых решений - перейти на VirtualTreeView + интерфейсы.

Прочитал основное про VirtualTreeView здесь:
http://forum.vingrad.ru/forum/topic-97620.html
Думаю, что для lazarus принципиально не отличается.
Да, возможно, проще будет отображать изменения. Удобнее. Но ведь принципиально-то ничего не поменяется. Дерево отображает элементы коллекции. Её тоже надо обновлять.
А причём здесь интерфейсы?

Кстати, чего-то я не понял там:
NewPhone := VT.GetNodeData(NewNode);
...
PhoneNode := VT.GetNodeData(Node);
Если PhoneNode - указатель на запись, а GetNodeData возвращает обычный указатель, ведь всё-равно должно быть приведение типов?

Обычно это проблема смешения бизнес-логики (жлобское выражение, сорри) и собственно отрисовки/визуализации/...

Опять же, если визуализация тесно переплетена с "бизнес-логикой", то это тупик. Раньше или позже.

Но ведь нельзя же сделать этакий "сферический MVC в вакууме", где всё отделено?
Особенно в RAD среде, типа lazarus. Больше мучений. Как их не смешивать?
Или я здесь ошибаюсь?

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

Исключения уберут загромождающую проверку ошибок. И станет легче читать. Т.е., я могу не проверять на nil, а сразу вызвать метод. Если объектная переменная не была инициализирована, выбросится исключение, которое будет обработано "ниже" (в смысле по тексту метода, в блоке except). Это удобнее. Теоретически.

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

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

krab писал(а):О исключениях. Даже о использовании в цикле обработки упоминается:

Вот это точно: "Abstract: Exceptions are both powerful and very misunderstood."
Читаю. Там, по ходу, много по тому, что нужно написано. Штука годная.

Добавлено спустя 8 минут 51 секунду:
Хм... Да там все мои ошибки рассмотрены:
Код: Выделить всё
  Result := True;
  try
    Temp := StrToInt(aStr);
  except
    Result := False;
  end;

:?

Добавлено спустя 8 минут 29 секунд:
2krab:
А есть ли что ещё хорошее по теме? Желательно на старосоветском, поскольку проще читать? :)
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Возврат ошибки из функции

Сообщение Odyssey » 02.08.2010 14:39:13

А.Н. писал(а):А где его нужно ловить?
Зависит от того, что нужно делать, и где можно это сделать. Чем глобальнее, тем наверное лучше (чище код), но иногда в "общем обработчике на всю программу" уже нет доступа к объектам, которые помогли бы восстановиться после исключения. Тогда придётся делать обработку там, где такой доступ есть.
А.Н. писал(а):Хм... Вот, только вы вспомнили про многопоточность... :- Да и как будет выглядеть этот обработчик? Не слишком сложно?
Ох, зря я про неё вспомнил :) Просто больше не пришло в голову ни одного случая, когда можно восстановиться после нехватки ресурсов. Вообще, нехватка ресурсов -- это такая штука, что если приложение о ней узнало, должно не восстанавливаться, а самоустраниться как можно быстрее, чтобы освободить ресурсы. Если это конечно не АСУ на АЭС.
А.Н. писал(а):Плюс к тому, надо вернуться из обработки исключения, если она прошла, в точку вызова.
Насколько знаю, это невозможно. После exception'а можно попасть только в обрачивающие блоки except/finally, и назад пути нет. Поэтому их и приходится размещать там, где ещё можно восстановиться, если в восстановлении есть смысл.
А.Н. писал(а):Т.е., обработчик должен знать много о структуре программы?
Если его назначение -- спасти ситуацию, то да. Ведь нельзя же восстановить работоспособное состояние программы не зная её структуры. Если назначение -- выкинуть сообщение в лог, уведомить пользователя об ошибке и закрыть программу -- то можно и не знать о структуре.
А.Н. писал(а):
Odyssey писал(а):Т.е. если ловим исключение о нехватке русурсов, в обработке освобждаем ресурсы
Каким образом? См. выше.
Мда, с нехваткой ресурсов плохой пример. Я тут имел в виду примерно то, что написано в статье Ника с примером
Код: Выделить всё
try
  SomeCodeThatRaisesAnEConvertError;
except
  on E: EConvertError do
  begin
  // Deal with this specific exception here
  end;
end;
Возьмём другой пример, пусть с циклом. Допустим в цикле вызывается некая процедура HandleItem, которая умеет обрабатывать элементы всех типов кроме одного, и в случае этого "одного" вызывает исключение EWrongItemType. Тогда код цикла может выглядеть примерно так:
Код: Выделить всё
for i := 0 to List.Count - 1 do
begin
  try
    HandleItem(List[i])
  except
    // только так:
    on EWrongItemType do MarkItemAsUnhandled(List[i]);
    // но не так:
    // MarkItemAsUnhandled(List[i]);
  end;
end;
А.Н. писал(а):Есть два варианта:
1. Это проблема элемента.
2. Это другая проблема. Какое-то внешнее исключение.
В первом случае, надо продолжить цикл, пропустив элемент. Во-втором - завершить программу или что-то сделать. Нужно определять к какому классу исключений оно относится?
Точно.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Возврат ошибки из функции

Сообщение А.Н. » 05.08.2010 17:59:48

Есть и слабые стороны. Вопрос как эти слабые стороны устранить? Я, всё-равно, так и не понял как толком исключения использовать.
Та статья - слишком краткая. Лишь несколько полезных рекомендаций. Охота чего-то "поглобальнее". :-)

Зависит от того, что нужно делать, и где можно это сделать. Чем глобальнее, тем наверное лучше (чище код)

Но, с другой стороны, усложняется код обработчика.

но иногда в "общем обработчике на всю программу" уже нет доступа к объектам, которые помогли бы восстановиться после исключения. Тогда придётся делать обработку там, где такой доступ есть.

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

Ох, зря я про неё вспомнил :)

Ну отчего же не вспомнить?

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

Хм... Кажется некоторые виндовс программы так и написаны. ;-) Если, не весь виндовс: самоустраниться по поводу и без повода.

Если это конечно не АСУ на АЭС.

А почему обязательно АСУ? Во-первых, почему другие программы должны быть менее надёжны (ну, если не учитывать затраты на разработку и тестирование, даже какая-то дискриминация :D )?
Во-вторых, взять, например, текстовый редактор/процессор.
Уж, вроде, прикладнее не бывает. И не столь важная программа.
Но представьте, что вы в нём отчёт за год пишите. Или у вас там логины/пароли/реквизиты. Вы не сохранили.
Что-то сделали. Символ дописали. Ему, например, потребовалось расширить буфер.
Расширяет он, например, блоками по несколько сотен килобайт.
Если не хватит памяти, в результате чего он "самоустраниться, как можно быстрее", будет здорово?
Особенно, если там были записаны какие-нибудь свежие коды доступа для АСУ на АЭС. :mrgreen:

А.Н. писал(а):в точку вызова

Насколько знаю, это невозможно. После exception'а можно попасть только в обрачивающие блоки except/finally, и назад пути нет. Поэтому их и приходится размещать там, где ещё можно восстановиться, если в восстановлении есть смысл.

Я про то и говорю. Общий обработчик неприменим. Хотя, вернуться-то и возможно (в объекте исключения передаётся адрес, по которому оно произошло. Взять, сделать некоторые манипуляции, зависящие от конкретного кода, запихнуть в стек и сделать ret. Не факт, что из этого что-то получится и стек не "сорвёт", что его "вручную" не придётся очищать перед возвратом. Только предположения.), но это неудобно.
Т.е., придётся делать try/except. Они загромождают код гораздо сильнее, чем проверка на NULL.
А, если это цикл (про что в той статье сказано: пустая обёртка try/except допустима), почему не сделать обёртку в методе, а не в цикле? Хотя, конечно, метод элемента используется не только в цикле. Но, NULL однозначно говорит, что "действие с элементом выполнить не удалось". Чего-то я запутался слегка...

А.Н. писал(а):Т.е., обработчик должен знать много о структуре программы?

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

Т.е., получается, что обработчик очень сложный? Этакая "программа в программе"?

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

Ну, это возможно и в OnException сделать. Для этого не нужен такой обработчик.

А.Н. писал(а):Нужно определять к какому классу исключений оно относится?

Точно.
...
Допустим в цикле вызывается некая процедура HandleItem, которая умеет обрабатывать элементы всех типов кроме одного, и в случае этого "одного" вызывает исключение EWrongItemType. Тогда код цикла может выглядеть примерно так:

Это красиво выглядит в примере. На самом деле, здесь, как мне кажется, есть неоднозначность.
Во-первых, реальный код сложнее. Например, занесение в БД. Если произошла ошибка, при занесении - проблема элемента. Если это внешняя ошибка, например ОС - проблема вышестоящего уровня.
Но ведь нельзя все ошибки выделить, даже если использовать базовые классы исключений?
К тому же, во всех классах, если их много, делать блок try/except, который учитывает все эти ситуации, повторяя код?
Так что получается? Здесь и нужен общий обработчик? Или как-то нужно эту обработку реализовать в базовом классе?
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Возврат ошибки из функции

Сообщение Odyssey » 05.08.2010 20:29:56

А.Н. писал(а):А почему обязательно АСУ? Во-первых, почему другие программы должны быть менее надёжны ..? ... текстовый редактор ... отчёт за год
Надёжность -- она сама по себе. Ничто не мешает редактору в OnIdle/OnTimer/OnChange делать бэкап отчёта и писать его на диск. Если ему не хватило памяти на то чтобы вставить кусок текста, совсем не факт, что в таких экстремальных условиях ему удасться сбросить отчёт на диск. А даже если удасться, лучше этих условий не ждать, а сделать всё заблаговременно.
Что же до самоустранения, то это вопрос "чуства собственного величия" программы, если этот термин применим к программам. Если система докатилась до того, что даже блокноту не может выделить памяти, значит ей очень нехорошо. И в таких условиях лучше отдать ресурсы тем, кому они реально нужны, иначе ОС может навернуться вместе с нашим блокнотом. ИМХО, обработка ситуации нехватки памяти без самоустранения целесообразна только тогда, когда остановка программы приводит к катастрофе. На десктопе, имхо, таких ситуаций нет.
А.Н. писал(а):про что в той статье сказано: пустая обёртка try/except допустима
Если имеется в виду try ... except end; то ничего такого в статье не написано! Как раз наоборот, Trap only specific exceptions, т.е. между except и end должна быть обработка с определением типа.
А.Н. писал(а):почему не сделать обёртку в методе, а не в цикле?
Можно и в методе, я написал пример прямо в цикле из-за лени, чтобы не писать метод.
Ведь нельзя же восстановить работоспособное состояние программы не зная её структуры.
А.Н. писал(а):Т.е., получается, что обработчик очень сложный? Этакая "программа в программе"?
Не знаю, я таких обработчиков не писал :) В моём случае это нецелесообразно.
А.Н. писал(а):Ну, это возможно и в OnException сделать. Для этого не нужен такой обработчик.
А OnException это тоже разновидность обработчика. За каждым OnException скрывается что-то типа
Код: Выделить всё
procedure TApplication.RunLoop;
begin
  repeat
    if CaptureExceptions then
      try // run with try..except
        HandleMessage;
      except
        HandleException(Self);
      end
    else
      HandleMessage; // run without try..except
  until Terminated;
end;
А.Н. писал(а):Это красиво выглядит в примере. На самом деле, здесь, как мне кажется, есть неоднозначность. Во-первых, реальный код сложнее. Например, занесение в БД. Если произошла ошибка, при занесении - проблема элемента. Если это внешняя ошибка, например ОС - проблема вышестоящего уровня.
Но ведь нельзя все ошибки выделить, даже если использовать базовые классы исключений?
Все -- нельзя, да это и не нужно. Например нам не хватает памяти на очередной запрос, или возникла проблема соединения с БД, и мы пытаемся подключиться к упавшему хосту с тремя попытками и таймаутом 1 минута на каждый элемент. Есть случаи когда нельзя продолжать беззаботно перебирать элементы как будто ничего не случилось. Поэтому смысл в том, чтобы обработать конкретные, относительно безопасные, "штатные" типы ошибок, а со всеми внештатными поступать по общему правилу для внештатных (например "лог, сообщение, завершение работы", или вообще для начала оставить обработку по умолчанию).
А.Н. писал(а):К тому же, во всех классах, если их много, делать блок try/except, который учитывает все эти ситуации, повторяя код? Так что получается? Здесь и нужен общий обработчик? Или как-то нужно эту обработку реализовать в базовом классе?
Да, общий. А чтобы не повторять код, можно вынести обработчик в отдельную функцию, по аналогии с HandleException в TApplication.RunLoop. Мне ещё приходит в голову что типа:
Код: Выделить всё
try
  ...
except
  on E do
  begin
    if not MyHandleException(E) then raise;
  end;
end;
Хотя наверное даже это усложнено, и в RunLoop проще. Тут лучше посмотреть исходники FPC/Lazarus.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Возврат ошибки из функции

Сообщение А.Н. » 05.08.2010 21:54:37

Надёжность -- она сама по себе. Ничто не мешает редактору в OnIdle/OnTimer/OnChange делать бэкап отчёта и писать его на диск.

Надёжность - интегральная характеристика. :-) А, если ему не хватит памяти, чтобы скинуть отчёт на диск?
А, если не хватит дискового пространства? А, если..? В итоге, всё сведётся к обработке ошибок/исключений.
Вы уже говорите о дополнительной системе "обеспечения надёжности". Это другое.

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

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

Что же до самоустранения, то это вопрос "чуства собственного величия" программы, если этот термин применим к программам.

Хех, вы, наверное, хотите сказать, что термин более применим к разработчику? :D

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

У вас какой-то гуманный блокнот получается. Христианский... :) Если он будет следовать такой морали, его будут бить по обеим щекам (если они у него есть, панелям там, каким-нибудь: правой, левой). По правой ОС, по левой пользователь. И разработчику может достаться, если поймают. :mrgreen:
В windaw$ 3.11 это ещё и могло быть оправданно, но не в современных системах.
Это вопрос "делегирования полномочий". Думаю, что, если каждый прикладной разработчик будет думать о том как будет плохо ОС, если его программа выживет, популяция разработчиков сократится. :)
Если программа обрабатывает такие ошибки, она должна стремиться восстановить свою работу.
Решать, будет программа работать или будет завершена, должна ОС, но никак не программа..
При этом, ОС может, например, спросить у пользователя (как в последних виндавсах, окошко "Это приложение не отвечает") или выполнить другие действия.

ИМХО, обработка ситуации нехватки памяти без самоустранения целесообразна только тогда, когда остановка программы приводит к катастрофе. На десктопе, имхо, таких ситуаций нет.

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

А для какого-нибудь школьника, например, и прерывание общения с девочкой в чате - катастрофа.
В связи, с вылетом браузера firef**s. :(
Ээээ, так о чём я? :)

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

Если имеется в виду try ... except end; то ничего такого в статье не написано!

Там описано одно исключение из правила:
The only time that you might even consider using this construct – which is only slightly better than eating the exception altogether is -- when you know that the calling routine doesn't want to handle any exceptions or when the calling routine expects to handle the specific exception. For instance, the TClientDataset has an OnReconcileError event that actually passes an exception into it. If you were doing some batch processing with a Clientdataset, then allowing this exception to bubble up the stack will stop the loop. In this case, you might want to generically trap all the exceptions that are passed into the event handler.


Можно и в методе, я написал пример прямо в цикле из-за лени, чтобы не писать метод.

Не, я имел ввиду не отдельный метод, вызываемый в цикле. А метод объекта, обрабатываемого в цикле.
Т.е., всё в методе обёрнуто try/except и он возвращает false/nil и т.д., в случае неудачи.
В принципе, как-то привычно, но, по-идее, неправильно?

А OnException это тоже разновидность обработчика.

Я имел ввиду сложный обработчик со своими параметрами. Отдельную функцию, когда OnExcept затруднительно использовать.

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

Т.е., всё сводится к тому, чтобы пропускать все ошибки, а по мере появления, писать их обработчики?

Хотя наверное даже это усложнено, и в RunLoop проще. Тут лучше посмотреть исходники FPC/Lazarus.

В смысле, в RunLoop проще?
Т.е., просто сделать общий обработчик, который вызовет raise, в случае отсутствия обработчика для конкретного исключения? Сложный он получается...
Хм... А есть какие-то готовые решения для обработки исключений, типа какой-нибудь библиотеки для работы с ними?
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Re: Возврат ошибки из функции

Сообщение Odyssey » 07.08.2010 11:04:12

А.Н. писал(а):Это вопрос "делегирования полномочий". ... Решать, будет программа работать или будет завершена, должна ОС, но никак не программа..

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

А.Н. писал(а):Там описано одно исключение из правила:

Точно, я упустил, это он про on E: Exception do. Это по сути сваливание исключений в одну кучу, и тут всё зависит от способа обработки. Если это например логгирование, то всё не так плохо, потом почитав логи можно будет хотя бы понять какие элементы не обработаны и с каким исключением.

А.Н. писал(а):Не, я имел ввиду не отдельный метод, вызываемый в цикле. А метод объекта, обрабатываемого в цикле. Т.е., всё в методе обёрнуто try/except и он возвращает false/nil и т.д., в случае неудачи.
В принципе, как-то привычно, но, по-идее, неправильно?

Да, это нехорошо, по двум причинам.

Во-первых, если нас не интересует что могло произойти в этом методе, например:
Код: Выделить всё
if not LoadFile(AFilename) then CreateFile(AFilename);

И нам на этом уровне совершенно не интересны все возможные причины, почему LoadFile не удался (нет файла, нет прав, недопустимое имя файла, ...). Тут лучше в LoadFile обойтись без исключений, а по мере обнаружения причин этих исключений делать обычные проверки (типа FileExists и т.п.). Это прямо по примеру из статьи Ника с (Don’t Go Looking For Exceptions). Кто-то даже придумал для такой ситуации новый термин "Expection Handling", от слов Expected + Exception, т.е. когда исключения становятся из исключительных ситуаций совершенно обычными, на них сознательно нарываются и обрабатывают как ни в чём не бывало, хотя есть способы обработки ситуации без исключений.

Во-вторых, это плохо тем, что мы прячем причину ошибки. Например если тот же LoadFile не удался из-за Disk read error: bad sector, или что-то типа того, то метод просто вернёт False, и у пользователя не будет ни шанса узнать, что его хард скоро накроется.

Поэтому имхо тут вариант такой:
1) для известных ситуаций которые можем обработать вообще без исключений (типа FileExists), обрабатываем без них и при необходимости делаем Exit(False).
2) для известных ситуаций, которые без исключений обработать нельзя или слишком сложно, создаём отдельный on-блок в try .. except и при необходимости делаем Exit(False).
3) все остальные ситуации нужно показывать -- например в логе или сообщением. Естественно, за окна сообщений в цикле можно получить лучей ненависти от пользователей :) False вернуть можно, но неизвестные непредусмотренные ошибки не должны замалчиваться. Прерывать при этом работу программы или нет -- это решать разработчику. Можно сделать как в Lazarus, вынести вопрос о продолжении в окно сообщения.

А.Н. писал(а):Т.е., всё сводится к тому, чтобы пропускать все ошибки, а по мере появления, писать их обработчики?

Да, именно это я имел в виду.

А.Н. писал(а):В смысле, в RunLoop проще?

Проще в том смысле, что судя про обрабочику из Лазарусовского TApplication.RunLoop, информация о последнем возникшем exception'е, похоже, хранится глобально, и для её получения необязательно передавать в обработчик объект E, как в моём примере.

А.Н. писал(а):Т.е., просто сделать общий обработчик, который вызовет raise, в случае отсутствия обработчика для конкретного исключения? Сложный он получается...

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

А.Н. писал(а):Хм... А есть какие-то готовые решения для обработки исключений, типа какой-нибудь библиотеки для работы с ними?

Для Delphi вроде было что-то, типа EurekaLog, для Lazarus -- не знаю.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Возврат ошибки из функции

Сообщение svk12 » 07.08.2010 11:27:04

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

Добавлено спустя 1 минуту 53 секунды:
2MageSlayer:
Такое же, как и всё остальное. Один из случаев, когда я не понимаю, как с ними правильно работать.
Ещё, например, меня интересует тонна проверок на nil. Ведь, по-идее, в коде не должно быть таких проверок?


Если ошибка произойдёт в цикле, то перед выдачей сообщения RTL выполнит выход из цикла и программа не зависнет.

Можно не упоминать конкретный класс, а написать
Код: Выделить всё
try
.........
  except on E:Exception do

end


Для проверки на nil есть функция Assigned
svk12
постоялец
 
Сообщения: 408
Зарегистрирован: 09.06.2008 18:42:47

Re: Возврат ошибки из функции

Сообщение А.Н. » 29.08.2010 21:03:04

Odyssey писал(а):Поэтому имхо тут вариант такой:
1) для известных ситуаций которые можем обработать вообще без исключений (типа FileExists), обрабатываем без них и при необходимости делаем Exit(False).
2) для известных ситуаций, которые без исключений обработать нельзя или слишком сложно, создаём отдельный on-блок в try .. except и при необходимости делаем Exit(False).
3) все остальные ситуации нужно показывать -- например в логе или сообщением. Естественно, за окна сообщений в цикле можно получить лучей ненависти от пользователей :) False вернуть можно, но неизвестные непредусмотренные ошибки не должны замалчиваться. Прерывать при этом работу программы или нет -- это решать разработчику. Можно сделать как в Lazarus, вынести вопрос о продолжении в окно сообщения.

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

EurekaLog я посмотрел. Спасибо. :-) Штука хорошая, но дорогая. Я имел ввиду чего попроще...

svk12 писал(а):Если ошибка произойдёт в цикле, то перед выдачей сообщения RTL выполнит выход из цикла и программа не зависнет.

Смотрите тему. Будут обработаны не все элементы. Это может привести.

Для проверки на nil есть функция Assigned

Ну вы открыли мне глаза. Чем она отличается от <> nil? И как это согласуется с темой?
А.Н.
постоялец
 
Сообщения: 230
Зарегистрирован: 13.03.2010 12:23:58

Пред.

Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 60

Рейтинг@Mail.ru