I think abstraction is good in theory, but at the end of the day the implementation is what is happening. The classic thing of people relying on undocumented behavior, etc etc.
What good is a conceptual representation that doesn't actually align with what happens? It's not like the gas pedal being pressed down is actually what makes a car accelerate, after all! Whenever you're dealing with something _not_ working as expected, those who know the implementation are going to be in a much more comfortable position in general IMO.
Abstraction is absolutely necessary in practice. It's the language we use to tell a system what we want it to achieve. It's the only way for us to know what the system can do without starting a science project. It's the only way for the implementer to know what to implement. And it's the only way to check whether a system actually does what it's supposed to do.
Of course, knowing how a system actually works is always better than not knowing it. Every abstraction is leaky as so many side-channel attacks clearly demonstrate. But that doesn't mean the abstraction is some theoretical pie in the sky that we can live without.
Sure, if they need to actually know how something works (because, say, they want to dig in and modify the system itself), then of course they need to understand what happens below the abstraction.
But if my goal is "drive a car", then it may not be helpful to know anything about that pedal beyond "car starts moving when I press the accelerator pedal". Because the nice thing is that I don't need to know about fuel injection or whatever happens when I press the accelerator on an ICE car. And the really nice thing is that if I switch from an ICE car to an EV, the basic usage is the same, and it doesn't matter that something completely, entirely different happens when I press that same pedal on those two different cars.
(Ok, well, now we have one-pedal mode on some EVs, but... otherwise...)
It can become a problem for when someone wants to make the switch from the consuming side to the producing side of things, or even just be part of a team that wants to do that. Every person first needs to be able to identify ones own faulty concept as misconception, and any decision made during this process involves a lot of risk.
For example, with the car analogy, you could have a very simple model that says that the accelerator pedal makes the car go faster - the harder you press the pedal, the faster the car goes. This model will break down, however, when you start driving in rough, wet, or snowy conditions. For those conditions, you need a more complex model that can take into account things like torque and grip. (I think: this analogy is taking me to the limit of my car knowing!)
I worked on a project involving a device that could be configured. The backend developer's mental model was that the device was always in a configured state, and that you could then save the current configuration as a kind of preset, or replace the current configuration from an existing preset, or just change the configuration on the fly.
The users, however, had a mental model more like files: they would load a configuration, make changes to that configuration, and then select a different configuration, expecting the previous configuration to be updated. In the end, these two mental models proved to be too incongruous, and we needed to switch the backend to use a different system internally that better matched user expectations. (Another option would be to build a UI that made the backend model more clearly.)
In the case of git, the problem to me is that both mental models (diff-based and snapshot-based) are true and shown in different places. If you rely entirely on the diff-based model, you can use git very well, but you'll get weird errors when you start cherry-picking commits. If you rely entirely on the snapshot-based model, you'll be fine up until you want to rebase something, and then git will feel like dark magic. Both of these models can represent a lot of git operations, but if you take them too far, both will allow you to make a number of assumptions that aren't true.
(I think there's also a nice analogy to physics as well here: Newtonian physics is a really good model of the way the universe works, it allows you to do a lot of very precise things with maths. But if you try and build a GPS system using only Newtonian physics, you'll find that your model doesn't match up to reality any more.)
The problem seem to be that the interface wasn't clear about whether it would save implicitly or explicitly.
Which makes me think leaky abstractions aren't so bad as long as they don't facilitate a mental model that works opposite to "what it leaks". Essentially it seems fine as long as it's close enough, like in the example of the car.
> It's not like the gas pedal being pressed down is actually what makes a car accelerate
In proximity to the driver it certainly is the cause.
Even in a good old ICE car the throttle is not as straightforward as “more ‘gas’ equals more acceleration” (depending on many factors, such as road surface conditions, it can also make you go slower or stop completely).
An airplane would take it to a whole new level. What throttle does is dependent on fuel mixture, air density at your altitude, the configuration of controls—in fact, it’s probably much easier to enumerate the limited scenarios in which throttle actually makes you go faster as opposed to straight up crash.
There can be made an argument that smart systems could/should, with their layers of abstraction, maintain an illusion of ‘gas’ = acceleration, and that human should not have to know anything else; but would you trust your life to such an arrangement?
There can be an argument that stakes are high in case of air travel, and low in case of VCS; but that falls apart if you consider source code managed in a VCS can indeed power a life-critical system, and if not knowing well enough how the VCS works can lead to development overhead and eventually bugs in that system, then human cost is real.
If that isn't possible, the problem lies with Git, not with users "not understanding" it.
Now, aside from that I think I generally agree with what you are saying. Git's UI, especially the way things are named, does feel very much tied to the implementation in ways that are a problem.
Which, funnily enough, isn't a problem at all, unless the abstraction is leaky, or the purpose of the thing itself is poorly explained.
The abstraction is "the way things work". If not, something is fundamentally wrong with the design.
Of course the canonical, annoying examples of the leaky abstractions involve software stacks, where we end up writing a bunch of code, and find that things work great until weird edge cases happen, and then we get upset and have to dig through the abstractions to figure out what's going on.
But the leaks in a lot of real-world abstractions just don't matter. Like the accelerator pedal in a car. That one does leak a bit in some places: for example, pressing the pedal isn't linear. On a (usually ICE) car with a geared transmission, pressing the pedal hard when in gear one gives you different results than when you press it hard when in gear six. But then you switch to an EV and the response is different. Or even switch to a different ICE car with a different transmission, and the response is different. But ultimately that doesn't really matter; the person driving gets used to it in very short order. The leaks in that abstraction just don't matter all that much.
That’s the least of your problems, because it leaks profusely. We could start with the fact that in many scenarios the “accelerator” pedal does not, in fact, accelerate. This is not an edge case—any driver who had to drive on ice, snow, dirt or otherwise not have traction knows that. What it does do reliably is make the wheels (some or all, depending on your car) go faster.
> But ultimately that doesn't really matter; the person driving gets used to it in very short order.
Let’s recap. I believe you were saying that the abstraction is leaky, the system does not always do what we expect, and even though many of us don’t really care to understand how the system works it’s OK because we sort of adjust and roll with it because for all its faults the system is good enough (and, perhaps much more importantly, we’re used to it). Hold on, it’s almost as if you’re talking about one well-known version control system…
…and on balance I’d agree with you, except I think a serious software engineer is more like an auto mechanic or professional racer rather than a casual driver—“press the pedal to go faster” doesn’t really cut it, and we should have a good and correct model of how Git works.
Abstraction as in the throttle example is more like ORM vs. SQL (we all know someone who ran into issues with the former in a non-trivial project). You simplify the idea of X to Y, which works in Z% but breaks down otherwise.
It is pretty much this topic; how to reconcile complexity and confusion.
He talks a lot about reaching designs of irreducible complexity as a way to mitigate confusion.
For what it’s worth, I’d say that Git is pretty far from irreduciby complex and thus has preventable confusion baked into it.
>> Of course not,
As long as the user is a software developer I would disagree. If the developer cannot understand the implementation concept style of explanation, I don't want to use or review their code either. I am saying implementation concepts as object storage, branches being pointers to objects etc. Of course implementation details and optimizations like packfiles don't matter.
Every user-friendly explanation will have its limits once we get merges, rebases etc. For software developers it is important that their mental models don't hit such limits.
If the user is not a software developer you have a point.
Likewise for engineering tools. If you'd like to use a simple abstraction as your mental model, you'll be able to operate the tool in the basic sense without issue. But as engineers, when something goes wrong with our tools we often don't immediately bring it into a mechanic. We need our tools to do very specific things, and we must understand what they did each time we perform an operation. We establish workflows that build on top of the tools and depend on the tool matching our precise expectations of its behavior. In essence, we are the mechanic for our own car.
And of course, our needs are much more diverse than "make car go forward". So inevitably, we run into trouble more often. What user-facing function of a car is as precise, powerful or intricate as a textual merge or reconciling two diverged work histories? There are many ways those operations can be done, and questions the user must answer about their desired result.
Git actually presents a very simple view to the user: repository states across time. These states can be compared to each other.
That doesn't explain commands like rebase or cherry-pick or even patch. The diff algorithm that generates diffs based on history is very much integral to git, even if it is configurable. There are plenty of git commands that assume you can meaningfully transform a commit into a diff from its parent(s). So commits are sometimes snapshots sometimes diffs, depending on the tool you're using to interact with them.
I'm just curious -- which of the two do you think is the correct mental model to safely drive a car? If you think its the former, my guess is you've never driven where there are mountains and/or never driven manual.
Certainly my understanding of it is a bit more nuanced than simply "accelerate", but ultimately.... "press pedal harder, car moves faster" is... fine? Sure, you have to consider road conditions and (if driving manual) your current gear, speed, RPMs, etc., but... I dunno, I still think "press pedal harder, car moves faster" is a useful mental model to have, to some non-zero level of approximation.
Put another way: if I were to think of all the details of what the accelerator of a car actually does under the hood every time I pressed it, and consider the implications of it and allow that to inform how I press the accelerator, I would have precious little time to actually drive the car... like, the things that actually matter, like steering and staying on the road and avoiding other cars and people and whatnot. When I press the accelerator, I think about the effect it's going to have on the car. And I have a good idea of what effect it's going to have based on my experience driving that car (or any car, really). Not based on any knowledge -- knowledge that I lack! -- of what pressing the accelerator actually does.
That's why experienced drivers drive so much more safely than novice ones. It's certainly not because in the meantime, they somehow learned how the throttle works internally.
The same basic lesson applies to software. The best design is the one that lends itself most to human intuition. Because when saving a photo from an image editor, users sure as hell aren't thinking "now an inode is created with this and that metadata".
Cries in sad turbo noises
That would mean, I think, that the implementation details are irrelevant, because it's abstracted well enough to serve its purpose - and it's so easy that usually the FIRST driving lesson takes you into actual traffic (at least in Poland).
But ultimately, the implementation should involve choices and the possibility of change. git commit is saying the current state of the staged tree should be recoverable by any future user of this same repo. There are many possible ways to achieve this goal and how git happens to currently do it is worth knowing for some people. It's certainly been fascinating to read the months of output Julia Evans has put into this git plumbing exploration series. But I don't think it's critical for every single user of git to understand at this level of detail exactly how git works, and in principle, the developers of git should be able to change this without changing the semantics of what it means to commit in any user-facing way.
Of course not, and nowhere outside of software engineering will you find such widespread confusion between concepts and their implementation.
Do we think of pressing the gas pedal in a car as "accelerate", or as "regulate the intake manifold"?
Commits are one of the most high-level concepts in Git. If an explanation for them resorts to implementation details, it's a bad explanation.