Encapsulation & Exceptions

A place to discuss the science of computers and programs, from algorithms to computability.

Formal proofs preferred.

Moderators: phlip, Prelates, Moderators General

Encapsulation & Exceptions

Postby shawnhcorey » Thu Feb 23, 2012 4:47 pm UTC

I'm design a OO language and I was happily going along until I hit exceptions. Then I realized that most exception schemes involved violating encapsulation. For example: if the was a class A which has an object of class B which, in turn, has an object of class X. If X throws an exception, which B ignores, then it is often possible to have code in A that handles it. This violates the encapsulation of X. If B is changed so it no longer uses X, A still has code for it. And if A has another class C that is changed to start using X, A would handle exceptions from X thinking it has passed through B, not C.

So, my question is: Should this be allowed or should exceptions be restricted to its own class and any class that immediately contains it?
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby mousewiz » Thu Feb 23, 2012 9:56 pm UTC

Are you going to stop someone from changing:
Code: Select all
public void read() throws IOException {
    is.read();
}

into:
Code: Select all
public void read() throws IOException {
    try{
        is.read();
    } catch (IOException e) {
        throw e; // Or new IOException() or whatever
    }
}


Because I'm thinking that's what you'd get... if you don't know how to handle an exception at some point, what else are you proposing be done? I think it has all the same problems, plus it generates a lot of extra ugly try/catch code.
mousewiz
 
Posts: 87
Joined: Wed Oct 26, 2011 6:50 pm UTC

Re: Encapsulation & Exceptions

Postby shawnhcorey » Thu Feb 23, 2012 10:30 pm UTC

I was thinking of getting around all those try-catches with a change in syntax:

Code: Select all
log is a File:
   mode is append,
   path is "./log.txt",
   when permission denied:
      throw permission denied,
   when could not create:
      die "cannot log actions",
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby mousewiz » Thu Feb 23, 2012 11:25 pm UTC

I might be missing something, so feel free to point it out if you spot it.

In your original example, you want to address this problem:
- A uses B which uses X
- X throws an exception
- B ignores exception, so it gets sent up the handler chain to A
- A handles exception, there is potentially some ickyness here

You propose that the exception should be handled in X, or in B, but should go no further.

What my previous post was meant to highlight is that when B handles the exception that originated in X, there is nothing to stop B from 'handling' the exception by just re-throwing it (or a new exception), which still results in the ickyness of A handling the exception. If B was previously ignoring the exception, then I'm not sure what else you would do.

Although your syntax probably looks better than try/catch blocks, I'm not sure how it prevents A from handling an exception that originated in X.
mousewiz
 
Posts: 87
Joined: Wed Oct 26, 2011 6:50 pm UTC

Re: Encapsulation & Exceptions

Postby shawnhcorey » Fri Feb 24, 2012 12:16 am UTC

Exceptions would no longer be global; they would be part of each class API. B can't re-throw X's exceptions. The best it can do is copy all the data from X's exception into its own and throw that. If, for example, you were to rewrite B so that it no longer uses X, you would remove the exception from B's API. And since its API changed, you would expect all classes that use it to change to match the new API.
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby phlip » Fri Feb 24, 2012 3:44 am UTC

A concrete example: A is some program that has to process images. B is an image-processing library. X is a file-handling library.

A needs to load an image, but the file doesn't exist. It says "load this image" to B, and then B says "open this file" to X. X throws a file-not-found exception. It is entirely reasonable for B to allow that file-not-found exception to propagate up to A. A is the place where handling for that exception can reasonably be placed. And it certainly doesn't break the encapsulation of B to do so - the expected result of calling "load this image" with an invalid filename is a file-not-found exception.

Your suggestion would, essentially, result in just having to write something along the lines of:
Code: Select all
public void loadImage(String filename) throws imglib.FileNotFoundException
{
  try
  {
    inputStream = new FileInputStream(filename);
  }
  catch (java.io.FileNotFoundException e)
  {
    throw new imglib.FileNotFoundException(e);
  }
}
Which... is certainly a thing that some Java libraries will do, and which makes sense where there's a logical disconnect between the problem viewed at the B/X layer, and the problem viewed at the A/B layer... but it just results in a lot of clutter if you force it unilaterally...
While no one overhear you quickly tell me not cow cow.
but how about watch phone?
User avatar
phlip
Restorer of Worlds
 
Posts: 7185
Joined: Sat Sep 23, 2006 3:56 am UTC
Location: Australia

