On Developers as a cargo-cult
I just saw Jack Rusher’s presentation at StrangeLoop called „Stop writing dead programs”
and while I agree with him on a few points, especially the idea that we have became some sort of a cargo-cult, not necessarily contained but suggested in the presentation, there are some things he gets wrong, and I felt like explaining a bit how I see things. But I’ll start with the positives.
The presentation
Indeed, as he notices, a lot of the parts that are hard about programming come from doing things that are not directly related to the problems that we’re trying to solve. He spends a bit of time going through how to increment a list of numbers in ASM and C, then goes fast through other languages to show that you can do things better - the best being APL with the following statement: 1 + 1 2 3 4
which would return the desired list of 2 3 4 5
. That’s nice.
Next, he goes on to explain how the punchcard philosophy still influences today. The compile and run phase are different because this needed to happen because you want to run your code elsewhere, but that (he suggests) is a relic of the past. He then goes on to show the keyboard that Bill Joy used to write vi for, explaining the quirks of the editor and a few of UNIX. He then argues, strongly and convincingly, that developers should have the best tools possible, that docker
is just a cover-up for us not being able to do things right so we add another layer and wreck development experience as well, and that we should have the best tools and the best development environment possible. And I agree. But.
1. We are as good as our tools are
And by tools I mean, programming-wise, libraries. Going back to the ASM code he displayed, I was amazed by the usage of the malloc
call - and felt strange that this was needed, but at the same time the memory needs to be managed somehow and indeed, calling malloc
is appropriate. However, the C code shows the problems better:
int *add_one(int array_length, int array[]) {
int *result_array = malloc(array_length * sizeof(int));
for (int i=0; i<array_length; i++) {
result_array[i] = array[i] + 1;
}
return result_array
}
There are many things that we can improve about this, but first of all, why do we need a new array? Do it in place, and you get two lines of code. But if we don’t want to, we can see that the ASM version leaks, as well as this one. But I can offer you a version that doesn’t leak.
constexpr std::vector<int> add_one(const std::vector<int>& array) {
std::vector<int> result_array;
result_array.reserve(array.size());
for (auto x: array) {
result_array.push_back(x+1);
}
return result_array
}
Iaiks, this is longer. Can we improve? Yes, definitely. But before we do let’s think what we want to do. Because if we want to calculate what is [1, 2, 3, 4]
and add 1
, then this C++ version offers us (in the C++20 incarnation) a version that does this with 0 CPU statements, because it can calculate at compile time. But that’s not what we’re interested in. So I suspect that you want to do something like „add a number to an array”, which makes sense. But this is already happening in various languages, granted, not C or ASM.
>>> import numpy
>>> numpy.arange(1, 5)
array([1, 2, 3, 4])
>>> numpy.arange(1, 5)+1
array([2, 3, 4, 5])
One statement, all good. And I can imagine a C++ class called Vector or Array that can override the +
operator to allow such statements as vec+1
. Heck, I implemented a few since 1996.
The point I’m trying to make is not that Jack is wrong about his digression, but that the languages he pointed at (granted, not C or ASM) are more powerful than he claims them to be. C++ might be a pile of hot garbage, but for a pile of hot garbage it can do its job quite well. But also the problem is a bit more complex than what he explains there - there’s the topic of what happens to the memory that you don’t see in APL, but you painfully see in C and ASM. Why don’t we all do what APL does?
Mostly because of my next point.
2. We don’t afford the cost
And in this case we don’t afford the cost of debuggable programs at all times, but also the performance cost of non-system languages. Some problems require to squeeze the last bit of performance out of your system, and the cost of keeping it debuggable at all times is too great. Not saying that it’s not fun to have those facilities, and when available they should be used as much as possible, but reality is quite a different story. Not everyone affords the price, and, in fact, almost nobody affords the price. Sure, if you write complex machines that perform a function complex enough that you can afford the cost of doing things a bit differently, more power to you. But generally embedded engineers deploy on machines that can accept the bare minimum of code needed to run the operations, often with little or no memory to spare.
3. We can’t leave debuggable things in the open
Now, one of the things Jack Rusher argues is that we should all have the ability to debug things on running systems, which is plain wrong. There are many reasons not to do that, so I’ll give a few:
- Code is still intellectual property, and sometimes it’s very costly intellectual property, so we can’t afford leaving that merchandise available and modifiable in the open.
- We barely trust legitimate users to the systems, having a live debuggable system is an invitation for any illegitimate actor to attack our systems.
- Performance wise we can’t afford it, especially in a world where you pay for processing seconds and memory usage.
And you can argue that „we don’t leave them in the open”. But his suggestion is yes, we should. We should have introspectable servers and services. Instances modifiable at run-time. Explain to me how you’d detect intrusion and modification of a running program, unless you have a whole layer of new tools to memorize all the states of the running code. Modern processors actually make it expressly difficult to modify running code, to stop a number of issues that might occur, choosing the other side would just make us dump all those security features in the garbage bin. But there’s one more reason
4. We emulate a system that works
So Jack Rusher argues that we don’t have better ideas and we still use the batch programming model from the 60s. True. But we do that because it works. Because we don’t want to deliver a programmer together with each product sold, to fix the issues that the person might have with the acquired solution. The system of writing the code in one place and running it in another is necessary. It’s a system that works, and that will work for quite some time from now.
Of course, there’s a load of cargo-cult in what we do. Yes, you’ll have people who will say that navigating with hjkl
is the best way to do it. But the problem is always to create a system that works better. Give me a more debuggable system that gives me more performance than C++ on the project I want to deliver, and I’ll take it. Oh, I have to double the RAM and quadruple the processing power? Ah, thank you, but no.
But I suppose that AI will change that for all of us, and make software development obsolete.