Chris Uppal said:
Contrariwise, there is no reason why bbb() should expose its relationship with
inner() -- that is also breaking abstraction.
If bbb were exposing its relationship with inner, then that would be
breaking abstraction. That's not what's going on, though. bbb is
making the (true) statement that it can fail in certain ways. Whether
it can fail that way because it calls inner or for some other reason, is
immaterial and is properly hidden behind an abstraction boundary.
It seems to me you're proposing we break that boundary. What would
strike me as odd is if bbb needed a different publicly visible
declaration depending on whether it throws IOException by using a
FileInputStream directly, or by calling inner to do the same thing.
That is not a choice that affects anyone except bbb.
Another way of putting the same thing: if it's too hard to construct and
throw some complex exception directly, I ought to be able to say
private void throwException(ComplexData d) throws ComplexException
{
...
}
then I could reasonably expect that the line:
public void foo()
{
...
throwException(myComplexData);
...
}
would be approximately equivalent to a throw statement. Specifically,
it should fail validation. But it seems that you're saying that it
should be fine, because it's just acting as a carrier for that exception
and shouldn't need to know about it. Yet that's not really true. In
every way, my foo absolutely knows that it may throw that exception, and
it should know. I just made the irrelevant implementation choice to
encapsulate some complex block of code into a separate method. I don't
want my static validation to break as a result.
In fact it is complicating the relationship between aaa() and bbb() by
introducing irrelevant (to them) complications and conditions on how
they fit together.
How is it irrelevant? It's only irrelevant if there's some other way
for outer to know that it's indirectly calling inner or otherwise
incurring those error conditions. But ANY other way for that knowledge
to exist besides via aaa is necessarily violating the abstraction
boundaries defined by aaa. That is necessary because I can make that
piece of knowledge false simply by changing the implementation of aaa;
and changing the implementation of aaa is right at the core of what
abstraction is supposed to protect me from.
I think we are still differing in terminology -- this time about "contract".
For me it is about assigning a certain responsibility, and (perhaps) policing
whether that responsibility is met. Specifically, it's an architectural thing:
the Achitect has deemed that <this place in the code> shall be responsible for
handling errors (etc) detected in <these places in the code>.
What if the "Architect" has forgotten to do so?
But we can't do that without accepting one or another of several rather
unappealing options: we can increase coupling and reduce flexibility by
requiring that every method "carry-forward" the contract of everything it
touches;
This isn't nearly so unreasonable as you make it sound. The fact is
that aaa CAN fail in those ways. Your choices are to declare it, or (in
some other language) to not declare it. The only arguments I see for
not declaring it are:
1. The Java language features are not sufficient to capture everything
you want here; e.g., the callback example elsewhere.
2. It's too much typing.
I don't see anything else that's valid. How could this possibly
increase coupling? The coupling is there whether you choose to ignore
it or not. Flexibility? The obligation exists to handle that error
whenever you call aaa, whether or not you declare it. These attributes
are all common to the validated and non-validated situations; it's just
that in one case, someone holds it all in their head.