Re: Encapsulation & Exceptions

Postby shawnhcorey » Fri Feb 24, 2012 3:24 pm UTC

phlip wrote:A concrete example: A is some program that has to process images. B is an image-processing library. X is a file-handling library.

A needs to load an image, but the file doesn't exist. It says "load this image" to B, and then B says "open this file" to X. X throws a file-not-found exception. It is entirely reasonable for B to allow that file-not-found exception to propagate up to A. A is the place where handling for that exception can reasonably be placed. And it certainly doesn't break the encapsulation of B to do so - the expected result of calling "load this image" with an invalid filename is a file-not-found exception.


My gut reaction is that B should create its own "image file not found" and add in the image attributes any information sent by the file handler. If A just receives a "file not found" exception, it does not know what image is associated with it. Combining all the information together in B would make it a more useful package.
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby freakish777 » Fri Feb 24, 2012 3:32 pm UTC

Since it's your own language, couldn't you have a Global Exception Handler defined (much like a Garbage Collector)?

For the record, I don't think this is a good idea (for a lot of reasons, mainly because it's a lot of work to make useful), but instead of bubbling exceptions back up the call stack, you could have a Global Exception Handler that would handle exceptions when they happen.

So, using philip's example of:

A is some program that has to process images. B is an image-processing library. X is a file-handling library.

A needs to load an image, but the file doesn't exist. It says "load this image" to B, and then B says "open this file" to X. X throws a file-not-found exception


Your Exception Handler handles X throwing "file-not-found" immediately. You could do this with a Pub/Sub pattern where exceptions publish to some Exception Queue, and Exception Handlers subscribe to different types of Exceptions in that Queue (your language could have built in Exception Queue and Handler object types, and have an always running global Queue and Handler), and allow people to write their own specific Handlers ("I want to sub to only SQLExceptions so I can write my error log to disk"). This could make sure you aren't breaking encapsulation (depending on how you do it).

Problems with this:

