If the expressive code was written with a good level of abstraction, it should be pretty straightforward to figure out what it does.
Abstractions are necessary, of course, but they also leak. Good judgment in these issues is one of the hallmarks of an experienced programmer. I'm just finding in my own code that I'm gravitating towards less abstraction, not more.
Clojure in it's current incarnation is a great example of this problem, IMO. It provides a very expressive and highly abstract interface to the JVM and java libraries, but once you hit a stack trace the abstraction comes tumbling down and you have to start picking through the mixed java/clojure stack trace to figure out what went wrong. Throw macros into the mix and things get even hairier. I've found that in some cases it's better to just bang out vanilla java. Sure it takes more LOC to get the same things done, but the result is often dead-easy to understand and debug.
Like everything else in engineering though, it depends a lot on exactly what you're trying to build.
Good stack traces are about the maturity of the compiler and tool support- they have absolutely nothing to do with clarity of the language. Clojure is simple and Clojure opts for explicit context over implicit context almost everywhere (dynamic binding being a big exception)- this is exactly what Linus argues for. All you have in Clojure are functions and values - how much straightforward can you get? No objects, no hidden behaviors, no private variables, everything in namespaces, etc.
After two years of hacking on Clojure, I haven't really found macros to obfuscate anything. But that's because I learned not to use them unless I need them.
But, yeah, I'm looking forward to the community growing and providing better error messages and tools for deciphering raw Clojure stacktraces.
You're looking at Clojure from the point of view the language specified by the docs though, and I'd agree as far as that goes.
From another point of view, Clojure, like all higher level languages, is just an abstraction over an underlying machine. From this angle and in it's current state it's (IMO) a pretty leaky abstraction.
I think it's fair to say that this has more to do with the implementation than the design though.
C is just an abstraction over the underlying machine. The amount of tooling to required to make C programming bearable is staggering - how much leakier can you be?
Clojure in it's current incarnation is a great example of this problem, IMO. It provides a very expressive and highly abstract interface to the JVM and java libraries, but once you hit a stack trace the abstraction comes tumbling down and you have to start picking through the mixed java/clojure stack trace to figure out what went wrong. Throw macros into the mix and things get even hairier. I've found that in some cases it's better to just bang out vanilla java. Sure it takes more LOC to get the same things done, but the result is often dead-easy to understand and debug.
Like everything else in engineering though, it depends a lot on exactly what you're trying to build.