Abstraction, simplicity and predictability

As a short follow-up to my last post on reasoning about code, I wanted to touch upon abstraction in general - meaning "information hiding" in the general sense, and not necessarily a particular programming mechanism such as polymorphism or some such thing.

Abstraction buys us a number of good things for a design - the ability to think about things using fewer concepts (because we can focus on the more general stuff) and the ability to change system behavior by changing an isolated part (the abstracted part), to name the first two that come to mind. In general, abstraction is a great way to have the consumer of the abstract focus on something, by hiding or diverting focus away from the implementation particulars.

There is a price to the use of abstraction, however. Well, there's more than a single thing in practice, but I want to focus on one. When you choose what to hide, you may run into the risk of making the system behavior harder to predict, and as such more difficult to reason about.

For example, if my abstraction doesn't explain some basics of the performance characteristics of the implementation, my only recourse is to profile to understand how the system will behave overall (you should profile anyway, but that's a separate matter). Or an abstraction might not give clear rules as to what happens when an object enters an error state - should you "not touch it", try to dispose of it, continue using it, or what?

I'm not advocating any particular solutions or tradeoffs here, but instead I just want to bring awareness to the fact that there is a trade-off to be considered. It's good to know how others will reason about our code, even if just to say "this isn't specified and you can't count on anything, so you should be careful and something". Whether the "something" is 'ignore this', 'bail out', 'work around this', 'assume worst case', 'terminate the process', or something else, there should still be a clear way of explaining and reasoning about our code.