This may cover existing topics, but I cannot find one that seems to express the generic issue very well. Maybe if I end up saying the same thing as existing topics, then we can move this.
I understand the need to wrap (abstract) against low-level implementation details. However, I see an ugly trend toward wrapping higher-level interfaces with yet another interface of our choosing. Examples include wrapping GUI and database API's. Sometimes it is to get away from strings, somethings to try to get independent (swappable) from vendor-specific or proprietary API's, and sometimes simply WrappingWhatYouDontLike
, that is, wrapping an interface that you believe to be a bad design.
The problem is that there is not going to be much improvement in design from doing this. Reasons include:
- Creates interface or adapter code that complicates the design and adds more layers to debugging
- Usually existing interface frameworks have certain flavors, patterns, or philosophical models that one is locked into "buying" when an interface is chosen. For example, the TK graphics engine has the concept of "packing" left, right, up, down, etc. HTML does not directly have this and translating between TK to HTML would be nearly impossible if the packing concept is heavily used.
Wrapping strings such as SQL or HTML is a sticky matter. Some see strings as a really ugly thing, some see them only as kind of ugly. Perhaps this should be left to the topic of NoStrings
Ignoring the string issue right now, if you are wrapping against another non-string interface/framework, is your replacemeng going to be significantly better to justify all the translation layers and code?
If the purpose is to be able to swap vendors, can't you just override or replace the vendor A's implementation with vendor B's, but using the vendor A's interface? If the answer is "no", then why would a wrapper solve it?
While you CantAbstractMuchPastInterfaces
, you certainly can abstract over
interfaces. This happens whenever you create higher-level, structured descriptions of intent that you then compile to utilize the interfaces that exist. Creating LanguageIntegratedQuery?
support like SQL, or support for dynamically producing HTML by rapidly formatting variant components and providing any necessary structure, are often quite useful. It is difficult to deny the value of abstracting over such interfaces as OpenGL even if you don't plan to replace it as a back-end: if it allows you to express your intent more directly (at a higher level), that is a GoodThing
. The trick is the 'compilation' part - taking various expressions of intent, unifying them, then formulating sets and sequences of operations or data that meet the intent; compilers are tricky things to write, whether it be done by a runtime library or by a separate compiler/optimizer.
But these are often usage-specific optimizations, domain- or app-specific simplifications. It is not moving to a "higher" abstraction, but rather a local one. For these, I often prefer HelpersInsteadOfWrappers so that I can take advantage of what the existing API/interface already offers, but "take it local" when needed. See FrameworksShouldAutomateNotHide.
I don't agree. If you can express your intent at a 'higher' level, you are certainly moving your own labors to a 'higher' abstraction, even if the compiler is doing all the work to get it back down to the lower abstraction. 'Abstraction' is simply about removing the details about which you don't care, and expressing intent at a higher level involves the same thing. For example, if your intent is only to have the flag up, you don't care about -how- it gets up there, and so having to specify 'how' would be an abstraction failure. And the opposite holds: if your intent is to have the flag up, and you specify exactly that, and something else (e.g. a compiler or library) figures out -how- to do it, then that is an abstraction success - it is undeniable
that you worked at a "higher" abstraction, not the local one.
The above examples are ALL moving to a "higher" abstraction. Even if you're ONLY taking a declaration of which variables you want filled with which data in HTML, this is an example of abstraction because you've avoided need to specify HOW to fill those variables.
As far as 'FrameworksShouldAutomateNotHide
', I disagree fundamentally (encapsulation has great utility, too), but I'll move my disagreement to that page.
RE: "If you can express your intent at a 'higher' level, you are certainly moving your own labors to a 'higher' abstraction, even if the compiler is doing all the work to get it back down to the lower abstraction. 'Abstraction' is simply about removing the details about which you don't care, and expressing intent at a higher level involves the same thing. For example, if your intent is only to have the flag up, you don't care about -how- it gets up there, and so having to specify 'how' would be an abstraction failure."
It is not "higher", it is "specialized" abstraction.
Specialized, yes. Higher, also - if done correctly. For the flag example, we could merely have a function "raiseFlag()". We don't need to wrap everything to make such a function. I am not against such local abstractions, but rather attempts to completely hide the "standardized" abstractions (such as GUI libraries, HTML, SQL, etc.)
A traditional "raiseFlag()" procedure doesn't abstract your intent at all. It only packages one particular mechanism for getting the flag raised. That you must specify 'how' the flag is to-be-raised within the procedure description indicates that it was not abstracted, and is a full and sufficient proof that you didn't write one line of code expressing your intent at a 'higher' level. Packaging is undeniably valuable, but is fundamentally different from abstraction and coding at a higher-level. If you wish an example of writing 'raiseFlag()' at a higher level, consider:
PRECONDITIONS('flag' identified in context, 'pole' identified in context)
POSTCONDITIONS(identified 'flag' properly raised on identified 'pole')
With that, you've described your precise intent (in the form of the postconditions), but you've dropped the details of how the flag gets raised, and thus the process of raising the flag has been (well and truly) abstracted. It has ALSO been packaged (with the 'raiseFlag()' name). But you can tell it has been abstracted since it gives no specification of how to go about raising the flag... just leaves the little details up to the compiler (which is, of course, free to any approach within its physical capability, since you specified no constraints - see CritiqueOfIntentionalProgramming
The 'raiseFlag()' example might better illustrate what I originally meant when I said: "The trick is the 'compilation' part - taking various expressions of intent, unifying them, then formulating sets and sequences of operations or data that meet the intent; compilers are tricky things to write, whether it be done by a runtime library or by a separate compiler/optimizer." I'm not at all talking about "usage-specific optimizations, domain- or app-specific simplifications"
. When I mentioned taking expressions of intent and compiling them, I was, very literally, referring to mechanisms for expressing one's intent directly, at a higher level, then compiling it down, just as our 'higher-level' languages do for us right now. If you understood this as "packaging" intent (which, in 20/20 hindsight, is a perfectly reasonable assumption on your part), I'm sorry for mis-communicating.
Anyhow, can you tell me why it is somehow a problem to hide the "standardized" abstractions (and I use that quoted word extremely loosely) such as GUI interfaces, HTML, SQL, etc? Is it somehow worse than hiding machine-code and registers on whatever "standard" processor on the machine you're currently utilizing? Is it fundamentally any different? Now, you shouldn't just abstract things gratuitously, but if you don't feel like specifying the details of how
to go about structuring your HTML pages to particular effect, why should you? Now, I know there's the back-end issue - if your backend might vary between HTML, XML, YAML, and plain old output string, I imagine you'd readily agree that abstracting your interface to the backend is an appropriate move. But, suppose we knew for absolute certain that our backend 'interface' was going to be one of, say, 'standard' SQL, or 'standard' x86_64 assembler. Can you not think of any valid reasons you might abstract above each of those? Is a slightly-higher-level-language like FORTH of no value at all if you happen to know, for absolute certain, that the backend is x86_64 asm? Can you not think of valid reasons to 'hide' (forbid direct access or direct manipulation of) the final representation? (And I'm not talking about post-processor access; I'm talking more about forcibly poking holes through your abstractions to inject little tidbits of the underlying SQL or x86_64 code at arbitrary locations as per the C/C++ 'asm' blocks.)
I can think of many reasons, and I described several in my objection to FrameworksShouldAutomateNotHide
Some good points, and I agree that wrapping something is often a case of YouArentGoingToNeedIt
. Still, there are very many times when it's good to put a wrapper around an existing API.
can be a more useful thing than not. For instance, in VB, there's a Dir function that is used by making multiple calls to it and relying on a global state. First, you call it with an argument for the directory you want and to get the name of the first file in the directory, then you call it repeatedly with no arguments to get additional items in the directory passed on the first call - ouch. If I write a loop to do this, and call a procedure from within the loop, how do I know it won't make a call to Dir and invalidate my loop's state? I prefer to wrap Dir by having a function that performs an entire Dir loop, gathering the results into a colleciton, and returning the collection. By making each Dir loop atomic, I've removed a significant potential cause of bugs.
Sometimes files are too big to read into a entire collection. Anyhow, you may be somewhat describing HelpersInsteadOfWrappers.
Another time I like to wrap APIs is when my application repeatedly needs to do something simple with it that the API makes somewhat complex. For instance, if I repeatedly want to get a collection of values from a column of a table, I usually have to create and destroy 3 objects to do it. Simply in factoring out duplication, I naturally end up with a wrapper. I usually end up with a select query handler that allows me to set the query SQL, tell it to generate a result, and ask for a collection of values for a named column. You can't very well factor out duplication without creating wrappers, and you definitely want to factor out duplication.
I am not sure what the "3 objects" are. Perhaps this kind of issue could be put in QueryAndLoop.
See also: SeparateDomainFromPresentation