8 ms·
The Cost of Abstraction (2016)
- theaeolist 8y agoStructuring 1M line of code as one function or 1M functions is clearly equally absurd. What is the sweet spot? There must be an answer from psychology and/or information theory.
- jstimpfle 8y agoThe answer is: What is the problem? It depends on the problem. We're unlikely to encounter a problem that is solved with a single funtion, or as one function per line. But the important thing is to look at what the code should achieve. Most importantly, local decisions should be made based on what the code should achieve in a global context. Local syntactic optimizations are less important than the global picture. I strongly agree with the Golang authors and with experienced C programmers that it's much better to write a few lines more and be more clear and explicit in exchange. Note that there are some additional lines that increase complexity, but I would argue that lines which contribute to clarity do not contribute complexity. In fact, those investments in additional lines usually decrease the number of moving parts. Syntactic homogeneity is important so one can easily see what one piece of code achieves in the global context. It does not help if we micro-manage and constantly think about the type of for loop or lambda abstraction or error handling mechanism to use, only to shave another line off. Unfortunately looking at the actual problem is what most people forget. Instead the discussion are about languages (filter, maaap. maaaaap), frameworks, libraries, object orientation without any concern of what these features can do towards reaching a specific goal. Now that you mention information theory, I want to mention the term "Semantic compression", created by Casey Muratori. I think he has done at least one stream about it, which you should be able to find on YouTube. In general I recommend to follow him. He is one of the most experienced and no-nonsense guys I've found on the internet.
- taneq 8y ago> What is the sweet spot? There must be an answer Keep things 'square'. This is a fuzzy concept and I don't really know how to explain it properly, other than that the effort spent on each layer of abstraction should be roughly equivalent. Your example is a single 1m-line function or 1m one-line functions. In this case you probably want 100 functions of 100 lines each (and yes, refactoring like this you probably save 100-fold in overall LoC so it works out.) And your 100 functions are probably nested in a 10-deep hierarchy where they all do roughly the same amount of cognitive work.
- amelius 8y agoThat's a really simply abstraction there, and I bet most people won't even call that an abstraction. It's a bit like saying bricks are useless, by limiting the entire argument to a single brick.
- coldtea 8y agoIt's almost like he's making a small non-real-life example to illustrate his general point, which is not confined to the particular example.
- JoeSmithson 8y agoYour comment reads just as well without the condescending "It's almost like" at the beginning
- coldtea 8y agoYeah, but the condescension was put in to counter-balance the parent's facile dismissal of the author/TFA. In other words, to add a cost (the possibility of being dismissed/sneered back) to such dismissals.
- house_atr 8y agoAnd yet the author is still making a dumb point with an even dumber example "polymorphism so hard me brain hurt aBstRaCtIoN bad"
- c22 8y agoThis whole pointless sub-discussion has had a small cost for me because for some reason I bothered reading it. A simple downvote of the original comment could have provided feedback to the author while sparing the rest of us.
- afarrell 8y agoI think its better to give feedback explicitly rather than sneering. The latter just feels bad to read (even as a third party) but doesn't really espouse some better active social norms to follow. > to add a cost (the possibility of being dismissed/sneered back) to such dismissals. Given the fact that this is a conversation among strangers, I would assert that it isn't really that effective to just add costs by making discourse less pleasant. -- In general, I think a community is healthier when we treat people 25% better than you expect to be treated, to account for the Fundamental Attribution Error and other misinterpretations.
- hu3 8y agoI've seen excessive abstraction kill projects time and time again. The crime scene is similar in every case: - What if we ever need to change database server or driver? Proceeds to write a layer to abstract data access - We might have different login forms one day? LoginFormFactory it is - This code hits our KV Redis/memcache/NoSQL by calling the driver lib directly? Can't have that, I'll write a CacheStorage or DocumentStorage layer. Over time, this defensive mentality produces codebases that are so bloated that no one in the team can confortably grasp all the moving parts despite the project not being rocket science. At that point, devs rather quit or get a substantial raise in order to continue working and ultimately endup leaving to start a new project. But this time, they'll be sure to not make the same mistakes. This time, they'll abstract even more so "when one part of the project becomes bloated, it can be easily rewritten thanks to the abstractions". And the cycle continues.
- jstimpfle 8y agoWhich is not to say that we shouldn't wrap dependencies. We absolutely should. But by default, the wrapper should not offer an abstraction that is so flexible that the dependency can be exchanged. Instead, it should start with the actual problem, by offering only abstractions that (a) make sense in the context of the program and (b) translate well to concepts of the dependency/library. For example, hiding POSIX-style select/poll vs Win32 Completion ports behind a common abstraction is something I tried recently, and seems to be HARD. Did not complete. Why? I started with the abstraction without being familiar with what the problem was. All that I knew was that I wanted to experiment with "game engine design".
- Nursie 8y ago> Which is not to say that we shouldn't wrap dependencies. We absolutely should. Why? If I'm writing something for a known environment that's unlikely to change, it seems like a waste of time.
- jstimpfle 8y agoEven then, often there will be small bits that don't fit well. By wrapping everything you make the model and assumptions of your application explicit. Code that is written against your own model (which is almost always simpler than what the library offers) will be more understandable. I really like the Dijkstra quote, "The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise." Usually I even wrap libc and POSIX functionality in my C programs. Advantages include (1) not having to mess with specific types, such as size_t, time_t, DIR*, and so on -- which often don't fit my own model (e.g. dealing with unsigned size_t vs signed integers is painful), and often need small fixes to be portable. (2) compiles a lot faster since I don't need to include 10000s of lines from system header files (3) not wrapping makes huge portions of the project dependent on the library, instead of only the wrapper. (4) You tightly control access to the library, which makes it easier to troubleshoot. You can also make up your own strategy for managing library objects in a central place.
- pdpi 8y agoI find that there's two core points that help me figure out where I'm missing some form of abstraction: the body of a function should only operate on a single level of abstraction, and it should only know technical details about one thing. If you're writing some database code, having high-level fetchMyEntity() calls mixed with connection/resultset/cursor logic is bad news. If you're writing something that reads from a message queue and stores the message in a database, the place where the two meet shouldn't know much of anything about either the message queue or the database. Obviously, exceptions exist where these rules need to be broken, but I find they're fairly few and far between.
- wwarner 8y agoYes. I look for something I can measure when I make this decision. Will it result in less code? Will it improve performance? Will users/customers care?
- base698 8y agoThe "Simple Made Easy" talk describes this as the root of complexity in an application. Mixing and tying various subcomponents that shouldn't have to know each other exist.
- taneq 8y ago> Mixing and tying various subcomponents that shouldn't have to know each other exist. And this is the real trap when dealing with abstractions - maybe the implementations of two different operations are the same, but if the semantics are different then de-duplicating them creates an unnecessary dependency between the two. The more cross-links in your application structure, the harder it is to do anything without breaking everything.
- pdpi 8y ago> And this is the real trap when dealing with abstractions - maybe the implementations of two different operations are the same, but if the semantics are different then de-duplicating them creates an unnecessary dependency between the two. One of the truly remarkable things about Haskell is that its approach to structural abstractions allows you to break out of this problem (though you end up paying a different cost for it)
- echofavor 8y agoThanks for sharing. You can also share your ideas for free on www.echofavor.com
- denart2203 8y agoVery insightful. I like thinking about software complexity and one of the concerns there to deal with complex software is that the design and intentions should be communicated (which means either documentation or exist as a common understanding of purpose and function). From your perspective, it means that there is also a need to establish agreements on the levels, depth and ways abstractions in the code are formed. Indeed, I worked with software where the functions and operations weren't implemented in a messy way per sé, but the many levels of indirection, abstraction (and obscurement) made things just really difficult to read and a real tail-chaser when it came to maintenance. Those levels can also make it much more difficult to understand the flow and the operations that are happening, because in many languages you pass references to data objects, so data gets changed in many ways. Nice article, puts me into thinking mode again! :)
- thunderbong 8y agoRule of Three https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming) https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra...
- martin_drapeau 8y agoI totally agree that the cost of abstraction should become more important. It is often the number one cause of frustration when trying to add a new feature to an existing code base. Building a mental model of any code base takes time. Abstractions for the sake of abstraction makes it harder to grasp.
- danielovichdk 8y agoAbstractions are of course due when time is right. It's difficult to address but imo not be the goal from the get go, to introduce abstractions to a codebase. There is also a cultural thing around abstractions where inexperienced developers look up to or are fascinated with the complexity of something or someone that brings that to the table. It's also a common feature of certain so called architects, because they probably feel they need to some advanced techniques. The thing is though, that when you have worked with developers or architects that advocates for simple abstractions, and it over time proves that is both efficient and cheap, then you start to doubt complexity in total. Also remember that complexity is often not complex per se, as long as you spend time on breaking that complexity down. And then you have a better fundamental platform for solving what you need to. Simple code is fast code. And also easier to change.
- chestervonwinch 8y agoLately I've been thinking about the similarities between the abstraction that results from post-hoc software refactoring and post-hoc mathematical proof "refactoring". In both cases the refactoring is an attempt towards a more ideal form, but that form is almost always more impenetrable to newcomers. E.g., a quote about the mathematician Carl Gauss: > Gauss' writing style was terse, polished, and devoid of motivation. Abel said, `He is like the fox, who effaces his tracks in the sand with his tail'. Gauss, in defense of his style, said, `no self-respecting architect leaves the scaffolding in place after completing the building'.
- sorokod 8y agoIntroducing an abstraction is a way of extending the "base" language into the domain of the problem being solved. Viewed in this way, creating an inc_pair() function does not make sense beyond applications that deal with incrementing stuff. On the other hand, if we are moving a player on a grid, move_diagonaly_up() makes sense and is worth introducing into the extended language vocabulary.
- nkingsy 8y agoThese abstraction discussions seem to always result in commenters talking over each other. I would love to have these conversations rooted in a code sample. Otherwise no one is talking about the same thing. If someone writes a blog series called “should this be abstracted, what’s the abstraction?” I think we would see some great discussion.
- karmakaze 8y agoThere's a difference between deduplication/extraction and abstraction which often seems to get lost. Abstraction is not about the mechanical reorganization of code. It is the structuring the code to follow a natural/logical abstraction that exists outside of the code. The first clue is the name of the abstraction. If it describes how the 'abstraction' works or what is going on inside it, then it's best to leave it. An abstraction should be able to opaquely represent what it is. This is the value of abstractions, it let's you not think about what's inside while working at a higher level. A similar issue I have is with people constantly 'refactoring'. I choose to say I'm 'factoring' code instead. If you can't name the factors that you're separating then it's likely you'll change your mind an end up 'refactoring' it. Sometimes you take factored code and factor it further, which I don't have a different name for, just more factoring.
- hyperpallium 8y agoAbstraction should be based on whether the abstraction makes it easier or harder to reason about: to see, to predict consequences of decisions, to diagnose causes of behaviour. Usually, the established abstractions of the domain are an excellent guide. Even if you think they are sub-optimal objectively, they are easier to reason about for experts in that field. I like the article's language of comtaining the "damage" code can do. A C-style modularizarion technique I haven't seen used is long functions, with sections' variables' visibility limited by braces.