Примеры обработки ошибок в Eiffel

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

--

РЕДАКТИРОВАТЬ: Я знаю основы обработки ошибок в Eiffel (статусы и исключения). Однако я не могу найти ни одного существенного примера того, как приложения обрабатывают ошибки с помощью статусов. Как связаны статусы отказов?


person Eleno    schedule 16.11.2014    source источник
comment
Вы делаете это так же, как и на языке, который не имеет исключений. Исключения зарезервированы для ошибок программирования. например У объекта установлено has_error. Программа вызывает функцию с предварительным условием (требует) not has_error. (Исключения используются из-за опасений, что коды возврата и т. Д. Могут быть проигнорированы. На некоторых других языках есть полиция, которую мы можем использовать через исключения, потому что их нельзя игнорировать, но они могут и часто так и есть. У Эйфеля есть полиция, которая выбрасывает исключение, если вы игнорируете ошибку.)   -  person ctrl-alt-delor    schedule 26.07.2015


Ответы (2)


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

has_error: BOOLEAN
        -- Has operation terminated with an error?

error_code: INTEGER
        -- Last error code or `no_error'.

is_closed: BOOLEAN
        -- Is connection closed?

response: detachable RESPONCE
        -- Last response if `not has_error'.

send_request (data: REQUEST)
    require
        is_open: not is_closed
    do
        ...
    ensure
        is_closed: is_closed implies (has_error and not connection.is_open)
        is_successful: not has_error implies attached response
    end

Затем клиент может оценить состояние объекта поставщика и продолжить его использование предсказуемым образом:

interface.send_request (...)
if interface.is_closed then
    ... -- The connection is unusable and should be reestablished.
elseif interface.has_error then
    ... -- Inspect `interface.error_code', possibly trying to resend the request.
else
    ... -- Use `interface.response' to continue processing.
end

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

Если ошибка происходит глубоко в стеке, можно использовать механизм исключения с _4 _ / _ 5_. Однако это может привести к тесной связи между сетевым компонентом низкого уровня и пользовательским интерфейсом, которая не имеет ничего общего с деталями сбоя сети. В простейшем случае сетевой класс вызовет {EXCEPTIONS}.raise с соответствующим сообщением. Более конкретным подходом было бы создание объекта типа EXCEPTION (или потомка), установка соответствующего сообщения путем вызова для него set_description и вызова исключения путем вызова raise. Пользовательский код, который будет обрабатывать исключение, может выглядеть так.

local
    is_retried: BOOLEAN
    e: EXCEPTIONS
do
    if is_retried then
            -- There was an exception, handle it.
        create e
        if e.developer_exception_name ~ "This error" then
            ... -- Do something.
        elseif e.developer_exception_name ~ "That error" then
            ... -- Do something else.
        else
            ... -- Report yet another error.
        end
    else
        ... -- Some code that may fail with an exception.
    end
rescue
    if not is_retried then
        is_retried := True
        retry
    end
end

ИЗМЕНИТЬ

