6 ms·
The best way to achieve a good abstraction is to recall what the word meant before computer science: namely, something closer to generalization. In computing,
by voidhorse 2y ago
The best way to achieve a good abstraction is to recall what the word meant before computer science: namely, something closer to generalization.
In computing, we emphasize the communicational (i.e. interface) aspects of our code, and, in this respect, tend to focus on an "abstraction"'s role in hiding information. But a good abstraction does more than simply hide detail, it generalizes particulars into a new kind of "object" that is easier to reason about.
If you keep this in mind, you'll realize that having a lot of particulars to identify shared properties that you can abstract away is a prerequisite. The best abstractions I've seen have always come into being only after a significant amount of particularized code had already been written. It is only then that you can identify the actual common properties and patterns of use. Contrarily, abstractions that are built upfront to try and do little more than hide details or to account for potential similarities or complexity, instead of actual already existent complexity are typically far more confusing and poorly designed.
- 29athrowaway 2y agoComputers are to manipulate data. Data = representations = abstractions This article is so fundamentally lost that it forgets what computers are for. Computers exist to implement abstractions.
- dishsoap 2y agoDid you even read it?
- 29athrowaway 2y agoUnfortunately, I did. It is an attempt to approach complexity, cognitive load and high entropy in code, jumping to conclusions prematurely while suggesting a solution that is worse than the problem.
- f1shy 2y agoI would really like to convince you that you are missing something very important. Please try to make sense of the article, reading it again a trying to find cases where it makes sense for you.
- deleted 2y ago[deleted]
- javcasas 2y agoComputers are to manipulate data, not to manipulate a pointer to a pointer to a pointer to a pointer to data. They can do that too, but the cache miss cost already makes that expensive.
- astrobe_ 2y agoYou are confusing too many things. "10" is the representation of a data; "0xA" is another representation of the same data. Not being able to touch something doesn't make it an abstraction. Light is not an abstraction, a contract is not an abstraction. 10 is not an abstraction, it is an ordinal [1]. "Isomorphism" is an abstraction. It doesn't name a particular data or value, but a class of functions that share common properties. A function template or functions written in a dynamically typed language can describe a particular group of isomorphism. [1] https://en.wikipedia.org/wiki/Set-theoretic_definition_of_natural_numbers https://en.wikipedia.org/wiki/Set-theoretic_definition_of_na...
- 29athrowaway 2y agoNumber notations and the glyphs and conventions used in them are abstractions. 10 in a database record of a warehouse system may be an abstraction for 10 items of a given type in a warehouse.
- JadeNB 2y ago> Light is not an abstraction, a contract is not an abstraction. 10 is not an abstraction, it is an ordinal [1]. It seems to me that all of those things very much are abstractions. They are not the utmost level of abstraction, but they are abstractions! (Actually "a contract," which at first I thought was the clearest win, I'm now not sure about. On reflection, it seems like a concretization, turning abstract ideas of trust and reliability into concrete conditions under which the contract has or has not been met.)
- astrobe_ 2y agoAll right, light is an abstraction, contracts are abstractions, 10 is an abstraction, time is an abstraction, and I am an abstraction too. The notion is so fuzzy that everything can be an abstraction in a certain way. The word "abstraction" is a black hole for thoughts. Stay away from it. For instance in this threads and in TFA, people made useful distinctions such as "generalization" or "indirection".
- patrulek 2y agoI kind of agree and i find that is why (other) abstractions should be created around data not around behaviours.
- weMadeThat 2y agoEver read any MS Windows code? The author is relatively clear about good and bad, or rather pointless, abstractions.
- f1shy 2y agoThis is exactly the point, also in other comments, quoting Dijkstra. Abstraction not only hide details, but also add semantic value.
- astrobe_ 2y agoYes, abstraction and generalization are properties you'd rather look for the second time around. Someone was already warning about this 25 years ago [1]: You have a boring problem and hiding behind it is a much more interesting problem. So you code the more interesting problem and the one you've got is a subset of it and it falls out trivial. But of course you wrote ten times as much code as you needed to solve the problem that you actually had. Ten times code means ten times cost; the cost of writing it, the cost of documenting it, it the cost of storing it in memory, the cost of storing it on disk, the cost of compiling it, the cost of loading it, everything you do will be ten times as expensive as it needed to be. Actually worse than that because complexity increases exponentially. This person did his own CAD software from scratch in order to make custom chips [2]. [1] https://www.ultratechnology.com/1xforth.htm https://www.ultratechnology.com/1xforth.htm [2] https://en.wikipedia.org/wiki/Charles_H._Moore https://en.wikipedia.org/wiki/Charles_H._Moore
- zelphirkalt 2y agoAlthough of course solving the abstract problem does not have to be 10 times as much code. The best solutions are often those, that recognize the more general problem, solve it with little and elegant code, then turn to the specific problem, expressing it in terms of the abstract problem and thereby solving it in just a few lines of code. Such an approach should usually be accompanied by some documentation. To give a trivial example: Binary search or any other bog standard algorithm. You would want to have an implementation for the algorithm and named as such and then only apply it in the specific case you have. Sorting algorithms. You don't want to rewrite it all the time. Actually rewriting it all the time would be the thing that creates "10 times" the code.
- mattmanser 2y agoNo. Just no. This is the exact thought process that leads to unnecessary abstraction. This is the attitude that the article is criticizing. A good rule of thumb is never abstract unless you genuinely have done the same thing twice already. i.e. only write an abstraction after you've written the boring, simple, concrete, implementation twice and are about to write it a third time.
- themadsens 2y agoWell said! I have met a few of these codesmells where the actual functioning is hidden behind a bewildering maze of facades, shims, proxies and whatnot. I guess some has had an irresistible itch to use as many patterns from the GoF book as possible.
- xtracto 2y agoSmells like Ruby to me haha. I know is not the language, but for some reason the ruby code I've stumbled into are all like that.
- vidarh 2y agoTo me it sounds like what I use Ruby to get away from. It's rare to need facades, proxies, and shims in a dynamically typed language where the caller doesn't need to care about the type of the object they call. In fact, most of the Gang of Four design patterns either make no sense in Ruby or are reduced to next to nothing.
- antfarm 2y agoThe best abstractions I've seen have always come into being only after a significant amount of particularized code had already been written. It is only then that you can identify the actual common properties and patterns of use. Early Ruby On Rails comes to mind as a great generalization of web applications in the era of Web 2.0.
- moomin 2y agoGeneralisation is definitely a good approach, but it’s not the only one. Another is “conceptualisation” or reducing repetition: if you find that large numbers of functions are taking the same three parameters, especially if they’re using them in similar ways, that’s a good sign there’s a concept you can introduce that makes those observations explicit.
- layer8 2y agoTo put it in slightly simpler terms, abstractions are generally to separate the “what” from the “how”. Functions are the fundamental mechanism for abstraction in computing, which demonstrate that very well, in their separation between interface and implementation. The function signature and associated interface contract represent the “what”, and the function’s implementation the “how”. If the effective (often non-explicit) interface contract relies on most aspects of the actual implementation, then the “what” is almost the same as the “how”, and there is little abstraction. The greater the difference between the “what” and the “how”, the more of an actual abstraction you have. This relates to Ousterhout’s notion of “deep modules”, which are modules whose interface is much simpler than their implementation. In other words, the “what” is much simpler than the “how”, which makes for a good abstraction. It’s true that often one has to first implement the “how”, possibly multiple times, to get a good notion of which aspects are also still important to the “what”, and which aren’t. Note also that generalizations go two ways: The caller needs less knowledge about the implementation, but it also means that the caller can rely less on the properties of a concrete implementation. This is again well reflected in function types. A (pure) function g: A –> B is a generalization of a function f: C –> D only if A is a subtype (specialization) of C and B is a supertype (generalization) of D. Here D could expose properties of the function’s implementation (f) that g wants to hide, or abstract from.
- marcosdumay 2y agoIMO, I dislike that kind of mixing several concepts into the same name that software engineering is full of. Abstraction means removing details from the thing you present. Generalization means making the same representation valid for several different things. Are abstractions that don't generalize valuable? Well, maybe there is something better to be found, but those are the bread-and-butter of software engineering; they are what everybody spends almost all of their time writing. Are generalizations that don't abstract valuable (or even possible)? Well, not if they don't abstract at all, but there are plenty of valuable generalizations that abstract very little. Hell, we have all those standardizing organizations that do nothing more than creating those. Are the best interfaces the ones that achieve most of both of those? Honestly, I have no idea, there are other goals and if you optimize to extreme levels, they start to become contradictory.
- gspencley 2y agoI would warn against conflating the concept of an interface with an abstraction just as much as I would against conflating generalizations and abstractions. An interface often accompanies an abstraction, sometimes even represents one. But if we get down to definitions, an interface is merely something that you interact with: a function, a class, a network endpoint etc. If you write in machine code, you might not think that you are working with any kind of an interface, and certainly it's not a high level one, but you are interfacing with the machine's native instruction set. You could then argue that the instruction is an abstraction that hides what the machine is capable of. But now we're splitting hairs and about to ask silly philosophical questions like whether a car's steering wheel qualifies as an abstraction that exists to hide the axles. I would argue not. The interface's primary responsibility is to provide a mechanism of interaction, rather than to reduce complexity (though a good interface is simple and intuitive). Both you and the author of the article posit a similar definition of 'abstraction'. From the article: > An abstraction is only as good as its ability to hide the complexity of what lies underneath. And from your comment: > Abstraction means removing details from the thing you present. I would actually argue that both, while very close, are missing the mark. An abstraction exists to reduce a concept down to its essentials. This doesn't necessarily contradict the definitions offered, but I think there is a nuance here that, if missed, causes the offered definitions to become useless. The nuance is in deciding what is essential or necessary. If, definitionally, you choose to dispense with acknowledging the essential... well then you get the problems that the author is writing about. You get shit abstractions because no one bothered to think in terms of what they INCLUDING rather than DISPENSING with. Yes, obviously, we abstract in an attempt to simplify. But that simplification needs to come from a positive rather than a negative. In other words: What is the thing for? What does it do? "Hides complexity" is the shittiest answer that anyone could ever offer as a response when faced with a given problem. First, what is the complexity that we are trying to reduce? Secondly, why are we trying to reduce it? Thirdly, when we have achieved our primary goal of reducing the complexity, then what does the thing look like? What value does it offer over interfacing directly with the "thing" being abstracted? Abstractions in computer science are extremely valuable. So valuable, I would offer, that any time we hear engineers decry abstractions or point to abstractions as the root of all evil we ought to ask a very pressing question: "who hurt you?" A good engineering solution is a simple one, and an abstraction is intended to be a simpification mechanism. But it doesn't have to necessarily simplify the intuitive understanding of the problem at hand. This is a good goal if you can achieve it, don't get me wrong. But that abstraction might exist so you can swap vendors in the future if that's a business priority. Or because you've identified some other element / component in your system that will be difficult to change later. So you stick it behind an abstraction and "Program to Interfaces" rather than "implementations" so that you can simplify the process of change down the road, even if it comes at an more immediate cost of making the code a bit less intuitive today. Everything in software is tradeoffs. And a good abstraction exists so that we can focus on requirements and programming to those requirements. This is a smiplification when done properly.But the focus ought to be on defining those requirements, of asking "what should simple look like?"
- fsndz 2y agothis feels like you are talking about langchain haha:https://medium.com/@fsndzomga/jailbreaking-langchain-ad9415a7abb6 https://medium.com/@fsndzomga/jailbreaking-langchain-ad9415a...
- highfrequency 2y agoGreat point, and agree that generalization makes for the clearest wins from abstraction. But there are also cases where there is no generalization, but the encapsulation / detail hiding is worthwhile. If you have a big function and in the middle of it you need to sort some numbers, you would probably implement a Sort routine to make the control flow much easier to understand - even if you only use the function once (let’s pretend there’s no sort functionality in standard library). Curious if others agree, and what heuristics you use to decide when implementation encapsulation is worthwhile.
- oalae5niMiel7qu 2y agoI saw this in my codebase firsthand. At first, we had to communicate with one device. Then two. For that second device, I subclassed the class that handled the communication with the first thing, and changed 3 methods in the subclass. Now it was possible to substitute that second device for the first, allowing a different product configuration. After a few years, we had to support a third device type, and now they wanted it to be possible to have more than one of them, and to mix and match them. Supporting the third device was handed off to a junior dev. I pointed him at my subclass and said to just do that, we'd figure out the mixing and matching later. But he looked at my subclass like it was written in Greek. He ended up writing his own class that re-imagined the functionality of the superclass and supported the new device (but not the old ones). Integrating this new class into the rest of the codebase would've been nigh impossible, since he also re-implemented some message-handling code, but with only a subset of the original functionality, and what was there was incorrect. His work came back to me, and I refactored that entire section of the code, and this is when the generalization occurred: Instead of a superclass, I took the stuff that had to be inherited and made that its own thing, having the same interface as before. The device communication part would be modeled as drivers, with a few simple functions that would perform the essential functions of the devices, implemented once per device type. I kept the junior dev's communication code for the new device, but deleted his attempt to re-imagine that superclass. Doing it this way also made it easy to mix and match the devices.