You want your code to be made up of small pieces that can be tested and understood in isolation.
Each small piece should do one thing and do it well, whether that piece is a function, and method, or a class.
You want to be able to compose larger pieces of functionality out of these small pieces, in such a way that each composition, no matter how complex internally, remains simple at the level of abstraction of that functionality.
In other words, even at the high level, we should still be able to describe complex internals in tems of simple externals.
This is what Andrew Koenig means when he says "Abstraction is selective ignorance". By intentionally giving up knowledge of how something wmay work internally, we can consider not how it works,but what it does.
Let's give a short example. At the high end, we might say, "this class find that smallest int in some data structure". That tells us what it does, not how it does it, and at this high level of abstraction, that's all we care about.
We have a something that does something, and its modular, we can replace it with anything else that does the same thing, no matter how does it. That's having a public interface.
Now at a lower level, it may be that how this works is that internally it's a heap, or a priority queue, or whatever.
And those things may be implemented in terms of trees, or self-balancing trees, or even (sub-optimally) a linked list. A linked list would be a sub-optimal implementation, but as long as it did that same thing, we wouldn't really cafe, because if the suboptimality got back enough to slow down our program, we could swap it out for a better implemenation with the same interface.
And those things are implemented in terms of tree traversals (Pre-Order: always go in the order left, parent, right). And those things are implemented in terms of simple operations on nodes.
And here's the important part: because the rest of out app has no reliance on the internals or side effects of that module, the swap out of a poorer implementation for a better one changes none of our other code, it just speeds things up overall.
Each layer communicates only with the layers immediately above and below it, so that each layer can be replaced. Visually, it looks like circles inside circles inside circles, not like the overlapping of a Venn Diagram.
If you're having to rely on intuition, it suggests that your code has side-effects or that it has overly broad interfaces to other modules, or that instead of interfaces at all, instead of being code that is built up of self-contained, non-intersecting, modules, you have overlaps and intersections and fragile code. That you don't have it broken down into pieces simple enough to understand.
(Crap, it's late,and I fear I could have broken down this explanation better. I'll be back to edit this.)