Конкретный способ обработки вложенных ошибок зависит от дизайна приложения и не имеет отношения к языку. Возможные альтернативы:

  1. (Если используется механизм исключения, не рекомендуется.). После перехвата исключения (нижнего уровня) и его обработки для восстановления инварианта класса создается новое исключение без отмены предыдущего. Затем запрос {EXCEPTION}.cause можно (рекурсивно) использовать для доступа к вложенным объектам исключений.

  2. Можно использовать механизм, аналогичный предыдущему. Однако вместо создания новых объектов класс может делегировать запрос подробностей классу более низкого уровня. Например,

    class A feature
        has_error: BOOLEAN
            do
                Result := nested.has_error
            end
        error: STRING
            do
                Result := "Cannot complete operation X. Reason: " + nested.error
            end
    feature {NONE}
        nested: B
    end
    
    class B feature
        has_error: BOOLEAN
            do
                Result := nested.has_error
            end
         error: STRING
            do
                Result := "Cannot complete operation Y. Reason: " + nested.error
            end
    feature {NONE}
       nested: C
    end
    
  3. Могут быть использованы лесозаготовительные сооружения. Они могут различать серьезность ошибки, указывать источники и т. Д.

    class A feature
        do_something
            do
                nested.whatever
                if nested.has_error then
                    log.error ("Cannot complete operation X.")
                end
            end
        has_error: BOOLEAN do Result := nested.has_error end
    feature {NONE}
        nested: B
    end
    
    class B feature
        whatever
            do
                nested.try_something
                if nested.has_error then
                    -- An error has been reported by "nested".
                elseif something_else_goes_wrong then
                    has_inner_error := True
                    log.error ("Something goes wrong.")
               elseif has_minor_issues then
                     log.warning ("Be careful.")
                end
            end
        has_error: BOOLEAN do Result := nested.has_error or has_inner_error end
        has_inner_error: BOOLEAN
                -- Some error that is not one of the errors reported by `nested'.
    feature {NONE}
       nested: C
    end
    
person Alexander Kogtenkov    schedule 16.11.2014
comment
Спасибо, но, к сожалению, это тривиальный пример, потому что задействованы всего две процедуры. Я забыл упомянуть, что знаю, как работает обработка ошибок в Eiffel. Моя проблема в том, что я не могу найти существенные образцы, которые включают более пары процедур. Например, в вашем коде, предполагая, что ошибка не может быть устранена, будет ли interface сообщать об ошибке таким же образом (через состояние)? И будет ли его вызывающий, в свою очередь, сообщать об ошибке (через состояние)? Как связаны ошибки? При поиске и устранении неисправностей одна ошибка более низкого уровня может быть неясной, но точно так же может быть только ошибка более высокого уровня. - person Eleno; 16.11.2014
comment
@Elena, чтобы быть более конкретным, вы хотите распространять информацию об ошибках, используя исключения или состояние объекта? Кроме того, не могли бы вы подробнее рассказать о своем примере, чтобы ответ был более конкретным? - person Alexander Kogtenkov; 16.11.2014
comment
Я хотел бы знать, как информация об ошибках распространяется в Eiffel. Вы сказали, что Eiffel защищает использование состояния объекта вместо исключений, и я знал это, но я хотел бы знать, как такая политика выглядит на практике, помимо тривиальных примеров, когда приложение должно предоставлять подробные диагностические сообщения. По этой причине я предложил приложение, использующее сетевые средства (где многое может пойти не так), но я не буду возражать против реального примера. Есть ли приложение Eiffel, источники которого я мог бы прочитать? - person Eleno; 16.11.2014
comment
@Elena, универсального механизма распространения не существует - разработчики приложений сами решают, как распространяется информация об ошибке. Я добавлю к своему ответу несколько примеров. Что касается источников, вы можете взглянуть на EiffelStudio и найти класс ERROR и его потомки и клиенты. - person Alexander Kogtenkov; 16.11.2014
comment
Точно. Я знаю, что универсального механизма распространения не существует. Поэтому хотелось бы увидеть, как опытные разработчики приложений решили эту проблему. Может быть, они выработали какие-то закономерности. Я подожду ваших дополнительных примеров, прежде чем взглянуть на EiffelStudio, потому что я думаю, что мне будет слишком сложно понять это (я не разработчик Eiffel, я просто смотрю на Eiffel в поисках вдохновения). - person Eleno; 17.11.2014
comment
Привет, Елена, в своих программах я просто всплываю сообщениями об ошибках точно так же, как это делают программы на языке C. Поэтому я проверяю, удачен ли результат вызова, если нет, копирую сообщение об ошибке и устанавливаю is_ok в false. - person Berend de Boer; 18.11.2014
comment
Одно дополнение (см. Также мой другой ответ): в моих программах http fastcgi я могу использовать исключения, так как я не восстанавливаюсь после них. Итак, вызовите исключение (разработчик), это перехватывается на высокоуровневом предложении восстановления, клиенту возвращается сообщение 503, и процесс завершается. Это работает, поскольку я создаю новый процесс для каждого вызова. - person Berend de Boer; 18.11.2014
comment
@AlexanderKogtenkov: Очень интересные дополнения, спасибо. Насколько я понимаю, вам даже не нужно передавать сообщения об ошибках, потому что каждый объект в цепочке вызовов сможет запросить следующий объект о причине его сбоя. - person Eleno; 18.11.2014
comment
Если возможно, я все же хотел бы просмотреть исходный код некоторых приложений или библиотек, реализующих вторую альтернативу. Есть ссылки? - person Eleno; 18.11.2014
comment
@Elena, в исходном коде EiffelStudio вы можете перейти по ссылкам, чтобы получить исходные коды классов trunk/Src/Eiffel/eiffel/interface/external_class_c.e и trunk/Src/framework/configuration/compiler/interface/conf_consumer_manager.e и ищите last_error. Первый класс создает новую специфичную для компилятора ошибку из специфической для конфигурации ошибки. Поскольку используются исключения (но только для сигнализации об ошибках, а не для передачи их значения), это гибридное решение, основанное на подходах 1 и 2, упомянутых в ответе. - person Alexander Kogtenkov; 18.11.2014

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

person Berend de Boer    schedule 17.11.2014