Previous: You cannot have at-least-once broadcast
It's not that often that one encounters a really hard problem. Complex, yes, you deal with that daily. You have to integrate with different mutually imcompatible technologies, you keep bumping into edge cases and so on. But a problem that's hard in its essence, not very much so.
Hard problems are rare and precise. That's why I want to share the one I stumbled over recently. And, to warn you in advance, I have no freaking idea about the correct answer.
And here's the problem:
Have you ever had to make sense of an unfimiliar Java codebase? (If you have no Java experience, replace it with C++, or maybe with some other language, although Java fits the best.) You open some random file and look at the code. You see some abstract classes inheriting from interfaces and other abstract classes, some templates forwarding method calls to other templates and similar stuff. What you need is a line of code that actually does something, so that you can figure out what the program is supposed to do. However, finding such line is not an easy task. It looks like there's nothing but scaffolding everywhere.
Finally, you find a line of code that actually does something. However, it's alone in it's own function. There are other such lines in other mini-functions. And those functions are invoked via five layers of abstraction and it's not even clear how they fit together, whether one is invoked before another one or other way round. Or maybe they are independent and can easily run in parallel. You have no idea.
Anyway, you know what I am talking about.
Now, imagine that I am your grandmother and I ask you about your job. You show me some code, you show me a line that prints a message on a screen, another one that converts celsius to fahrenheit. And I (or your grandmother, really) ask: "What are all those other lines doing?"
And that's my hard question.
Just remember that I am your grandmother and you can't trick me by saying "it's abstraction" or something similarly vague. I don't know what abstraction is. I just want to know what are all those lines good for.
It's a week since I started to think about the problem and I still don't have a slightest clue. The only answer I was able to come with was an evolutionary one: The code that's good at reproducing reproduces. It doesn't have to be useful to be ubiquitous. But that's a defeatist answer. Surely there is at least some value for us programmers in those lines of code. But if so, what exactly is it?
Martin Sústrik, September 14th, 2015
Previous: You cannot have at-least-once broadcast
I despise code like this - to be able to (in the general case) decode all that junk you need to re-implement the compiler…
Honestly? I'd say it's the code a better language (or library, or framework) would make unnecessary. It's logistics - the supply train getting code to data and data to code - but it's only there in the first place because they're so dang far apart.
A large part of why I've found myself hugely enjoying Rust is because of its focus on composition rather than inheritance, which together with its tendency towards using concision in the service of clarity makes it quite nice to work with.
For a good example of this, check out the "clap" argument parsing library (kbknapp/clap-rs on github), specifically the "Quick Example" in the README.
Looking at that example code, I would have trouble picking out ten lines of scaffolding - and reading it, I can get a clear idea of what it's doing without much effort at all.
But regarding the virtual/abstract morass specifically?
Rust has Traits, which are roughly akin to C++ concepts. If you want to use something, you have (to a first approximation) three ways to do it:
A trait is defined in some module A, and a struct is defined in some module B. The struct's implementation of that trait must live in either A xor B if it exists. Some methods may be inherited from the trait definition, but that only happens if the implementation block doesn't do anything at all for them. And even so, that's still in module A.
Due to this, the last two of the above only have at most one level of indirection regarding behavior. The first, of course, has none.
They're allowing you to make adjustments here and there without breaking stuff. You can replace the screen with a file on the other side of the internet without changing everything else.
It's kind of like asking what an electrical outlet is for. It lets you swap out a toaster for a microwave without ripping up the wall.
If your grandmother is in the knit hobby, tell her these lines serve as the knitting pattern. You will never wear it, but it is usefull nonetheless.
Abstraction is what you do as a programmer. You take code that does something and snip it into all these tiny little code shavings.
The opposite process then happens in the computer. The computer takes all these little code shavings and strings them together into an execution sequence that makes something happen. (Or not.)
The point of doing abstraction is to cut the string of execution into segments so you can change beads. And each bit of “all this other code” describes the shape of one bit of string.
(The reality is of course that every line of text makes something happen, as far as the computer is concerned. The difference between “string” and “beads” is something that matters only to as humans.)
As for your grandmother, you can explain that computers are really dumb chefs and programming is the act of writing recipes for them. Then abstraction is easy to explain, starting from switching out a few ingredients, then also changing the preparation of one group of ingredients (e.g. different kinds of pizza with toppings that have to be cooked in different ways) and so on, up to describing a whole category of dish (like, soup or pasta or stew or whatever). Then describing the shape of the recipe matters as much as the ingredients and the steps to prepare them.
All the other lines answer the question, "What do those pretty obvious lines really mean, in detail?". If you have a lot of similar questions (function/procedure calls, statements) and similar answers (definitions) in a program, it makes sense to reuse answers and parts of answers, which may require splitting them into parts and describing ways of combining them.
The more you do that, the less text there is in total, but the price you pay is that it can be hard to see how everything is connected, especially if the program is big, which is why it's important to cut programs into mostly unrelated chunks before that happens.
A program is as much a description of what the computer could do as what it should do when you actually run it.
I had experience with java code that had multiple objects referencing each other and having their methods calling the methods of the objects they reference.
Finding where the final code was required to untangle the string of method calling passing from each object multiple times.
Because of that, there is a simple rule to solve this:
A method should only be able to mutate the originating object. All other parameters should be constant.
There should be only one mutable reference to an object.
job security. for them.
I think I can pinpoint where things went wrong—the corruption of an oldschool unix adage about interfaces: "build programs with straightforward access to components while hiding details of the implementation."
All this happened as soon as concealing the details of a program became more than acceptable, but considered widely as a positive engineering aim. The second it becomes cool to hide shit you give merit to unscrupulous abstraction, honoring form without function.
I think one could say they're akin to cables in electronic/electric installations, or (if the grandmother is a bit older), pipes in water installations. In that they: connect things doing real stuff with other things doing real stuff; and also allow to change the connections pretty easily when needed.
Does that make it for you? or nope? :)
The value of these lines is as incentive, providing psychological comfort/sense of fulfillment to the developers, so that they can keep maintaining the parts of the code that do the actual work.
What is really more fascinating is that the very same people that decry the contrived complexity of the codebase, tend to rationalize it as they start groking it (sense of fulfillment), and then typically leave their mark by add yet another distinct layer of abstraction (psychological comfort). Rationally challenging, or (gasp) removing useless abstractions is looked upon as act of hostility to whoever added it.
Post preview:
Close preview