14 ms·
That's not an abstraction, that's a layer of indirection
- weMadeThat 2y agoGangBang-Abstractions would be a good name. Abusive, damaging the respective micro-biome ( parts of the code/system ) almost irreversibly and passing around the data for brutal telemetric exploitation ...
- ciwchris 2y agoI can't help but wonder whether the problem is subjective, to each person and what they need to accomplish at the time. What is cognitive load and indirection to one at one time is a simple abstraction to another at another time. And so I wonder if a solution to this is for editors to be able to represent the code differently, depending on what the person's needs are at the time.
- wvlia5 2y agoChuck Moore, the creator of Forth, was famous for implementing solutions without layers of abstraction.
- danparsonson 2y agoPerhaps this is a minor nitpick, but > Abstractions are also the enemy of simplicity. Each new abstraction is supposed to make things simpler—that’s the promise, right? Not exactly, no. The purpose of abstraction is to hide implementation detail, and thereby insulate one part of the codebase/application/system from variations in another. Graphics APIs for example - yes your code may be simpler for not having to deal with the register-level minutiae of pushing individual triangles, but the core benefit is that the same code should work on multiple different hardware devices. Good abstractions break a codebase up into compartments - if you drop a grenade in one (change the requirements for example), then the others are unaffected and the remedial work required is much less.
- uoaei 2y agoIn short: good abstractions simplify by centralizing operational logic. But it's not until a certain scale where that option is more efficient from bespoke implementations.
- magicalhippo 2y agoA heuristic we use at work is to not introduce an abstraction layer until there are at least two different implementations required. That is if you think you'll probably need multiple implications, delay introducing an abstraction until you actually do. Also, there are different ways of providing abstraction. Perhaps you don't need to abstract the entire implementation but, as an example, rather change one parameter from passing a value to passing a function returning a value.
- hansvm 2y agoThat touches on a couple related principles: - not doing extra work now if it's not necessary yet and if it's as cheap to do later - delaying building a thing till you know what that thing should be
- wvenable 2y agoA good abstraction is like documentation written in code.
- bruce511 2y ago>> but the core benefit is that the same code should work on multiple different hardware devices. I came here to say this. Attractions act as a bridge between things which allow those things to change independently. Using an ORM allows my program to easily work against multiple sql databases. Using a compiler allows me to target different hardware. Using standard protocols allows me to communicate with different programs. Using libraries to do say email hides me from those protocols and allows me to adapt to service providers Using APIs not protocols. In other words the abstraction is designed to hide a layer (which can change) from a program not interested in that level of change. The key is to stop abstracting when the program cares. By all means encapsulate rules and processes, but they're a direct implementation of those rules and processes. One can argue my "calculateLeaveForEmployee" function is an "abstraction" - but that would be a misnomer. Since there's only one set of rules in play (the set that matters now) its an implementation. An abstraction supports (at least) two things at the same time.
- necovek 2y ago> Using an ORM allows my program to easily work against multiple sql databases. A curious example, since most developers who've worked on any project with significant amount of data in a database would likely disagree. IMO, ORMs mostly allow using programming-language-of-choice as a syntax for relational queries instead of constructing it by hand on top of serialization and deserialization of objects into rows and vice versa.
- ludston 2y agoIndeed, given that most sql dialects have subtle differences that make them noncompatible with one another, and most ORMs have support for dipping into raw sql, sufficiently large systems tend to end up coupled with a particular database anyway. That's not to mention that the BAs will want raw sql access for report writing and switching systems breaks all of their scripts too.
- zharknado 2y agoYes, ORMs came to mind for me as an example of indirection without abstraction. If you accept OP’s litmus test of “how often do I have to peek under the hood” I think ORMs generally don’t score particularly well.
- cpeterso 2y ago“The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.” — Edsger Dijkstra But sometimes a new semantic level isn’t needed. Abstraction gets so much press when you might just need some good ol’ fashioned information hiding and separation of concerns.
- crabmusket 2y agoThis is such a great quote, and helps explain what is a good abstraction. Because CRDTs have been in the zeitgeist a lot lately, I want to pick them as an example of a "good" abstraction. CRDTs have mathematical properties which can be described and understood independently of a specific implementation. And importantly, you can judge whether an implementation is correct with reference to these abstract rules. This means that when using a CRDT, you largely can treat it as a reliably-solved problem. Once you understand the concepts, and work out how to use the library you've picked, you don't have to think about the details. Though that doesn't mean sometimes the behaviour can be surprising: https://www.moment.dev/blog/lies-i-was-told-pt-1 https://www.moment.dev/blog/lies-i-was-told-pt-1 TCP and HTTP are great examples too, though interestingly I don't know if they rely on mathematical definitions so much as just being extremely widespread to the point that reliable implementations are available anywhere you care to write code. I like this article which also leans on the Dijkstra quote: https://www.pathsensitive.com/2022/03/abstraction-not-what-you-think-it-is.html?m=1 https://www.pathsensitive.com/2022/03/abstraction-not-what-y...
- sgarland 2y agoCRDTs are also an excellent example because of how their supporting infrastructure is impacted by their design, namely that Postgres’ method of dealing with updates makes for massive write amplification. I wrote this [0] previously, but it still applies. IMO, as a dev, there are times where you really do need to think somewhat deeply about infrastructure choices. Unfortunately, knowing when you need to care practically requires you to already understand it. [0]: https://news.ycombinator.com/item?id=40834759 https://news.ycombinator.com/item?id=40834759
- chii 2y ago
- voidhorse 2y agoWhile this is how the term is often used, I think it's cavalier use of language and confuses abstraction for modularity, and this linguistic confusion is one of the reasons a lot of programmers write bad "abstractions". Organizing your code into components that are as independent as possible is a good practice and is the pursuit of modularity. A proper abstraction on the other hand, is a generalization that simplifies a conceptual layer in your code base. Abstraction often enables greater modularity as a consequence, but they are not the same thing. For example, in the problem of text editing, people eventually realized that the manipulation of text is typically line oriented. Thinking of a text file as a collection of lines may seem like an obvious and modest abstraction, but it works well. This abstraction, in turn, leads to other couplings (e.g. line oriented motion is highly dependent on the line abstraction), but it also leads to potential modularity (e.g. printer code may no longer need to understand exactly how a display renders each character of text in a grid, instead, it too can work on "lines"). Good abstractions support modularity to the extent that they establish a shared domain of objects to communicate about and across systems, but they do not necessarily produce modularity in themselves.
- danparsonson 2y agoYes, abstraction and modularity are coincident but distinct topics, and my last paragraph implies that I am equating those two things. Good point. I disagree that considering a text file as a set of lines really qualifies as an abstraction; it's just a different representation of the data - you could instead choose to use a list of words or a tree or whatever. An abstraction is a general interface to a concrete thing that allows you to substitute different but similar concrete things without changing the consumer of those things. The Linux filesystem is a great example - I can 'cat' basically anything that has a path, and some driver will pull data from the associated endpoint and display the data on screen. "File" is the abstraction, and consumers of files need not care about the specifics of communicating with the underlying devices. Such a system is also modular, but it's hard not to be when your abstraction is well conceived. Perhaps we're saying the same thing with different words?
- mannykannot 2y ago
- zdragnar 2y ago> The purpose of abstraction is to hide implementation detail Technically, that's encapsulation, though the sentiment is close, I think. I rather view it as a matter of semantics. At one low level, you have operations that deal with some concrete interface or API, etc. You bundle those operations up behind an abstraction, providing methods whose names involve your application domain. Perhaps they are still at a technical level, and you bundle those up behind another abstraction, whose method names involve your business domain. Yes, the lower level details are hidden from the higher levels, but the hiding is not the point. The point is to be able to write code that readily corresponds to the problem you are trying to solve.
- danparsonson 2y agoHmmm I prefer Wikipedia's definition: > In object-oriented programming languages, and other related fields, encapsulation refers to one of two related but distinct notions, and sometimes to the combination thereof:[5][6] > - A language mechanism for restricting direct access to some of the object's components.[7][8] > - A language construct that facilitates the bundling of data with the methods (or other functions) operating on those data The hiding is absolutely the point of abstraction, in the sense that caller can manipulate the subsystem without knowing about (important) inner details of it. As I said, graphics drivers are a great example - I just want to put a triangle on the screen, I don't want to care about the registers I have to set on an NVidia card vs those on an AMD card, or what their memory maps look like, and I don't want to have to rewrite my code when they release a new generation of hardware. Drawing a triangle is an abstraction, hiding away the details of how a specific graphics card achieves that. Think about what the word 'abstract' means - the idea of a 'human' is an abstraction for bundles of molecules conforming to a general pattern with broadly similar qualities but almost infinite variability. The word conveniently hides a wealth of detail that we usually don't need to think about; if I tell you there are three humans in a room, you can make use of that information without knowing anything about their exact physical attributes or who they are. I would refer to what you're describing as 'modelling' - these are related topics but I don't see them as the same thing.
- voiceofunreason 2y agoBerard 1993 offers a good survey of the meanings of Abstrasction, Encapsulation, and Information Hiding https://web.archive.org/web/20071214085409/http://www.itmweb.com/essay550.htm https://web.archive.org/web/20071214085409/http://www.itmweb...
- mgaunard 2y agoThat would be encapsulation, not abstraction.
- danparsonson 2y agoI disagree; to save duplicating my other reply to a similar comment: https://news.ycombinator.com/item?id=42529743 https://news.ycombinator.com/item?id=42529743
- nsonha 2y ago[flagged]
- fnord77 2y ago[flagged]
- jongjong 2y agoThis is a great point. Most modern software is riddled with unnecessary complexity which adds mental load, forces you to learn new concepts that are equally complex or more complex than the logic which they claim to abstract away from. I find myself saying this over and over again; if the abstraction does not bring the code closer to the business domain; if it does not make it easier for you to explain the code to a non-technical person, then it's a poor abstraction. Inventing technical constructs which simply shift the focus away from other technical constructs adds no value at all. Usually such reframing of logic only serves the person who wrote 'the abstraction' to navigate their own biased mental models, it doesn't simplify the logic from the perspective of anyone else.
- mightyham 2y agoI forget which programming talk I watched which pointed this out, but one extremely common example of this in Java is recreating subsets of the Collections API. I've done this before, heck even the Java standard library is guilty of this problem. When a class has a full set of get/put/has/remove methods, it is often not actually hiding the complexity of its component data structures.
- oftenwrong 2y agoRich Hickey on HttpServletRequest? https://www.youtube.com/watch?v=aSEQfqNYNAc https://www.youtube.com/watch?v=aSEQfqNYNAc
- mightyham 2y agoYup that's the one. Thanks for linking it.
- mrkeen 2y agoGood example of a bad abstraction. If you're speaking the language (or "abstraction") of sets, you should see certain terminology: union, intersection, disjunction. These words are not part of the Java Set interface.
- mightyham 2y agoI would actually argue that the Collections API itself is a pretty good abstraction. It is often the case that conceptually I just want to work with multiple things and properties like order, duplicates, random access, etc. are not particularly important (in fact, requiring them adds inherent complexity). It's very useful that a vast amount of the standard library data structures conform to this interface or can create data views that conform to it.
- getnormality 2y agoHow did "abstraction" and "hiding complexity" become perceived as such fundamental virtues in software development? There are actual virtues in that ballpark - reusable, reliable, flexible - but creating abstractions and hiding complexity does not necessarily lead to these virtues. Abstraction sounds no more virtuous to me than indirection.
- lizzas 2y agoIt can be good if done well. The goal is not abstraction but understandability. Any function call is abstraction after all. Unabstracted you would just inline that code or use goto.
- wvenable 2y agoThe only way we accomplish anything is abstraction. It's the basis of all computing.
- MobiusHorizons 2y agoI disagree. There are base abstractions you can’t avoid, of course, like the machine code of your computer, or the syscalls presented by it. Using these is not abstraction, unless you choose to build up interfaces of reusable pieces. Abstraction is structure. You still have to actually write some code that can be organized into structure. You could write code using just those base abstractions if you wanted, or as many do, choose libc as your base abstraction. Watching a program through strace gives you basically this view, regardless of the abstractions the program actually used to achieve the result.
- wvenable 2y agoSome abstractions are so ingrained you don't even think of them as abstractions. A file is an abstraction. A socket is an abstraction. The modern terminal is an abstraction. People only notice bad abstractions.
- zahlman 2y agoDecades ago, when The Structure and Interpretation of Computer Programs taught us that programmers fundamentally do two things: abstraction and combination; and we are interested in programming languages insofar as they provide means to those two ends. The two classic "hard problems" of computer science - cache invalidation and naming things - are both aspects of abstraction. Cache invalidation is a special case of making sure the abstraction does what it's supposed to, and naming is the most important part of causing the abstraction to have meaning.
- feverzsj 2y agoWhat about java?
- praptak 2y agoIt's a huge ecosystem which turns 30 in May. Yes, it accumulated lots stuff that was cool back then and isn't so cool now but overall is doing pretty well (COBOL was 36 when Java got public).
- 29athrowaway 2y ago[flagged]
- DEEP-MELTDOWN 2y ago[dead]
- VirusNewbie 2y ago>There’s a well-known saying: "All abstractions leak." It’s true. No matter how good the abstraction, eventually, you’ll run into situations where you need to understand the underlying implementation details This is false. One can read up on Theorem's for Free by Wadler to see that not all abstractions are leaky.
- stickfigure 2y agoThis has such potential to be an interesting thread of conversation but all I get is a reference to a book that I haven't read and am unlikely to. What examples of non-leaky abstractions do you have? I could imagine something like "newtonian physics" but that leaks into my daily life every time I fire up google maps and get a GPS fix. The OP's example of TCP seems close to the mark, but to be totally honest I'm not convinced. Every time I have to run ping to check whether my hung connection is due to connectivity, I'm breaking the abstraction. And I've had to debug network issues with Ethereal (yes, I'm dating myself). TCP does leak, it's just that most people don't know what to do with it when it does.
- abstra4free 2y agoTheorems for Free tells you that some abstractions satisfy some mathematical properties (for free!) under some circumstances. If you write down a function with signature {T : Type} -> T -> T then it must be the identity function, if you do not use "malicious" extensions of the type system. But what is the performance of the identity function? Here is an identity function: lambda T, lambda t, if (2 + 2 = 4) then t else t In other words: I can hide pretty much arbitrary computation in my identity function. Users of my identity functuon will notice that it is wicked slow (in reality, I let my identity function compute Busy Beaver 5, before doing nothing). Their complaints are evidence of leaky abstraction. Now you might have a smart optimizing compiler that knows about Thm4Free... But that's another story.
- javcasas 2y agoI don't think you can have such compiler, at least for a general case, without solving the halting problem first. After all, you can encode arbitrary computations at the type level.
- noduerme 2y agoI got a piece of advice writing UI code a long time ago: Don't marry your display code to your business logic. I'd like to say this has served me well. It's the reason I never went for JSX or other frameworks that put logical code into templates or things like that. That is one abstraction I found unhelpful. However, I've come around to not taking that advice as literally as I used to. Looking back over 25 years of code, I can see a lot of times I tried to abstract away display code in ways that made it exceedingly difficult to detect why it only failed on certain pieces of data. Sometimes this was sheep shaving tightly bound code into generic routines, and sometimes it was planned that way. This is another type of abstraction that adds cognitive load: One where instead of writing wrappers for a specific use case, you try to generalize everything you write to account for all possible use cases in advance. There's some sort of balance that has to be struck between these two poles. The older I get, though, the more I suspect that whatever balance I strike today I'll find unsatisfactory if I have to revisit the code in ten years.
- josh2600 2y agoAhh yes, the labor of love that is code maintenance. Done is better than perfect until you’re reviewing the code in 10 years (or maybe 3 years with a more adept set of eyes over your shoulders). These days I ask my teams to be less clever and more simple. Simple usually wins over clever in the long run.
- mcfedr 2y agoCompletely this. KISS, my favourite acro for this.
- skydhash 2y agoI learned that lesson building an utility with JavaFX. I've done a few years with React and the usual pattern was to move almost everything out of the components. Unless it's an event handler, a data transformer for the view, or something that manipulates the view, it has no business belonging to this layer. I don't try to generalize it, I just try to make the separation clear using functions/methods/classes. Instead of having the post button's handler directly send the request, I create a function inside the `api` module that does it. It does have the effect on putting names on code patterns inside the project.
- thomasjudge 2y agoI wish this article had more examples/details; as it is, it is kind of .. abstract
- Darmani 2y agoTCP is great. Long chains of one-line functions that just permute the arguments really suck. These both get called abstraction, and yet they're quite different. But then you hear people describe abstraction ahem abstractly. "Abstraction lets you think at a higher level," "abstraction hides implementation detail," and it's clear that neither of those things are really abstractions. As the OP mentions, we have a great term for those long chains of one-line functions: indirection. But what is TCP? TCP is a protocol. It is not just giving a higher-level way to think about the levels underneath it in the 7-layer networking model. It is not just something that hides the implementations of the IP or Ethernet protocols. It is its own implementation of a new thing. TCP has its own interface and its own promises made to consumers. It is implemented using lower-level protocols, yes, but it adds something that was fundamentally not there before. I think things like TCP, the idea of a file, and the idea of a thread are best put into another category. They are not simply higher level lenses to the network, the hard drive, or the preemptive interrupt feature of a processor. They are concepts, as described in Daniel Jackson's book "The Essence of Software," by far the best software design book I've read. There is something else that does match the way people talk about abstraction. When you say "This function changes this library from the uninitialized state to the initialized state," you have collapsed the exponentially-large number of settings of bits it could actually be in down to two abstract states, "uninitialized" and "initialized," while claiming that this simpler description provides a useful model for describing the behavior of that and other functions. That's the thing that fulfills Dijkstra's famous edict about abstraction, that it "create[s] a new semantic level in which one can be absolutely precise." And it's not part of the code itself, but rather a tool that can be used to describe code. It takes a lot more to explain true abstraction, but I've already written this up (cf.: https://news.ycombinator.com/item?id=30840873 https://news.ycombinator.com/item?id=30840873 ). And I encourage anyone who still wants to understand abstraction more deeply to go to the primary sources and try to understand abstract interpretation in program analysis or abstraction refinement in formal verification and program derivation.
- cloogshicer 2y agoHey Jimmy, I've read your comment and also your article in the past with great interest. This topic is absolutely fascinating to me. I just re-read your article but unfortunately I still struggle to really understand it. I believe you have a lot of experience in this, so I'd love to read a more dumbed down version of it with less math and references to PL concepts and more practical examples. Like, this piece of code does not contain an abstraction, because X, and this piece of code does, because Y. Keep up the good work!
- ChrisMarshallNY 2y ago> a bad one turns every small bug into an excavation. I find that I need to debug my abstractions frequently, while I’m first writing my code, then I never need to dig into them, ever again, or they do their job, and let me deal with adding/removing functionality, in the future, while not touching most of the code. That’s why I use them. That’s what they are supposed to do. Because they are abstractions, this initial debugging is often a lot harder than it might be for “straight-through” code, but is made easier, because the code architecture is still fresh in my mind; where it would be quite challenging, coming at it without that knowledge. If I decide it’s a “bad abstraction,” because of that initial debugging, and destroy or perforate it, then what happens after, is my own fault. I’ve been using layers, modules, and abstractions, for decades. Just today, I released an update to a shipping app, that adds some huge changes, while barely affecting the user experience (except maybe, making it better). I had to spend a great deal of time testing (and addressing small issues, far above the abstractions), but implementing the major changes was insanely easy. I swapped out an entire server SDK for the “killer feature” of the app.
- voidhorse 2y agoThe 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.
- mannyv 2y agoAbstraction hides detail, but at what coat? A network close call at a high level closes a network socket. But at the tcp level there's a difference between close and reset. Which do you want? Your api has removed that choice from you, and if you look you will have no idea if rhe close api does a close or a reset. Is the difference important? If depends. If you have a bunch of half open sockets and run out of file descriptors then it becomes very important. Another example: you call read() on a file, and read 10k bytes. Did you know your library was reading 1 byte at a time unbuffered? This abstraction will/can cause massive performance problems. My favorite one is when a programmer iterates over an ORM-enabled array. Yes, let's do 50,000 queries instead of one because databases are too complicated to learn. Just like any tool, abstraction has costs and benefits. The problem is that lots of people ignore the cost, and assume the benefit.
- atomicnumber3 2y agoI think an important distinction is hiding details from other parts of the program, and having details being hidden from you. 99.9% of the time I don't care what the tcp socket closes with as long as it isn't leaking a resource. And if I did care, then I picked the wrong level of network abstraction to engage with. I should've used something more raw. Regarding ORM arrays. I have myself recently debugged such a case. I chortled a bit at the amateur who wrote the code (me last year) and the schmuck who accidentally wrapped it in a loop (me 3 weeks ago). Then I changed it slightly to avoid the N queries and went on with my day. No need to lambast the tooling or the programmers. Just write something maintainable that works. No need to throw the entire ORM away just because we accidentally made a web page kinda slow that one time. And don't worry, I too lament when web pages I don't control are slow. You may rest uneasily knowing that that page would be slow regardless of whether ORMs existed because it is not slow because of ORMs, but because there is no incentive for the business to care enough to make it faster.
- f1shy 2y ago>> But at the tcp level there's a difference between close and reset. Which do you want? Your api has removed that choice from you, and if you look you will have no idea if rhe close api does a close or a reset. If you are doing a „TCP application” of course it makes no sense to abstract the TCP layer. Is not about cost. Now if you have an application that has to communicate somehow with other system, and you want to not depend on specific protocols, then the communication part should abstract away that part. How to deal with your example? Well, if you can say “I will always want X, you can make a configuration option “TCP.close” or “TCP.reset”. If “it depends” then you have to build the logic for the selection in the abstraction layer, which keeps hidden.
- noodletheworld 2y agoPretty easy to give generic advice without examples. “Write more tests, but not too many” “Use good abstractions where appropriate?” “The next time you reach for an abstraction, ask yourself: Is this truly simplifying the system? Or is it just another layer of indirection?” It’s easy to create a strawman here (the FactoryAdaptorMapper or whatever) but in reality this kind of generic advice doesn’t help anyone. Of course people want to use good abstractions. That’s not the problem. The problem is being able to tell the difference between generic arbitrary advice (like this post) and how your specific code base needs to use abstractions. …and bluntly, the only way to know, is to either a) get experience in the code base or b) read the code that others have left there before you. If it’s a new project, and you’re not familiar with the domain you’ll do it wrong. Every. Single. Time. So, picking “good” abstractions is a fools game. You’ll pick the wrong ones. You’ll have to refactor. That’s the skill; the advice to take away; how to peel back the wrong abstraction and replace it with your next best guess at a good one. How to read what’s there and understand what the smart folk before did and why. …so, I find this kind of article sort of arrogant. Oh, you want to be a great programmer? Just program good code. Use good abstractions. Don’t leave any technical debt. Job done! …a few concrete examples would go a long way here…
- jchmbrln 2y agoI can see the value of examples, but in this case I appreciate the post largely for its universality and lack of examples. On reading it, examples from past and present experience spring immediately to mind, and I'm tucking this away as a succinct description of the problem. Maybe I can share it with others when more concrete examples come up in future code review. A principle takes skill the apply, but it's still worth stating and pondering.
- noodletheworld 2y ago> examples from past and present experience spring immediately to mind Examples of what? Picking the wrong abstraction? Regretting your mistakes? I can certainly think of many examples of that. How you unwrapped an abstraction and made things better by removing it? I have dozens of battle stories. Choosing not to use an abstraction because it was indirection? Which is what the article says to do? I’m skeptical. I suspect you’ll find most examples of that are extremely open to debate. After all, you didn't use the abstraction so you don’t know if it was good or not, and you can only speculate that the decision you made was actually a good one. So, sharing that experience with others would be armchair architecture wouldn't it? That’s why this article is arrogant; because it says to make decisions based on gut feel without actually justifying it. “Is this truly simplifying the system?” Well, is it? It’s an enormously difficult question to answer. Did it simplify the system after doing it is a much easier one, and again that should be the advice to people; Not: magically do the right thing somehow. Rather: here is how to undo a mistake. …because fixing things is a more important skill and (always) magically doing the right thing from the start is impossible; so it’s meaningless advice. That’s the problem with universal advice; it’s impossible to apply.
- ericflo 2y agoClassic post in this genre: https://web.archive.org/web/20151217104831/https://zedshaw.com/archive/indirection-is-not-abstraction/ https://web.archive.org/web/20151217104831/https://zedshaw.c...
- mixermachine 2y agoReminds me of an old Java Android project I encountered. EVERY class implemented an interface. 98% of interfaces had one implementation. Every programmer was applying a different programming pattern. A lot of abstractions seemed incomplete and did not work. Proguard (mostly used for code obfuscation for Android apps) definitions were collected in the top module even though the project had multiple modules. Half of the definitions were no longer needed and the code was badly obfuscated. Problems were solved by continuesly adding classes and checking what sticks. The UI was controlled by a stateful machine. State transitions were scatter everywhere in the code with lots of conditions in unforeseen places. Legacy code was everywhere because no one wanted to risk a very long debugging session of an unforseen change. No API definitions. Just Maps that get send via REST to URLs. By biggest mistake was to not directly rewrite this project when I entered the team. We did after one year.
- anonytrary 2y agoI'm not sure what it's called (abstraction vs. indirection) but I dislike when everything needs a class/object with some odd combination of curried functions. Some programming languages force this on you more than others I think? As a contrived example "StringManager.SlicingManager.sliceStringMaker(0)(24)(myStr)", I've seen code that reminds me of this and wonder why anyone uses a language where this not only an acceptable idiom, but a preferred one.
- Jaxan 2y agoIn Haskell gMaker(0)(24)(myStr) and gMaker(0, 24, myStr) would have the same syntax, namely gMaker 0 24 myStr. So that solves the issue.
- aktenlage 2y agoInteresting read, although I don't agree with everything. I like the distinction between different qualities of abstractions, made in the beginning. The following bashing of abstractions is too generalized for my taste. The best part comes close to the end: > Asymmetry of abstraction costs > There’s also a certain asymmetry to abstraction. The author of an abstraction enjoys its benefits immediately—it makes their code look cleaner, easier to write, more elegant, or perhaps more flexible. But the cost of maintaining that abstraction often falls on others: future developers, maintainers, and performance engineers who have to work with the code. They’re the ones who have to peel back the layers, trace the indirections, and make sense of how things fit together. They’re the ones paying the real cost of unnecessary abstraction.
- _kggb 2y ago[dead]
- deleted 2y ago[deleted]
- ozim 2y agoLots of crud apps add 3-tier architecture that end up something that could be 2 tier. People add it just in case but the case never materializes- for some probably do but ones I worked with not.
- pdpi 2y ago> Think of a thin wrapper over a function, one that adds no behavior but adds an extra layer to navigate. You've surely encountered these—classes, methods, or interfaces that merely pass data around, making the system more difficult to trace, debug, and understand. These aren't abstractions; they're just layers of indirection. “No added behaviour” wrapper functions add a lot of value, when done right. First off, they’re a good name away from separating what you’re doing from how you’re doing it. Second, they’re often part of a set. E.g. using a vector for a stack, push(x) can be just a call to append(x), but pop() needs to both read and delete the end of the vector. Push in isolation looks like useless indirection, but push/pop as a pair are a useful abstraction. A consequence of adding these two points together is that, if you have a good abstraction, and you have a good implementation that maps well to the abstraction, it looks like useless indirection. Another consequence is that those pass-through wrapper functions tell you how I think the implementation maps to the domain logic. In the presence of a bug, it helps you determine whether I got the sequence of steps wrong, or got the implementation wrong for one of the steps. Ultimately, the two aren’t completely independent —indirection is one of the tools we have available to us to build abstractions with. Yes, people misuse it, and abuse it, and we should be more careful with it in general. But it’s still a damned useful tool.
- kristiandupont 2y agoIndirection serves a purpose as well. One that is related to, but not the same as abstractions. When you add a layer of indirection, you make it easier to, say, delete or change every item X instead of iterating through everything. Unnecessary or redundant levels of indirection are bad, just like unnecessary or wrong abstractions are. But when applied correctly, they are useful.
- lifeisstillgood 2y agoI suggest there are three types of layer that one passes through Abstraction - this thing of rare beauty Decision - often confused for abstraction and wrapper, this is best thought of as a case statement in a function. They are wildly better in my opinion than lots of classes Wrapper - either fluff like getters and setters or placeholders for later decisions (acceptable) or weird classes and instances that the language affords but tend to be confusing - what is called indirection in the article Tools, utils, libraries - these are I classify as handles / affordances for other code to use - maybe they add layers but they add a single way in to the nice abstraction above.
- globular-toast 2y agoWhile I too like to marvel at the TCP/IP stack as an example of abstraction done right, it would be unwise to think an abstraction is only "good" if you get it right first time. The real point of abstraction is to enable software that is adaptable. If you are ever sure you can write a program the first time and get it perfect then you don't need to bother with any of this thinking. We do that all the time when writing ad hoc scripts to do particular tasks. They do their job and that's that. But if you ever think software will continue to be used then you can almost guarantee that it will need to change at some point. If it is just a tiny script it's no problem to write it again, but that's not going to be acceptable for larger programs. So this necessarily means that some layer or layers of your well-architected application will have to change. That does not mean it was a bad abstraction. Abstraction is not about hiding things, it's about building higher levels of language. It enables you to work on individual layers or components without breaking the rest of the system. It very much should not be hiding things, because those things are likely to need to change. The bits that really don't change much, like TCP, are rarely written into application code.
- psychoslave 2y agoThe article seems to go with the premise that abstractions are most often carelessly introduced when there is an obvious alternative that is simpler and more performant. Yes, abstractions have a cost that will accumulate as they are layered. But simple elegant solutions are not free. They are hard to come with, so they often need large amount of dedication ahead of any coding. And as long as we don't deliver anything, we have no clue what actual requirements we miss in our assumptions. The road to reach the nice simple solutions is more often than not to go through some some clunky ugly solutions. So rather than to conclude with "before running to abstraction think wisely", I would rather recommend "run, and once you'll have some idea of what was the uncharted territory like, think about how to make it more practical for future walks."
- globular-toast 2y agoBut don't forget that the territory will change underneath your feet. This is especially true if you write business software. A tectonic shift in the business can make the assumptions you made a year ago completely invalid. This is complicated further by the fact that good software will drive the business. So you will always be creating new problems because you're driving the business into new areas and capabilities that simply weren't possible before. So this makes it doubly important to make sure your software can change in small ways over time. It's not just trying to navigate the moors at night with a torch, it's like trying to navigate the desert at night with a torch. The sand will move under your feet.
- Voultapher 2y ago> That’s the sign of a great abstraction. It allows us to operate as if the underlying complexity simply doesn't exist. While I generally agree with the sentiment that current day software development is too indirection heavy, I'm not sure I agree with that point. All abstractions are leaky and sure good abstractions allow you to treat it like a black box, but at some point you'd benefit from knowing how the sauce is made, and in others you'll be stuck with some intractable problem if you lack knowledge of the underlying layers.
- ordu 2y agoIt is funnily recursive... I'll try to explain, but I'm not sure my English is good enough for that task. But lets try. When the author says "great abstraction" they mean "ideal abstraction". You can see this for example in this quote: " The less often you need to break the illusion, the better the abstraction." They say even the phrase "all abstractions leak", which is the main point of yours. So, if they mean an "ideal abstraction", what does it mean? What it means to be ideal? It means to be an imagined entity with all sharp corners removed. The idea of "ideal" I believe is an invention of Ancient Greeks, and all their art and philosophy were built around them. Their geometry was an ideal thing, that doesn't really exist anywhere except the brains of a mathematician. Any ideal thing is not real by the definition. Why to invent ideals? To simplify thinking about real entities and talking about them. They allow us to ignore a lot of complicating details to concentrate on the essence. So it is like an abstraction, just not for programming but for thinking, isn't it? And now we come to the recursion. The author used abstraction over abstraction to talk about abstractions, and you used built-in deficiency of all abstractions (they are not real) to attack the abstraction over abstraction. Somehow it not as funny as I felt first, but still...
- Voultapher 2y agoHey, thanks for sharing and trying to express it even if it's not your primary language - it isn't for me either - it gave me a new insight.
- sixthDot 2y agoAnother criticism would be the time spent to compile those abstractions, even if in fine the "zero cost" goal _at runtime_ is reached.
- scotty79 2y agoEvery problem can be solved by adding a layer of abstraction. Except for the problem of having too many layers of abstraction.
- Garlef 2y agoWow... That was a lot of text without much depth to it. [Edit] To make my criticism more precise: The text mostly rephrases it's central point a few times and presents these rephrasings as arguments.
- mrcsd 2y agoFunnily enough, logical deductions or formal theorem proofs can be seen as a set of transformative steps from the initial premises to the conclusion, where no new information is added in the process. Which makes the conclusion (at a stretch) a bit like "just" rephrasing the initial premises.
- scotty79 2y agoNot really. Usually during the deduction you have some steps that pull surprising knowledge from the rest of math to support the reasoning or create and prove interesting lemmas. If you can prove a theorem without any of that, that's a little boring theorem to prove.
- mrcsd 2y agoI agree with the spirit of your comment, but not the literal fact of it. Sure, interesting proofs require pulling out some interesting knowledge in the reasoning, but notions like "surprising" or "interesting" are about human subjectivity and don't really exist as a property of a deduction. Surprising or interesting knowledge is not somehow new knowledge that wasn't there before, it's just that we didn't see it previously.
- scotty79 2y agoSure, but it's humans that do the deduction. So "surprising" and "interesting" still matters. Especially when you are treating deduction as a parallel to a piece of prose that one might reasonably hope to be surprising and interesting not just repeated rephrasing of main thesis.
- nwmcsween 2y agoThe goal of an abstraction should be to make reasoning about the code easier, generally that means hiding complexity but that shouldn't be the goal. In my opinion a common issue in programming is premature abstraction without understanding the interactions as a whole.
- freetonik 2y agoShameless plug: a while ago I made a video explaining the idea of abstraction in computer science, and it seems to be helpful for beginners: https://youtu.be/_y-5nZAbgt4 https://youtu.be/_y-5nZAbgt4
- mrcsd 2y agoJust thinking on my feet as to how I separate abstractions from indirections and it seems to me that there's a relatively decent rule of thumb to distinguish them: When layer A of code wraps layer B, then there are a few cases: 1) If A is functionally identical to B, then A is a layer of indirection 2) If A is functionally distinct from B, then A is likely an abstraction 3) If A is functionally distinct from B, but B must be considered when handling A, then A is a leaky abstraction. The idea is that we try to identify layers of indirection by the fact that they don't provide any functional "value".
- mrkeen 2y agoIt starts with a strong point that abstraction is not indirection, but then slips back into using the terms interchangeably.
- theGnuMe 2y agoNice to see hacker news return to its roots.
- yolotech 2y ago[dead]
- havkom 2y agoI have seen tons of ”abstractions” in recently created code bases from ”senior developers” which in actual fact is only titanic-grade mess of complicated ”indirection”. Many people nowadays are unfortunately not fit to work in software development.
- mkoubaa 2y agoI disagree with nowadays. It has always been the case.
- Gehinnn 2y agoA good abstraction shouldn't make its usage shorter, it should make the proof that the usage is correct shorter. This usually means the total amount of assumptions needed to prove everything correct is decreased. (when the code is not actually formally verified, think of "proof length" as mental capacity needed to check that some unit of code behaves as intended)
- mitch-crn 2y agoThis is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface. -Doug McIlroy
- alexvitkov 2y agoWhile I wholeheartedly agree with the premise, the article doesn't really say anything other than "TCP good, your abstraction bad, don't use abstraction".
- bryancoxwell 2y agoThat’s not at all what the article says. It’s not about avoiding abstraction entirely, it’s about implementing them thoughtfully.
- rauljara 2y agoI wish articles like this had more examples in them. In between “this thin wrapper adds no value but a lot of complexity”, and “this thin wrapper clarified the interface and demonstrably saved loads of work last time requirements changed” is an awful lot of grey area and nuance. I did like the advice that if you peak under the abstraction a lot, it’s probably a bad one, tho even this I feel could use some nuance. I think if you need to change things in lots of places that’s a sign of a bad abstraction. If there is some tricky bit of complexity with changing requirements, you might find yourself “peeking under the hood” a lot. How could it be otherwise? But if you find yourself only debugging the one piece of code that handles the trickiness, and building up an isolated test for that bit of code, well, that sounds like you built a wonderful abstraction despite it being peaked at quite a bit.
- mkoubaa 2y agoThe way to tell whether an abstraction is good or bad is to develop good taste. Engineers with good taste have intuition about these things. You are not going to acquire good taste from reading an article.
- baobabKoodaa 2y agoMaybe not, but you can still move the needle one way or another based on reading an article. For those readers who recognize themselves as erring on the side of adding too many abstractions, they might move the needle a bit towards the other side.
- layer8 2y agoRelying on mere “taste” is bad engineering. Engineers do need experience to make good decisions, yes. But surely we are able to come up with objective criteria of what makes a good abstraction vs. a bad abstraction. There will be trade-offs, as depending on context, some criteria will be more important than other (opposing) criteria. These are sometimes called “forces”. Experience is what leads an engineer in assessing and weighing the different present forces in the concrete situation.
- 2y ago
- PaulHoule 2y agoWithout more details his position rubs me the wrong way. As somebody who has done a huge amount of "fix this bug" and "add this feature" on existing code bases I think excessive use of cut and paste is the worst problem in the industry. Cut-and-paste is the devil's own "design pattern" as it is a practice that gets repeated throughout a code base to solve various problems. When it comes to bugs repetition means a bug might have 13 copies throughout the code and you could easily get the ticket sent back 2 or 3 times because you didn't find all the copies at first. Repetition (together with poorly chosen abstractions) also causes features that should add in complexity to multiply, as if I have 3 versions of a function and now something can vary 5 ways I now have 15 functions. In a good design you might pass one of 8 functions to a 9th function. Repeat this a few times and one guy has 98 functions and the other guy would have had 13200 if he'd been able to get that far. Granted the speed demon won't like all that function calling, right now I am thinking about writing a big switch statement for a CPU emulator, I get it, for you there is code generation. It is also healthy to have "fear of framework planets", a horrible example is react-router which has gotten up to incompatible version 7 because (i) it's the kind of thing you can write in an afternoon (it would take more time to write good documentation but... take a look at that documentation) and (ii) the authors never liked any of the frameworks they created. More than once I have dug into a busted app written by a fresher where there was a copy of react-router and there were some use's from it in use but they had bypassed react-router and parsed document.location directly to figure out what to display. The very existence of a bad framework creates a kind of helplessness. Those folks will say various versions of react-router support React Native, SSR, etc. We don't use any of those where I work, I don't care. It is a very good prop bet that you can dramatically speed up so-and-so's program by switching from AoS to SoA. https://en.wikipedia.org/wiki/AoS_and_SoA https://en.wikipedia.org/wiki/AoS_and_SoA (If it's a Java program, you eliminate the overhead of N objects to start with) but it's tricky to implement arbitrary algorithms, my mental model to do it is to build programs out of relational operators (even in my head or on paper) SQL is one of the greatest abstractions of all time as I can write a SQL query and have it be run AoS or SoA or some hybrid as well as take advantage of SIMD, SMT, GPU and MP parallelism. 10 years ago I would have said I could have beat any SQL engine with hand-optimized code, today products like https://duckdb.org/ https://duckdb.org/ would make that harder.
- sltr 2y agoWhen discussing the definition of abstraction, Koppel's article "Abstraction: Not What You Think It Is" offers a helpful framing and disambiguation. https://www.pathsensitive.com/2022/03/abstraction-not-what-you-think-it-is.html https://www.pathsensitive.com/2022/03/abstraction-not-what-y...
- herdcall 2y agoTo me, the value of abstraction is more about making the code flexible than hiding complexity.
- vacuity 2y agoPerhaps, but isn't it flexible because complexity was hidden?
- marginalia_nu 2y agoWhile I think it's good the pendulum is swinging toward a more restrictive approach to abstractions, we've (and I've) certainly been leaning a bit too much toward just solving every problems by adding a layer of indirection around it, and such onion-layered designs tend to (as the metaphor implies) cause a lot of tears when you cut through them. That said, it's not like abstraction itself is bad. A big part of the problem is arguably that IDEs make code navigation easier, which has us adding all these indirections and discover only when it's too late what a horrible maze we've built. Being more judicious about adding indirection really does help force better designs.
- johnfn 2y agoI found this to be a pretty poor article. The article lacks concrete examples and speaks in generalities to explain its core thesis. But without specificity, a reader can nod along, sure in the knowledge that they already are wise and follow this advice and it's everyone else who's out there mucking up code bases with layers upon layers of garbage. I mean, > The next time you reach for an abstraction, ask yourself: Is this truly simplifying the system? Or is it just another layer of indirection? Is anyone reading this truly going to alter their behavior? If I could recognize that my abstraction was "just another layer of indirection" as simple as that, obviously I wouldn't have added it in the first place!
- ahuth 2y agoI don't know. Sure, examples could be nice. But an article can be imperfect and still be interesting or useful. Also, it's easy to say come up with examples, but I find it hard sometimes. In any case, I find it a nice article. Will it change how I write code? Maybe, maybe not. But it will change how I review code and talk about abstraction.
- toolslive 2y agoI don't consider TCP an abstraction at all. The abstraction is the unix API over it, and then again, the > ssize_t send (int socket, const void *buffer, size_t size, int flags) is not a nice one. When was the last time you had the data in a buffer, wanted to send it over to the peer at the other side, but didn't mind that it's not sent in it's entirety ? So you have to write a loop over it. Also, is the call blocking or not ? (well, you'll have to read the code that created it to know, so that's no fun neither). However, it does prove the point the author is trying to make: good abstractions are hard to find! Anyway, I tried to think of a better example of a good abstraction and found the "Sequence" that's available in plenty of programming languages. You don't need to now what the exact implementation is (is it a list, a tree, don't care!) to be able to use it. Other example I found were Monoid and Monad, but that's tied to the functional paradigm so you lose most of the audience.
- phtrivier 2y agoLet's not forget about a particularly frustrating kind of "level of abstraction": the bespoke interface to a part of the code that has side effect, and that has exactly two implementation : one in the production code, and one in the tests. If I were to create a language tomorrow, that's the one aspect where I would try something that I have not yet found elsewhere : can you make it so that you can "plug" test doubles only for test, but keep the production path completely devoid of indirection or late binding. (I'm curious if you know of a language that already does that. I suppose you can hack something in C with #ifdef, of course...)
- deleted 2y ago[deleted]
- nmilo 2y agoThe author misses the whole point of abstractions, and that is a layer B that covers layer A so completely that no one using layer B needs to know how layer A works at all, except for those working on the layer A/B bridge. For example, binary logic is a perfect abstraction over semiconductor physics. No one doing computer science needs to understand anymore the complexities of voltages and transistors and whatever. TCP is a perfect abstraction over IP. Memory as a big array of bytes is a perfect abstraction over the intricacies of timing DRAM refreshes. And that's about it. No one reading this post has written an abstraction ever. (a leaky abstraction is not an abstraction). So yes, actually, abstractions are free, and they don't leak. That's the whole point. The problem is that what you call an abstraction isn't an abstraction. Computer science's complete failure to create a new abstraction since like TCP intrigues me. Why don't we have a system X that abstracts over memory accesses so well that no one needs to know anymore how caches or memory locality works? Why aren't there entire subfields studying this?
- oalae5niMiel7qu 2y ago> When was the last time you had to debug TCP at the level of packets? For most of us, the answer is never. Who are you people who never have to debug TCP problems? I've had to do it on multiple occasions.