1. You're potentially having to force handling of all exceptions to maintain encapsulation. Changing the example, say X doesn't handle it explicitly, normally this would halt your app, and the unhandled exception would still bubble back up through the stack trace (which could break encapsulation again). So you make all exceptions go to the Exception Queue instead where the Handler will process it. This would add a lot of bloat, and make your language run a lot slower (in essence I think it'd be the same as writing a language in which "Every Line Of Code Gets It's Own Try/Catch").

2. If maintaining the normal flow of your app when you hit an exception is necessary, you'd need to have some sort of "token" to stand in for the exception that does get passed back up through the program anyways. If you don't, and you hit an exception, your app halts and either A. doesn't go back up the call stack (it just halts, period, which is probably Very Bad, since you still want to do things like close connections, free resources, return something to waiting processes, etc) or B. attempts to go back up the call stack (from X to B) where it will have another exception, which goes to the Exception Queue for the Handler to process. And then back up the call stack (from B to A) where it will have another exception and so on all the way up the call stack.

3. You could end up with some Very Bad infinite loops. What happens when a custom Exception Handler throws an exception? You then need some specially defined Exception type for when that happens, and not allow anything except the Global Exception Handler to be subbed to that type of exception (if even that, maybe you just halt your app at that point with an "Unrecoverable Exception Occured" Error written to the OS's event log).
User avatar
freakish777
 
Posts: 350
Joined: Wed Jul 13, 2011 2:14 pm UTC

Re: Encapsulation & Exceptions

Postby shawnhcorey » Fri Feb 24, 2012 4:20 pm UTC

@freakish777: The one advantage I can see to this approach is if you wanted more than one object to respond to an exception but an additional problem is that objects have to unregister with the dispatcher. Since programmers are notoriously lazy, this would almost guarantee that in a medium-sized project that one object will complain about an exception that it no longer cares about.

@all: I understand the complaints that forcing expletive propagation of exceptions adds extra work for the programmer. Its advantage is that it creates better understanding to those who come after, the maintenance programmers. So, is the extra work worth the better understanding? I think it is though I'm not (yet) 100% convinced. I'm still entertaining arguments both for and against.
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby Jplus » Fri Feb 24, 2012 5:29 pm UTC

I'm not sure whether we're really talking about encapsulation here, anyway. Or information hiding for that matter, which it seems to be confused with. Let's take the classes A, B and X again. You seem to consider it some kind of interface violation if B ignores an exception thrown by X and if A catches it instead. Let's say the possibility to throw IOException is part of the interface of X. If B doesn't catch that exception, that automatically means that B may also throw an IOException, which makes it part of B's interface as well. This is something that should be documented, which sloppy library developers may fail to do when working with a language that doesn't enforce it, but technically this is how it is.

Compare it with normal return statements. Say X returns a String instance. B may also return a String instance and in fact it may be simply forwarded from X. I see no encapsulation, information hiding or interface violations here.

The primary difference between an exception throw and a return statement is that an exception can unwind multiple levels of the call stack at once. If you don't want that to be possible, perhaps for aesthetical reasons (which I can understand), you don't need exceptions. Just normally return a discriminated union which contains the result if successful or an error description otherwise. Have a look at the Either type constructor from Haskell.
Feel free to call me Julian. J+ is just an abbreviation.
Image coding and xkcd combined
User avatar
Jplus
 
Posts: 1558
Joined: Wed Apr 21, 2010 12:29 pm UTC
Location: classified

Re: Encapsulation & Exceptions

Postby freakish777 » Fri Feb 24, 2012 6:05 pm UTC

Jplus wrote:The primary difference between an exception throw and a return statement is that an exception can unwind multiple levels of the call stack at once. If you don't want that to be possible, perhaps for aesthetical reasons (which I can understand), you don't need exceptions.


Depending on what exactly is in the call stack at the time the error occurs, and how it's unwound, couldn't you actually end up with security risks? Object A has an Object B, Object B has an Object X, and for whatever security reason, A really shouldn't know about the properties and methods of X.

Say A is a Store, B is a Cart (or checkout screen/cash register) and X is an Inventory Manager. B invokes a method of X, (let's say it checks if something is still in stock), which ends up throwing an exception. How do you prevent A (which our user or other code is interacting with) from knowing the specifics of the exception? Usually this is a task that would fall to developers (write more secure code, make sure you handle exceptions gracefully, never show anything but custom error messages, etc), but I could see wanting to "stop" specific exceptions from bubbling up through the call stack and instead replace with a generic error message that can't know anything further than it's own Objects/Properties/Methods (although this makes things harder to maintain, not easier, but then again that's the accepted trade off for more security). Not necessarily that it's a good idea to implement in my opinion, but wanting to.
User avatar
freakish777
 
Posts: 350
Joined: Wed Jul 13, 2011 2:14 pm UTC

Re: Encapsulation & Exceptions

Postby shawnhcorey » Fri Feb 24, 2012 6:08 pm UTC

@Jplus: You're thinking that A, B, and X are all written by the same programmer at the same time. What if X is written first, a year later B, and two years after that A, all by different programmers. For A to correctly handle X's exceptions, the programmer would have to know about B's use of X. In other words, when using a class, read all the code (because the documentation may be incomplete) for all the classes you use, all the classes they use, and so on and on and on and on. On the other hand, if exceptions are only thrown back to the calling object, all you would have to know to successfully handle all exceptions is to know the API for only the classes you use.

I think exceptions should always be used since programmers are lazy; they don't always check a return status to see if it's successful. With exceptions, if the programmer wants the program to continue pass the exception, he must add the code to handle it. Otherwise the program stops with error. This is better than having the program continue, the default with return statuses, with the possibility that it corrupts your data.
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby letterX » Fri Feb 24, 2012 6:31 pm UTC

I feel like most of these issues could be dealt with by something like exception specification, but done properly and made mandatory.

After all, the possible exceptions a function can throw (whether directly, or passed on from a function lower in the call graph) are really part of the return type of a function. Then, if you have a situation where X throws a private exception to B (where B has enough priviledges to see X's private exceptions) and B tries to pass this on to A (which doesn't have the ability to see X's private exceptions) then the exception must be part of B's exception specification, so A attempting to handle it (or indeed call any functions with the possibility of throwing the private exception) will be prevented by the type checker.

If you make exception specifications mandatory on all functions, then checking that they're correct is dead simple (make sure that the specification is the union of all specifications of all called functions, plus anything thrown directly by the function, minus anything caught and not re-thrown). It's more difficult if your functions have a heirarchy (as in C++) but essentially the compiler has to do this step anyways to determine how to build the exception jump tables.

Even if exception specifications aren't mandatory, you can still do a lot of inference to deduce the minimal exception specification for a given function. If you're doing compilation by modules or 'compilation units' then you probably need to specify the exceptions that can be thrown by everything in the interface to the module. Local functions you can deduce the specifications of.
letterX
 
Posts: 526
Joined: Fri Feb 22, 2008 4:00 am UTC
Location: Ithaca, NY

Re: Encapsulation & Exceptions

Postby Jplus » Fri Feb 24, 2012 7:26 pm UTC

@freakish777: Yes, you can end up with additional security risks because of exceptions. There are other problems with exceptions as well (see below). All I meant to say is that shawncorey can do without them and that shawncorey's problem doesn't seem to have anything to do with encapsulation or interface violations.

shawnhcorey wrote:@Jplus: You're thinking that A, B, and X are all written by the same programmer at the same time.
No. You'd know that if you'd read my post more carefully:
Jplus wrote:If B doesn't catch that exception, that automatically means that B may also throw an IOException, which makes it part of B's interface as well. This is something that should be documented, which sloppy library developers may fail to do when working with a language that doesn't enforce it, but technically this is how it is.


shawnhcorey wrote:What if X is written first, a year later B, and two years after that A, all by different programmers. For A to correctly handle X's exceptions, the programmer would have to know about B's use of X. In other words, when using a class, read all the code (because the documentation may be incomplete) for all the classes you use, all the classes they use, and so on and on and on and on. On the other hand, if exceptions are only thrown back to the calling object, all you would have to know to successfully handle all exceptions is to know the API for only the classes you use.
This clearly demonstrates that you're talking about a documentation problem, as I already suggested.

shawnhcorey wrote:I think exceptions should always be used since programmers are lazy; they don't always check a return status to see if it's successful. With exceptions, if the programmer wants the program to continue pass the exception, he must add the code to handle it. Otherwise the program stops with error.
Note that you're almost contradicting yourself here. Either you want to force programmers to add code for handling errors, or you give functions an explicit exception interface and you want code to fail when an exception is forwarded (not caught) while it isn't part of the interface. In the first case you should simply leave exceptions out of your language and force errors to be reported through discriminated unions, as I said before. In the second case you should use mandatory exception specifications, as letterX explained.
Feel free to call me Julian. J+ is just an abbreviation.
Image coding and xkcd combined
User avatar
Jplus
 
Posts: 1558
Joined: Wed Apr 21, 2010 12:29 pm UTC
Location: classified

Re: Encapsulation & Exceptions

Postby shawnhcorey » Fri Feb 24, 2012 8:08 pm UTC

Well, I worked under both MIL STD 2167 and ISO 9000 and from personal experience I can say that making documentation mandatory does not improve the code. It also doesn't improve the documentation. And yes, I was thinking of making the exception handling part of the class API, and therefore, mandatory.
User avatar
shawnhcorey
 
Posts: 42
Joined: Sun Jan 08, 2012 2:08 pm UTC

Re: Encapsulation & Exceptions

Postby korona » Fri Feb 24, 2012 8:37 pm UTC

The concern that unchecked exceptions can break encapsulation is valid.
Suppose A is a program that uses a library X which in turn uses a library Y.
After version 1.2.3.4 the developers of X decide that Y has some severe weaknesses and switch to library Z. But Z comes with its own set of exceptions. Now if A wants to continue using X they have to possibly rewrite all code that calls any function of X.
korona
 
Posts: 362
Joined: Sun Jul 04, 2010 8:40 pm UTC

Re: Encapsulation & Exceptions

Postby Jplus » Fri Feb 24, 2012 10:05 pm UTC

@shawncorey: if you make exception handling mandatory, there is no difference with wrapped return values and hence no reason to add exceptions to your language. But I'll not press the matter anymore.

@korona: what you describe is a library interface change. It's a problem if the change is undocumented or if there is no option to continue using the old interface for a while (i.e. the library devs don't make a support branch for the old version). I do agree that this is an additional risk that comes about by using exceptions, but the only proper way to eliminate that risk is by simply eliminating exceptions. Which is fine by me.

Exceptions add nothing to a language unless you allow them to unwind more than one level on the call stack.
Feel free to call me Julian. J+ is just an abbreviation.
Image coding and xkcd combined
User avatar
Jplus
 
Posts: 1558
Joined: Wed Apr 21, 2010 12:29 pm UTC
Location: classified

Re: Encapsulation & Exceptions

Postby Yakk » Tue Feb 28, 2012 1:39 am UTC

Exceptions are a flow control construct first, and a data construct second.

The data type problems, which you are puzzling over, are very much secondary to the flow control difficulties with exceptions.

As a simple case, if you simply require that every function list the exceptions it throws (with some syntactic sugar to make it easy to include families of exceptions -- not necessarily class-related families (there is more than one way things can be a family), but collections of types of exceptions) then the throwing of an exception by C through B to A is not a problem. B is incapable of throwing an exception that isn't in B's interface, as every function B calls has its exceptions it throws listed, and the compiler checks that functions it calls don't throw anything that B doesn't catch.

The hard part, as far as I am concerned, is a non-sucky syntax for handling the nearly arbitrary jumping around of code flow that exceptions result in, and making marking your methods and functions up with exceptions less of a boring chore, while avoiding the "there must be a root class" annoyance of Java-esque exceptions.
Jplus wrote:Exceptions add nothing to a language unless you allow them to unwind more than one level on the call stack.

Naw. An exception that unwinds a single level gives you pattern-matching like functionality in your function return types, at the least.

If you add the ability to meta-code -- write code that modifies what code in your method means -- you can then wrap all code in your function implicitly in a try-catch block that rethrows things you want to pass through to your parent.

Now we have a language that emulates multi-call stack unwiding, but it is implemented by a static polymorphic return type system (with type-pattern match branching) and automatic code generation, in the hands of the person using it (instead of the language framework) (with, naturally, code generation that does that emulation being pre-written and part of the standard library).

Ie, suppose you allow your language to operate on expressions. The code that operates on expressions is passed the literal context (and what types it will accept), and the expression (and what types it can generate -- where exceptions are implemented as marked-up alternative return types). The "default" meta-code then claims that the literal context can handle the non-exceptional return type. It then takes those exceptional return types that cannot be handled by the default context, and redirects them to the exception handler routine, which returns them as an alternative return type (an exception) from the method.

Another "simple" bit of meta-code instead only throws from a certain list. Now, any exception off that list generates an "unhandled exception" compile-time error. So you can choose to propogate "file errors" and "critical errors" up to the caller, but other exceptions are asserted at compile time not to happen unless explicitly handled.

By making it based on static polymorphic return types, we can have "hard guarantees" that our caller has to explicitly choose to ignore errors. We can even add a new error type, and get compile-time guarantees that our callers have to handle it -- or, derive off of an existing error we throw.

It does get weird, in that the return address we leave a function at becomes a function of the type we want to return, but not all that weird. As we are publishing a complete list of our exceptions, that just means the caller has to push a return address for each of the exception types for us to return to (assuming you are compiling down to machine code), or has to push a pointer to an exception table (which would involve moving less memory around when you call a function, but a tad more work when you return from a function).

At this point, I'm being overly insane.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.
User avatar
Yakk
Poster with most posts but no title.
 
Posts: 10448
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: Encapsulation & Exceptions

Postby WarDaft » Tue Feb 28, 2012 6:23 am UTC

Or we can separate error handling from code. Something like:
catchesWith :: (Error e, Error e') => (e -> e') -> (a -> e' b) -> e a -> e' b

We still allow most code to cause errors, but not allow general code to handle them.
All Shadow priest spells that deal Fire damage now appear green.
Big freaky cereal boxes of death.
User avatar
WarDaft
 
Posts: 1574
Joined: Thu Jul 30, 2009 3:16 pm UTC

Re: Encapsulation & Exceptions

Postby Jplus » Tue Feb 28, 2012 1:00 pm UTC

:)
Feel free to call me Julian. J+ is just an abbreviation.
Image coding and xkcd combined
User avatar
Jplus
 
Posts: 1558
Joined: Wed Apr 21, 2010 12:29 pm UTC
Location: classified

Re: Encapsulation & Exceptions

Postby Yakk » Wed Feb 29, 2012 9:54 pm UTC

I had a funny thought.

Instead of exception chaining, what if you could have more than one exception being thrown in the same throw?

You'd have an ordering on the exceptions. Someone trying to catch the exception would get the "narrowest" version of the exception.

This would allow you to have a tight interface that lists only a few exceptions, but some of these will wrap a more narrow exception. In the case that you do know more details than the interface says, you'd be allowed to catch the more narrow exception. At the same time, if you catch every published exception, you'll catch every possibility...

Exception chaining lets you do this manually, admittedly.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.
User avatar
Yakk
Poster with most posts but no title.
 
Posts: 10448
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove


Return to Computer Science

Who is online

Users browsing this forum: No registered users and 6 guests