Examples of error handling in Eiffel

1k views Asked by At

I can't find any substantial example of error handling in Eiffel. I have only found examples that either are trivial, or they completely ignore errors, or they leave error handling to the reader. I am interested in knowing how errors can travel through the call stack in absence of exceptions. For example, I would like to know how an application that sends a network request would inform the user of a network problem that has been detected down the call chain. Something like that.

--

EDIT: I do know the basics of error handling in Eiffel (statuses and exceptions). However, I can't find any substantial example on how applications handle errors via statuses. How are failure statuses chained?

2

There are 2 answers

10
Alexander Kogtenkov On BEST ANSWER

Eiffel advocates use of an object state instead of exceptions. In that case clients may figure out what they expect in case of an error and handle it properly. For example,

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

The client can then reason about the state of the supplier object and continue using it in a predictable way:

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

In presence of exceptions one cannot deduce what should be done in what case except from some documentation. Also, it prevents from using automatic tools that can easily check that the use of response is perfectly valid in the code above.

If an error happens deep down the stack, an exception mechanism can be used with rescue/retry. However it may introduce close coupling between low-level network component and user interface that has nothing to do with details of network failure. In the simplest case, the network class will call {EXCEPTIONS}.raise with an appropriate message. A more specific approach would be to create an object of type EXCEPTION (or a descendant), to set the corresponding message by calling set_description on it, and to raise an exception by calling raise. The user code that will handle the exception may look like.

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

EDIT

A specific way to handle nested errors depends on the application design and seems to be irrelevant to the language. Possible alternatives are:

  1. (If exception mechanism is used, not recommended.) After catching a (lower-level) exception and handling it to restore the class invariant, a new exception is raised without cancelling the previous one. Then a query {EXCEPTION}.cause can be (recursively) used to access nested exception objects.

  2. A mechanism similar to the previous one can be used. However instead of creating new objects, a class can delegate a request for details to a lower-level class. For example,

    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. Logging facilities can be used. They can differentiate error severity, specify sources, etc.

    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
    
0
Berend de Boer On

In addition to the answer from Alexander, it is sometimes convenient to use exceptions. In Eiffel we don't tend to catch them (usually class invariants have become invalid), but for certain applications you just don't want to deal with errors. If there's an error, you simply stop, and rely on a retry by something outside the program. Examples of libraries who use that approach are ecli and eposix.