问题
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?
回答1:
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:
(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.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
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
回答2:
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.
来源:https://stackoverflow.com/questions/26956743/examples-of-error-handling-in-eiffel