Does "good" == "pure" Python domain objects? Everybody at least claims to want this, but much wrapping and componentization is required, and I suspect once people try to troubleshoot a fully componentized application, they will long nostalgically for the days when you just subclassed the shit out of stuff, because it's damned hard to backtrack through a componentized app that you didn't write yourself (or wrote two years ago).
But my best suggestion for separation of data and presentation in Zope 2 in its current state: write a class that is a "Zope class" (something that has all of the murky bits like the ZMI web interface and security declarations and whatnot) and either subclass a "pure" Python domain object or delegate to one from within this class. This is kind of a static adapter pattern I suppose.
But IMO you can write fairly good Zope 2 code that isn't componentized. You just need to accept its limitations and be at one with the fact that you're in a framework that dictates in certain non-negotiable ways how you should think about the problem. Otherwise, you end up in the hellish cycle of "fighting the framework" because you don't agree with some core assumptions made early in Zope's lifetime.
The best thing about Zope 3, IMO, is what it *doesn't* do, which is coddle the "through the web developer", who, also IMO, doesn't actually exist. There was this notion in Zope's early days that almost everything could be done through the web without writing Python class code. When it became evident that the work towards infrastructure required to support this was just too large of a task to chew off (e.g. ZClasses), the TTW stuff was rebranded to be the domain of "site developers" who could use the web interface to customize bits of a site with their browsers (edit Python Scripts, DTML, Page Templates), etc. However, these are the people who don't actually exist, because usually it's the developer who winds up doing this anyway. And to make matters worse, no developer actually ever wants to make coding changes inside the ZODB, because typically he needs to version control the code in some external SCM system. So... we wound up with "skins", which are filesystem files that pretend to be ZODB objects. This actually works really well for most apps. But you're right, it's damned confusing.
IMO the real issue isn't really one of "good" vs. "bad" code. Abstraction isn't always a good idea; if you need it, you can get it out of Zope 2, but if you don't, you're not forced to use it. The issue is that the framework is schizophrenic because Zope was hyped so heavily as a completely-TTW-driven development environment. No attention was paid to creating sane APIs for Python programmers, no attention was paid to developer docs (and still for the most part isn't), and so forth. So from a Python programmer's perspective, Zope 2 forces you write code which fulfills contracts that you really probably don't (and cant easily) understand, because the API is a mess. If it were instead hyped as a "Python-developer-friendly" system instead of a TTW development environment, things would likely be much clearer. I have great hopes for Z3 in that respect. Although calling it "Zope" is a bit of a stretch. ;-) I don't have great hopes for any kind of backwards compatibility, despite efforts being made towards that goal.