11 ms·
The Law of Leaky Abstractions
- amelius 7y agoArticle could use a few more examples.
- bonoboTP 7y agoYes, it would be nice to see something other than speed issues and slow networks. I think encryption can be one. If you don't know the internals and handle it as a black box, you can lose security. For example if you encrypt a string twice with a one time pad, you get back the original. Another could be presenting content. For example saving content as html or pdf. Pdf has a concept of pages and you may have aesthetically suboptimal pagination if you try to abstract away the final format. You may even need to cut a sentence from the content to have optimal visuals. Or if you tried to consider all image formats equally and autosaved to JPEG every minute while editing, the lossy compression would degrade quality. The fix is usually exposing the particular aspect on the interface. The problem is that usually new things that we think should fit some abstraction turn out to have peculiarities we didn't think of when designing the abstraction. We thought our abstraction is more general and future-proof than it is.
- yasth 7y agoJoel's article ( https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/ https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a... ) has tons. A classic trap for new players is that in most modern compiled languages you can add strings and get a string, but strings are in fact immutable and can't be added without making an entirely new string and disposing of the original two. This means "a" + "b" is actually a horrible way to build up a string if you have to do lots of little additions, so most languages have some other method of making a string of strings/chars (StringBuilder in Java and C#, strings.Builder in Go, etc).
- thesz 7y agoI have a counterargument for you: https://www.researchgate.net/publication/221200232_Algorithm_Recognition_based_on_Demand-Driven_Data-flow_Analysis https://www.researchgate.net/publication/221200232_Algorithm... It's variant called rewrite rules is used in Haskell's compiler for ages and is a heart of good vector algorithms (on par with C, including SIMD C). I can't find a paper I read in 1998 or so where successive calls to fputc were replaced with fputs and with some other rules and same approach the OpenGL code was optimized to be as fast as possible. It is pity that research that is twenty years old was not put into C# compiler. And for a bonus, look at program distillation: http://meta2012.pereslavl.ru/papers/2012_Jones_Hamilton__Superlinear_Speedup_by_Program_Transformation__slides.pdf http://meta2012.pereslavl.ru/papers/2012_Jones_Hamilton__Sup... They transform quadratic concatenation algorithm (O(n^2)) into linear one (O(n)).
- mankeysee 7y agoWhy are strings made immutable by default in some langs (I have seen this mainly in java and python)? Nothing fundamentally requires strings to be so. Has some analysis been done indicating most string operations in software would benefit by immutable form rather than non-immutable form?
- ChrisSD 7y agoAt least in Python, string objects are widely used as keys to dictionaries or as options in functions. For speed and efficiency these small strings are "interned" so that there is only ever one instance of the same string. Also for mutable strings you either have to allocate enough memory to fit the final result or have some kind of rope data structure. Or else you end up copying it anyway.
- seisvelas 7y ago> most modern compiled languages you can add strings and get a string, but strings are in fact immutable and can't be added without making an entirely new string and disposing of the original two. In my mind, I add two strings like so: https://gist.github.com/seisvelas/c11d200d0040a3686e47af0068614cd6 https://gist.github.com/seisvelas/c11d200d0040a3686e47af0068... That is, I realloc the first string to fit the second inside of it, then add the second string into the new space. But I have no idea what I'm doing. I'm posting this comment so someone can explain to me why my way is bad and why I should be creating a new string to put the others inside (which apparently is what everyone else does!)
- deleted 7y ago[deleted]
- indogooner 7y agoHowever, for a magnetic drive, reading data sequentially will be significantly faster than random access (due to increased overhead of page faults), but for an SSD drive, this overhead will not be present. Even SSDs have overhead for random access although not as much as spinning disks (~3 times IIRC)
- mehrdadn 7y agoI never understood how the myth that SSDs have no random access overhead became so prevalent and oft-repeated. Did nobody ever measure?
- auscompgeek 7y agoNowadays there are NVMe SSDs with significantly less random access overhead, asymptotically approaching no overhead at all as I understand.
- dspillett 7y agoSSDs have effectively zero random read access overhead when compared to traditional drives, because the overhead is a couple of orders of magnitude smaller. Also for SATA connected SSDs the effect of this read latency is reduced by bottlenecks elsewhere. For the common home/office/other user the difference between zero and effectively zero is, well, effectively zero, so the two easily conflate. It isn't so much a myth as a convenient simplification. (The fact that there is still latency is very easy to show though - just throw something like crystaldiskmark at an SSD and show the measured throughput difference between the sequential and random tests.) For NVMe drives where the bottlenecks of SATA are removed the difference starts to become more noticeable, and on any SSD random write latency is more significant than random read latency, but NVMe has only recently become common for the general user and the tests that people usually look at are random read not random write as for those common users this is the most significant measure in terms of how it will affect their day-to-day use patterns.
- rswail 7y agoYou could say it's a leaky abstraction...
- CapsAdmin 7y agoMaybe https://github.com/denysdovhan/wtfjs https://github.com/denysdovhan/wtfjs counts as leaky abstractions. It's definitely something that's difficult with building a language. Abstracting and generalizing your language parser can lead to amigious behavior.
- ryanbrunner 7y agoI see this fact used pretty often to dismiss the idea that we should use abstractions at all, and I think that's pretty wrongheaded. As an example, it's pretty common to hear that people should prefer writing raw SQL to using an ORM or query builder because those are both "leaky abstractions" Abstractions aren't necessarily only there so that you don't need to understand anything about what's being abstracted. They are there so that your code can get away from nitty-gritty implementation details, and be more focused on the problem domain. What would you rather see when you're coming into a new codebase: const formData = new FormData(); formData.append('name', 'Widget'); fetch(`/api/widgets/${id}`, { method: 'POST', body: formData }) or: Widget.save({ name: 'Widget' });
- mannykannot 7y agoSQL is itself a high-level abstraction, and I have seen cases where an ill-conceived data abstraction layer has just added complexity without improving the quality of abstraction (I have also seen many cases where poorly-designed database schema have nullified the relational model's power of abstraction.) With regard to your question, I want to be able to see both the abstraction and the implementation, as, depending on what I am doing at any given time, I might need to understand either.
- sergiosgc 7y ago> As an example, it's pretty common to hear that people should prefer writing raw SQL to using an ORM or query builder because those are both "leaky abstractions" The takeaway of ORMs being leaky abstractions is that they should expose the lower abstraction. An ORM builds the query for you 90% of the time, but when (not if) you need to write raw SQL, the ORM should be able to integrate the raw SQL output in its flow. An example of an ORM that does this beautifully is SQLAlchemy [https://www.sqlalchemy.org/ https://www.sqlalchemy.org/]
- gchamonlive 7y agoAbstractions are also great to create sections of testable code separate from other parts harder to test (database comm, requests, IO...) But they can also get in the way when they are too coupled with the code or when the layers of abstractions get out of hand. "the only problem that more abstraction can't solve is too much abstraction" On a side note on orm leaky abstractions, that was particularly clear to me when using cakephp. It was really hard to debug what was going on in the background and getting the actual query being run with its placeholders in place is a nightmare. SQLalchemy in contrasts is extremely close to the actual query and with code you can find online you can actually generate the sql string and it is always very close to what I had in mind in the first place, thus properly integrating the extra expressivity python has into building sql queries.
- TickleSteve 7y ago"for a magnetic drive, reading data sequentially will be significantly faster than random access (due to increased overhead of page faults)," ...err... no. Magnetic drives have slow random access due to seek-time, i.e the time taken for the head and disk to physically change position. In comparison to that, SSDs are effectively zero latency but they still have read-ahead/buffering/caching latencys to deal with.
- mannykannot 7y agoThe quoted text appears to be comparing the sequential and random-access speeds of magnetic disks, so the differences with respect to SSDs does not come into it. On the other hand, I do not understand what the author means in the following clause, where the slower random access is attributed to the overhead of page faults, unless the author has in mind a specific (and unmentioned) scenario involving memory-mapped access (and if page faults are the issue in that scenario when using magnetic disks, why would one not have the same issue when using SSDs? I would have thought the causality goes the other way: the overhead of page faults is higher when using magnetic disks because of their relatively slow random access.)
- narag 7y agoThe difference is that magnetic disks have a reading head that must physically move (slow) so it's much faster to access data under the current head position. I guess access time is roughly the same for any data in an SSD.
- TickleSteve 7y agoPossibly... All I can surmise is that the OP may think disks are addressed in a memory-mapped fashion and hence may be subject to page-faults for some reason. (Obviously, they're not).
- tonyarkles 7y agoReading that generously and since we’re talking about abstractions... reading from disk via mmap does work via page faults! Except... it’s a layer up. Doing random reads on an mmap’d file will likely have terrible performance until those pages have been cached, but one layer down there’s no guarantee that sequential reads from an mmap’d file are going to be sequential reads from disk! (Because the file isn’t guaranteed to be laid out sequentially on disk) Others in this discussion have talked about some abstractions being perfect and a consumer not needing to understand the layer beneath; I strongly disagree. Ultimately, the physical reality of the machine will come into play (disk, RAM, caches, network, CPU etc), and I am generally uncomfortable if I don’t have a solid feel for how the high-level operations in an abstraction are going to use those resources.
- jasonkester 7y agoA fun example from SQL Server that bit me the other day. Observe this simple proc: create procedure SearchJackets @buttonCount int as select * from jackets where @buttonCount is null or buttonCount = @buttonCount It's a common pattern you use for multi-param search queries that lets you avoid building your query as a string. Normally there are lots of little filter params in there, but we'll just show one for now. "Computer: List me the jackets, and maybe just show the ones with a certain number of buttons." That version takes 60 seconds to run, whereas this version takes less than a second: create procedure SearchJackets @buttonCount int as declare @buttonCountCopy int set @buttonCountCopy = @buttonCount select * from jackets where @buttonCountCopy is null or buttonCount = @buttonCountCopy ... because it kicks the optimizer upside the head and convinces it to reconfigure the query plan in an efficient way. There are hints you can use at CREATE time that are supposed to do the same thing, but they don't actually work in this particular case. So now one of my codebases has this hack sprinkled about in a couple "Advanced Search" pages. Fun stuff.
- SkyBelow 7y agoIn your second proc, should set @buttonCountCopy = buttonCount be set @buttonCountCopy = @buttonCount ?
- jasonkester 7y agoYes. Fixed.
- manifestsilence 7y agoAnnoying that that should be necessary, but good to know! Out of curiosity, what does that particular inefficiency look like in the execution plan? Does it do a full table scan when @buttonCount is passed in null, or was it inefficient for any value?
- hackinthebochs 7y ago>There are hints you can use at CREATE time that are supposed to do the same thing, but they don't actually work in this particular case. Are you saying that "option(recompile)" doesn't work for you in this case? It works just fine for me when using that 'X is null or' pattern for optional filters.
- tabtab 7y agoRe: Modern practices like 'Microservice Architecture' can be thought of as an application of this law (The Unix Philosophy), where services are small, focused and do one specific thing, allowing complex behaviour to be composed from simple building blocks. An oop class or API can do the same thing. Microservices are overkill unless multiple applications will be sharing the service, and even then may have unnecessary overhead compared to say stored procedures.
- crimsonalucard 7y agoIt's like the 2nd law of thermodynamics. A surjective epimorphism. Information is lost as you go higher and higher into the abstractions.
- hcarvalhoalves 7y agoI feel "abstraction" (as a term) is thrown around a lot, but should only apply to the conceptual system (aka, while in the computer science realm). Once you're dealing w/ the implementation and it turns into something concrete (aka the engineering realm), it's just a layer of indirection (and it serves its purpose by reducing coupling), but you still have to consider that underneath all you're talking to some machine, over the network, there's latency, it can timeout, there could be a load balance in between, the cache may not be coherent for consecutive requests, you assume some operation is both atomic and produces instantaneous side-effects, and so on... if you ignore these details you end up w/ a system that looks great at a conceptual level but is full of problems and race conditions because you ignored physics. TL;DR "Leaky abstraction" is pretty much a tautology if you look up the meaning of "abstraction"?
- CSMR 7y agoA better formulation: Abstractions do not adequately describe the full working of a system. So higher-level approaches will always require a knowledge of lower-level operations. However, the opposing and terribly named "Dependency Inversion Principle" is true much more often: "High level [approaches] should not be dependent on low-level implementations."
- peter_d_sherman 7y agoGreat list of programmer laws, ideas, and understandings, not just "The Law Of Leaky Abstractions" (which of course is a classic in its own right...)
- aznanchevalier 7y agoThis one is really awesome.... https://coincircle.com/l/0w737YEhqa https://coincircle.com/l/0w737YEhqa
- aznanchevalier 7y agoThis is awesome!! https://coincircle.com/l/0w737YEhqa https://coincircle.com/l/0w737YEhqa
- _bxg1 7y agoI think the key is to make sure all leaks happen in the sphere of performance, not correctness. So you can use the abstraction on its own without fragility, and then you can learn more about it if you want to tighten up performance.