How to work with interfaces that can throw something?

Often I find myself in a situation where I have to deal with some exceptions created by interface developers. This usually becomes problematic when different implementations deal with completely different types of devices, etc .: Depending on the implementation, the number and types of exceptions differ very much. Here is a typical example:

interface DataStream { Data ReadNext(); } class DeserializingFileDataStream : DataStream { //this one does file operations + serialization, //so can throw eg IOException, SerializationException, InvalidOperationException, ... } class NetworkDataStream : DataStream { //get data over tcp //so throws IOException, SocketException } class HardwareDeviceDataStream : DataStream { //read from a custom hardware device implemented in unmanaged code //so throws mainly custom exceptions } 
All this probably also causes ArgumentExceptions, etc., but I am not interested in catching them: in this case they will indicate programming errors. However, I do not want the program to crash when the file is damaged, the network cable is disconnected, or the user device crashes, so other exceptions should be considered.

I have already tried a couple of solutions, but I donโ€™t particularly like any of them, so the question arises: is there any common practice / pattern for such situations? Please be specific and do not tell me to "take a look at ELMAH." Here are some things I've used in the past:

  • catch( Exception ) Well obviously the problems
  • TryAndCatchDataStream( Action what, Action<Exception> errHandler ) method consisting of an attempt, followed by a catch for any exception of interest to any implementation. This means that it must be updated when implementations are changed or added / removed.
  • add a comment to the interface, which says: "you just need to throw DataStreamExceptions", and in all implementations you should follow this rule, luring something interesting and wrapping it in a DataStreamException, and then throwing it. Not so bad, but it adds noise to every implementation.

Then I have a second more general question about exception handling (don't think that you need to make a separate entry): I have some cases where method A calls B, which calls C, which calls D, and D throws some exception, however, caller A gets an exception. Isn't that a sign that something is wrong with the code? Because, in order to be able to do this, the caller A must know that A will eventually call D. If A's documents cannot throw SomeException; but in both cases, this means that when updating a simple implementation implementation detail A (i.e. make it call something else than D), this change is displayed to users of A.

+4
source share
5 answers

It is impossible to โ€œknowโ€ what an unknown exception might be. All you can do is to catch the exceptions that you know about, and possibly register those that you donโ€™t have and restore them.

You can catch an Exception and then call the method in a separate assembly, which determines what to do with excpetion. You can then update the assembly as needed without updating the rest of the code.

Regarding your second question, A needs to document any exception that might bubble from it, including the exceptions thrown by dependent objects. B must document the exceptions it throws, so A can know about them, etc. Etc.

If you change A to do something that causes a change in exceptions, you must change the documentation. From the perspective of promising applications, all he knows is A. A can do anything, and the calling application doesnโ€™t care if he is responsible for B, C or D. A for everything that gets to the caller.

Think of it this way. Suppose you hired a construction company to build your home. They, in turn, hire subcontractors. Those Subcontracts could hire their labor. If lower-level workers are screwed, the final contractor you hired is to blame, regardless of whether it was some other company that hired them. You do not care, you just want your house to be built to specifications, and the construction company is interdependent.

+4
source

You can use an abstract class instead of an interface to wrap exceptions from various implementations in your base class so that you can wrap basic exceptions in the usual way. This way, you have one central point to change your exception handling and logic analysis. Some exceptions can be serious, which should still lead to the end of your process, such as OutOfMemoryExcepptions, where it is most likely not safe to continue working.

In case you insist on an interface, you should wait until some future version of the CLR, where the interfaces can have a basic implementation. There is a Vance Morrison video about this feature, but I never heard of it again.

If you cannot wait for the CLR vNext, you have another opportunity to create a wrapper class (let's call it DataStreamReader) that accepts the IDataStream interface, which also implements IDataStream, but does the packaging as you wish. If you make sure that in your code where you read, use only instances of DataStreamReader, you should be fine.

The design forces any inherited to implement the ReadNextImpl method, where your consumers implement their logic. You still call ReadNext and get the current simple implementation, which always returns a DataStreamException. This is the simplest solution I could come up with.

  class Data { } public class DataStreamException : Exception { public DataStreamException(string message, Exception inner) : base(message, inner) { } } abstract class DataStream { protected abstract Data ReadNextImpl(); public Data ReadNext() { try { return ReadNextImpl(); } catch (Exception ex) { throw new DataStreamException("Could not read from stream. See inner exception for details.", ex); } } } class DeserializingFileDataStream : DataStream { protected override Data ReadNextImpl() { throw new NotImplementedException(); } } class NetworkDataStream : DataStream { protected override Data ReadNextImpl() { throw new Exception(); } } 
+3
source

Basically, a code that catches an exception should know a few things:

  • What happened?
  • Is any specific action required
  • After performing the above action, if it is performed, an exceptional condition is considered permitted.
  • What condition will any applicable objects be in?

Unfortunately, many languages โ€‹โ€‹and frameworks, including C ++, Java, and .net, try to use one rather awkward kind of information (the type of an object of an excluded exception) to convey all three pieces of information, even if in practice they are often orthogonal in many ways. A particularly noteworthy complexity of this design is that several exceptional conditions arise at the same time, so that the handlers applicable to ANY of them must be launched, but the exception must continue to the stack until they are processed otchiki applicable to all of them.

Despite the limitations of type-based design, you need to work with it. When developing your own custom classes / interfaces and the exceptions that arise from them, you should try to use different types of exceptions to answer question No. 3- # 4 above, since these are questions that are most likely to be interesting to the caller. I would suggest that it might be desirable for classes to have a state variable or flag that might be invalid if an exception thrown inside the method is likely to leave the class in an invalid state, possibly using a pattern, for example:

  ... before doing anything that might cause object to enter invalid state even temporarily ...
   if (state! = MyClassState.HappyIdle) throw new StateCorruptException (....);
   state = MyClassState.UnhappyOrBusy;
   ... manipulate state in a fashion that will end up with a valid state when complete ...
   state = MyClassState.HappyIdle;

If such a pattern is used sequentially, callers do not have to worry too much about the possibility that the operation that led to the exception could leave the object in a damaged state, so trying to continue the operation may lead to further data loss, if the object was damaged, but the caller the code ignored the exception that occurred when it happened, further attempts to use the object will fail.

Unfortunately, many classes are not well protected with the help of such guards, so it is unsafe to assume that unknown exceptions are "harmless." On the other hand, from a practical point of view, there is no approach that is reliable and safe. Either the risk that one code dies is useless for an exception that would actually be harmless, as well as the risk of damage to damaged data structures that go out of control and distort other data structures. A better exception system would have better alternatives, but not with the existing system.

+1
source

You can implement your own exception. Have each implementation if the interfaces catch their own exceptions and throw your own exception, setting a real exception for the internal one. This may be good depending on your case.

Because exceptions should be exceptional. That way, you would at least know if you had exceptions that were implemented for implementation.

Not sure if this is a good idea or not. Just thought I was proposing this idea.

0
source

Java forces you to declare which exceptions you throw, and this is a real nasty pain with very few advantages - usually because exceptions are excluded in exceptional circumstances and it is difficult to plan them.

The fact that Java programmers often end up (after learning not to use throws Exception ) is your third option. This third option makes sense for exceptions that you don't expect your call code to know how to handle - if you want the calling code to handle them (say, repeat three times if an IOException or NetworkException is thrown), you better stick to what people already know.

In short, I recommend that you catch the exceptions that you know how to handle in your code code, and simply report those that you cannot handle and exit (or not perform the operation).

0
source

Source: https://habr.com/ru/post/1393596/


All Articles