People around me often hear me mumble things like "I think we are missing a level of abstraction here". This is something that often happens when I help people understand how to efficiently unit test a piece of code. But right before Christmas I was working in some code that perfectly highlighted that all abstractions are not equal.
The code in question was pretty straight forward. It had five levels of abstraction where each level had a very clear responsibility. At first glance it looked like this code was going to be a breeze to work with. Turned out that was not the case.
The reason was that each layer was so specialized that rather than abstracting things away for me they forced me into a very specific implementation making faking and modification harder. For example one layer was a buffering layer. But I didn't want buffering...
I think what had happened was that somebody confused abstraction layers with the single responsibility principle. In the code in question (I keep being vague here to protect the innocent) I agree there were definitely the need for five different classes in order to follow the single responsibility principle. But there was no need for five levels of abstractions! Two levels would have been enough but even three would have been acceptable.
The best example is buffering. Whenever you want to buffer things you don't want a new abstraction.You want to hide that functionality in another abstraction. A splendid example is the BufferedStream; it is just a stream wrapping another stream rather than a complete new interface.