I find that these days I prefer my complexity to be obvious. Things with hidden complexity aren’t any less complex, they’re just less obvious. If you make all of your complexity obvious then you know which pieces of code are complex and you can focus your attempts at simplification on the right places…
Some of this comes from the testing that I’ve been doing; liberal use of parameterize from above means that an object that uses several services is explicitly given those services when you create it. This makes it easy to test as you can replace service providers with mocks. The complexity in the object is explicit because you can see it when you create it. It’s obvious that it requires the services that it uses because you have to provide them at creation time. Some of it from a general distrust of ‘dumbing down’; I’ve always thought that dumbing down is for the dumb…
The result of obvious complexity is that you need to make decisions before you can use it. You need to think. Taking The Server Framework as an example; the server requires that you provide a object that implements the
IIOPool interface, a socket allocator and a buffer allocator. At present the socket and buffer allocators are concrete objects, but they would have been interfaces if the code had been developed test first and they will probably become interfaces at some point in time. The server looks complex. Some might say it’s harder to use because the complexity is obvious and that it would be better if the designer of the object made these decisions for the user. I’d prefer the user to learn a little more about the code they’re using and make the decisions themselves. It makes my job, as a designer, easier, I don’t have to know more than all my potential users, I can be stupid and it doesn’t matter because the code is flexible and they can configure it how they want to. The user needs to take a little more time, perhaps, to understand what’s going on; but once they do, they can make decisions that are appropriate for them.
I find that the complexity rises up the layers of code to the point where the decision can be made and at that point a new class is created that contains those decisions. Sometimes this decision point is at the top of the code, in
main; sometimes, often, it’s lower down. My point, I think, is that I like code where the decision point is under my control. For me this reduces risk. If I decide to use code where the decision point is inside the library or framework then I am tying my success to the fact that the library or framework designer knew better than me and made the right decisions. I don’t mind the designer providing me with sensible defaults, I do mind when some things are made impossible.
I guess this is related to Joel’s Law of Leaky Abstractions. I’m accepting that my abstractions can never be “perfect” for users that I don’t know who have uses that I haven’t thought of. In deference to them, I prefer to build in some flexibility. Providing flexibility means not making decisions that can’t be changed and making the complexity obvious and so allowing your users to understand the complexity and make appropriate decisions.