I generally agree with "use only as much cognitive overhead as needed to get the job done" ( I'm enjoying that term ) - but it seems like a slippery slope that could slowly bring everyones abilities down to the least capable developer.
I generally agree with "use only as much cognitive overhead as needed to get the job done" ( I'm enjoying that term ) - but it seems like a slippery slope that could slowly bring everyones abilities down to the least capable developer.
To me, it's a bit like telling a person how to get from point A to point B by telling them when, how hard, and for how long to step on the gas and what angles to put the steering wheel at. Sure, that might work to get someone out the driveway, but good luck getting to the grocery store that way.
I will genuinely never understand the go philosophy that "excessive" abstraction is bad, with no attempt to justify why current levels of abstraction — which are charitably hundreds of times more complicated than the difference between, say, go and ruby — are good, right, and just. Everything we do on a computer from processing to memory access to networked communications to rendering to handling input to relational data modeling to… anything is dozens of layers away from what's actually happening. But somehow right now we're at the optimal level of abstraction and any more would just be too much. Okay.
I think falcolas's philosophy is spot on - treat most problems plainly and simply. In the rare event a genuinely difficult problem arises, use your tools to get it done right.
There's a reason why we're solving the problems of today today, and not two decades ago.
You're right that code is read many more times than it's written. That doesn't argue against abstraction — it argues for it. But it argues for choosing the right abstractions, and that's where I feel like we do a poor job as an industry.
I've worked in large code bases with no abstractions and the grass is assuredly not greener. Every solution to every problem is as I said before — akin to reading directions specified in pedal pressure and steering wheel angles, rather than in terms of streets and turns.
Completely agree with this point. My go-to example of why abstraction should be used to increase readability is street directions. Imagine somebody asks you for directions to a restaurant, and consider how different your answer would be depending on how well the other person knows the city. If they are a tourist, you might need to give them very detailed directions, down to the level of "go forward X blocks, turn left, continue for Y blocks", etc. Conversely if they are a native, the directions could simply be "It's right next to Z". Obviously the latter directions are useless to the tourist but the former are far from ideal for the native -- it's just way to verbose, easy to forget and easy to get confused by. Understand your audience.
It seems to me that Go is part of a reaction from people who are not happy with current levels of abstraction.
Abstraction is fundamentally the process that allows us to solve progressively harder problems, by sharing knowledge of our solutions to similar problems. It's central to our understanding of math, physics, biology, psychology, and every other scientific field you can name. Software engineering is no exception.
Yes, some abstraction is beneficial. But too much abstraction leaves you with, you know, the decrepit state of modern software. Abstraction as you actually see it isn't letting us solve "progressively harder problems", it's letting GMail take up more RAM than my OS does from boot. Meanwhile most of the really interesting problems are still done in fairly primitive languages.
You mean the ones built on a handful of abstracted 3D engines like Unreal, Source, id Tech, CryEngine, Unity, etc?
> Linux
You mean the operating system that abstracts away from us things like filesystems, hardware access, shared memory, time sharing, networking protocols, access control, multicore processors, etc.?
> Rust
You mean the programming language that adds a new abstraction of "borrowing and ownership" to help simplify the confusing existing abstractions of memory management and data race detection?
> Vulkan
You mean the API that abstracts away parallel computation on graphics hardware, upon which they intend for dedicate graphics abstractions like OpenGL and WebASM to be abstracted?
> WebASM?
You mean the API that abstracts away running low-level machine code — not on your own machine, but sandboxed on remote machines via web browsers.
> Meanwhile most of the really interesting problems are still done in fairly primitive languages.
Do you want to take a guess why I'll argue that you consider the obscenely complicated task of "delivering secure, multiuser, interactive applications with offline persistent data storage to hundreds of thousands of remote clients" a non-interesting problem?
It's only because of the incredibly successful and complex network of abstractions that are Ethernet, IPv4, TCP, DNS, TLS, HTTP, HTML, JavaScript, SQL, graphical image formats, web browsers, database servers, caches, switched networks, load balancers, and so on that these kinds of achievements that would have been considered monumental even thirty years ago are not "really interesting problems" today.
Abstraction has allowed us to break apart the component parts of this incredibly complicated problem into small, independent, coordinating layers. The fact that you think that something like delivering web applications is a non-interesting problem practically makes my argument for abstraction all by itself.
AAA or kernels games are traditionally written on a low level, i.e. C with maybe some convenience C++. Especially for memory management there just isn't a good one-size-fits-all abstraction. Look for AAA games or kernels in Java.
Another problem of OO for high-performance or complex architecture is the mindset it inflicts upon developers. It motivates decomposition in "independent" objects, while this is just not possible. You have to think about processes in the large, about dataflows, about how to structure information and how to handle cross-cutting concerns, etc. Getting at these problems with an OO mindset leads right into desaster. Look up data-oriented programming. Look how a game engine is architected. There are lots of memory and resource managers, for example. But nobody would think of a Vertex as an individual "object". Or break his head how to design a character object, when related data really has to sit all over the project (character world position and state, associated 3d resources, sound resources, ....).
>> Rust
> You mean the programming language that adds a new abstraction of "borrowing and ownership" to help simplify the confusing existing abstractions of memory management and data race detection?
So far, it's just an attempt to enable mindless OO-style programming without inflicting a runtime, but I guess with very limiting mechanics. And it inflicts a huge syntactic overhead. I predict the applications of Rust are very limited.
Game programmers are usually (as far as I know) not very busy with memory management problems.
> Ethernet, IPv4, TCP, DNS, TLS, graphical image formats, switched networks, load balancers
These abstractions exists because they are necessary. They are a bit of a mess, admittedly, but invested people have worked hard to make what they were given as small a mess as they can manage, and what is left exists because it has to historically. When your packets get pulled through hardware specifically designed to do exactly that, the abstraction almost ceases to be. (Disclaimer: not a network person)
> HTTP, HTML, JavaScript
These abstractions are why 12 billion instructions per second per core counts as slow now.
asm Go Ruby SQL
reality <---------------------------------------------------> abstract
Their point is something along the lines of: there's not as much justification for why all that stuff to the left is "good" abstraction, versus the justification for why the tiny extra bit to the right is "bad". The line between good/bad abstraction seems to be fairly arbitrary.When you ADD two registers, you really are making two reads in the register file, which really is a piece of physical hardware, and those reads really do go to a physical adder and get written back to the real, physical register file. And languages like Go translate reasonably (but not exceptionally) straightforwardly to assembly. Even if you don't know all of the details that go into making it fast, your first guess of what it's doing is pretty much right.
In contrast, adding some lazy FP callback into your latest node.js framework on DOM objects might be doing something, somewhere, but who on earth really knows?
This is because it's an excellent layer of abstraction on top of the machine code which is interpreted by the abstraction of microcode which sends it to the abstraction of a physical processor that's itself just an abstraction on top of transistors that themselves are an abstraction around manipulating voltages on a complex network of circuits.
Nobody bats an eye at these levels of abstraction. And they're not perfect! Modern CPUs have hundreds of errata. Skylake's alone is almost 40 pages long, at 3-4 per page.
But you take them for granted because they're several layers lower than what you have to deal with on a regular basis.
Even your example of adding two registers required compiling through an assembler, scheduling execution by a time-sharing operating system that fakes the concept of running hundreds of parallel processes by rapidly looping through running processes, and on and on and on.
We are awash in a sea of abstractions more deep and complex than even a dozen of the world's best engineers put together could hope to fully understand. And yet people earnestly defend language design decisions that prevent a single function from comparing two values of any arbitrary numeric type as being too complex, when issues like that are less than a hundredth of a hundredth of a hundredth of a percent of the complexity that are modern computers.
> “The M0 is a third of the size of the M3 in its minimal configuration,” ARM CPU product manager Dr Dominic Pajak told EW – 12,000 against 43,000 gates.
That same page is 2.8MB compressed, the half of which is Javascript decompresses to 2.6MB.
Yes, a big OoO core has a lot more to throw around, but the vast majority is spent on tricks to make things go faster. The layers are thinner than you expect, and they're built that way on purpose. The hardware below assembly is a far smaller jump than the browser in the sky.
[1]: https://www.electronicsweekly.com/news/products/micros/arms-...
You are not arguing against abstraction. You're arguing against the user-hostile influence of advertising on delivery of content on the web.
You've elided a pile of abstraction here too, like what it actually means to "read" and "write" the physical register file. Additionally, CPUs do a ton of microcoding of operations, so simple instructions like a 2-register ADD may correspond to the "obvious" thing, but more complicated ones will go through more hardware/do more things.
The gulf of abstraction between the physics and any programming language is vastly larger than differences between programming languages, so it's weird that all of the hardware and operating system and C+GC is the right abstraction, but "tiny" extensions, say, like hardware and operating system and C+GC+generics^ is not right.
^ (Any of the common ways to implement them.)
> In contrast, adding some lazy FP callback into your latest node.js framework on DOM objects might be doing something, somewhere, but who on earth really knows?
This is the false comparison! Even Go can conceal arbitrarily complicated operations behind function calls. I can make a Go API that takes closures to execute later too. (There's even core language support built on this idea, `defer`, that behaves in a way that's resistant to the straightforward (RAII-like) implementation.)
If we're going to be talking about things at the instruction-level, you can definitely complain about the dynamism of Ruby forcing even simple + to be (quite) a few more instructions, but you need to consistently look at either the micro-scale or the macro-scale, or explicitly connect the two.
If you were doing DOM manipulations in Go, I'm sure, say, listening for some events (like a "lazy FP callback" might be) would be at least a similar order of magnitude in terms of non-obviousness to the JS version. It's fair that different language/ecosystems may encourage/lend themselves towards different coding styles, but this is most evident with apples-to-apples comparisons.
Yes! This is why I argue that the problem isn't "too much" or "excessive" abstraction. We're already so far down the rabbit hole of abstraction that another layer or two are peanuts to the amount of abstraction we implicitly accept as being totally normal, reasonable, and okay.
The issue is bad abstractions. And we as an engineering discipline have lots of them, at all layers of the stack! I'll even happily entertain the notion that generics^ (to borrow your use) are a bad abstraction. I don't agree, but at that point I think we're at least debating something worthwhile — as long as the alternative isn't "no more abstractions", but is some other layer of abstraction that better approximates some high-level concept we want to model.
Fighting against more abstractions is a losing proposition. We need to be focused on how to develop good abstractions, so we can progress the field on solid, stable ground instead of on the quicksand it feels like we've been progressing on for the past decade. In my opinion, Rust is a phenomenal leap forward here.
Abstraction in itself is a good thing. I just think of it as "compression". We have to express solutions to problems in the shortest way possible, otherwise they become unmaintainable.
What most people fail to see that a dead simple language like C provides with all of the tools to solve many problems with good or optimal abstraction. And if you add abstraction like OO and implicit memory management to solutions for many complex or performance-oriented problem domains, these solutions become effectively non-solutions. Because in these problem domains, decisions about memory allocation or data organisation are part of the solution.
The issue people should be fighting against isn't excessive abstraction, since the only way to do so is to counterproductively inhibit abstraction in the general sense. We as an industry should focus on how to develop good abstractions while avoiding bad ones.
Not that I have any particular insights on how to approach that problem. I'm just convinced that avoiding abstractions at all is throwing the baby, bathtub, and entire bathroom out with the bathwater.
No, that's almost exactly right. In my experience designing, building, inheriting, and maintaining systems that in some cases predate the dotcom boom, and in others need to run invisibly for just as long into the future, it's the simplest design that's the best design (but, as they say, no simpler).
I'm not talking about avoiding useful language features because nobody wants to learn them. I'm talking about the structure of the system itself. Minimize layers, minimize context, keep things as linear as possible. Don't use polymorphism just because you can, don't use functional tricks that obscure rather than enlighten the flow, don't make version zero an SOA, etc.
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”
Juniors/freshers never get this and always try to impress with their cleverness. KISS will always be true.