Earlier today I saw two unrelated posts that somehow lead back to human engineering being compared to evolutionary engineering. The first one is a slightly naïve (but nonetheless interesting) post on Discover Magazine called “Linux versus E. coli” which brings up research made at Yale, which compares how Linux evolved to how E. coli evolved*, and another completely unrelated post by Cory Doctorow on Locus Online Perspective, where he talks about being the victim of a phising attack, despite technical competence and general wariness online. In his post, Doctorow draws the correlation between human engineered networks (internet) and evolutionary engineered networks (the human body) and mentions how parasites infect each in strikingly similar ways.
It reminds me of a talk at the Voices that Matter conference last month in Seattle, where Erik Buch talked about MVC, but also about his experience in the Aerospace software development field, where bugs must not be, and how the development strategies for this field differ greatly from those of e.g. games, where a crash will lead to a pouty kid or a drop in sales, at worst, rather than burning, stinking bodies splattered against the ground around a crashed airplane.
If you think about it, traveling thousands of feet up in the air across the globe is a rather extreme thing to be doing, if you don’t have wings to keep you up there, but we do it all the time. We’re at the verge of taking that step into space (which in some ways is less extreme than air travel), and I can’t help thinking what evolution can teach us about maintaining autonomous bodies with up-times that average 70-80 years (i.e. us humans).
In software development, we oftentimes streamline and optimize by ripping stuff out and “sharpening” the software to where it does everything it should with as little code as possible in as little time as possible, but nature doesn’t really think this way. Sometimes nature does the obviously taboo thing in software engineering, namely copy-pasting functionality around. It’s like a merging of the philosophies behind RAID and the philosophies behind optimized code. With millions of years of experience, our Great Programming Mother has made some very curious design choices, and all of them have been extensively unit tested and thought through by changing fractions of the code, testing it extensively, discarding failures, and keeping successes. Very similar to how software developers write code, just infinitely more thorough, and over a much greater span of time.
Nature takes things into account at a far greater scope than we do. For example, let’s say we have code in an automatic sliding door. The code does 4 things: it detects movement on either side (A), it opens the door (B), it waits (C), and it closes the door (D).
- For this code, we have 2 sensors (one on each side of the door) for motion. We code these sensors to call up “B” (open door) whenever we detect movement.
- We code up “B” to open the doors wide. When “B” is done opening the doors, “B” then calls up “C” (wait).
- We write “C” to wait for X amount of seconds and then to call up “D” (close the door).
- We finally code up “D” to close the doors.
When we try our door, with the wait time set to 3 seconds, we realize pretty quickly that if two people walk through the doors with only a few seconds in between, person #2 will almost get the door in their face, so we tweak our code a little.
- In “B”, we open the doors if they’re not open already. If they are open already, we call up the new “E”.
- “E” we code up to reset the timer in “C”. Thus, every time someone triggers the motion sensor on either side of the door, the 3 second timer is reset.
And there we have a human engineered sliding door in operation. Now there’s one very distinct thing we’re doing here that nature oftentimes does differently: we presume that it works.
A lot of code that we write, we write with the following notion in mind: I have to take every scenario into consideration, and have to account for every possible thing that could happen, and to deal with that in a good way in my attempt to perform “X“.
But nature’s code works on a different methodology — it goes something like this: I am trying to do “X“, and it might fail or it might succeed.
Nature works based on a fire-and-forget philosophy, where the reaction to situations is much more “lazy”, while being infinitely more flexible. The downside to nature’s approach is redundancy. Nature copy-pastes code all over the place because it has to. It has to double- and triple- and quadruple-check to see what’s going on, because it discards the idea of guarantees of success.
In the above sliding door code, nature would have most likely made a lot of different choices on the implementation. Fewer presumptions on what works and what doesn’t, and more redundancy to cover up for potential failures.
In regular software engineering (not the aerospace software kind), the place where our code looks the most similar to nature’s code is in initialization routines. Take for example this very simple example, where the sound card, graphics card, joystick and keyboard modules are initialized for a game system — emphasis on the concept, not on the (bad) code example:
int error_code;
error_code = sound_init();
if (error_code != ERR_NONE) {
alert_user("Sound initialization failed! Check your sound card settings. Sound has been disabled. (internal error code = %d)", error_code);
sound_enabled = false;
}
error_code = gfx_init(true); // true for hardware accelerated graphics
if (error_code != ERR_NONE) {
alert_user("Graphics initialization failed! Check your graphics card settings. Software rendering enabled. (internal error code = %d)", error_code);
error_code = gfx_init(false); // false for software renderer
if (error_code != ERR_NONE) {
alert_user_and_exit("Graphics initialization failed, again! We can't even do software rendering. Aborting! (internal error code = %d)", error_code);
}
}
int use_keyboard = true;
if (setting_use_joystick) {
use_keyboard = false;
error_code = js_init();
if (error_code != ERR_NONE) {
// fall-back to keyboard
use_keyboard = true;
}
}
if (use_keyboard) {
error_code = kbd_init();
if (error_code != ERR_NONE) {
alert_user_and_exit("Keyboard initialization failed! (internal error code = %d)", error_code);
}
}
The above code is relatively robust. It doesn't presume that something is going to work, and it tries to deal with the situation if it doesn't. It disables sound if the sound card can't be initialized, it tries to fall back to software rendering if the hardware accelerated rendering fails to initialize, and it falls back to the keyboard if the joystick (is enabled and) fails to initialize. It pukes at the user only if the graphics or keyboard can't be initialized at all. This stands in stark contrast to especially low level code where the implementation simply cannot afford to do a bunch of fail checking. The lower we step in the hierarchy of code, the further towards hardware modules we sink, we find that the flexibility decreases.
When your computer freezes up and the mouse just sits there on the screen no matter how much you wag the version on your desktop around, that's when someting went wrong down below. Imagine for a moment what would happen if this was a biological machine that just "froze". The closest similarity we can find is when we're knocked over the head and everything goes black and we go unconscious. We might compare it to a kind of "reboot". Though I think a closer comparison is heart failure. If a heart failed as easily as a computer freezes up, we'd all be dead by now.
And that's kind of the point. A heart won't just fail like that. The ultra-low components of our body aren't made to do-or-die. They're made to do their best, always, and nothing but, and to make the best of the situation always. Unfortunately, writing code that "does its best and makes the best of the situation" is hard, to put it mildly.
Which brings us back to aerospace software and space travel, because aerospace software, software for a space ship and nature all have one thing in common: they mustn't fail, ever. If Microsoft Windows imploded on board a space ship, the pilot couldn't just put in the CD and reinstall. If somewhere in the system, there was a bug that caused trouble, it shouldn't wreak havoc throughout the ship's system.
A bug is a stupid little insect**. They just cause an itch. An insect doesn't knock you unconscious or cause your heart to fail.
The good news is, this isn't new and groundbreaking stuff. Erik Buch stated that to date, not one single person has ever died from software failure on an airplane. I'm not sure the same can be said about space ships though... quite a number of them have spontaneously exploded at launch, and I'm not sure they know exactly what caused the disaster in each of those cases. I just know that if you find yourself on a space ship several years worth of travel away from Earth, and something goes wrong, you can't likely call home and ask for a helping hand. I think it's also safe to say that without computers and software to automate things, we'll never be able to go into space without a dedicated education on our belts, and as such, we will be relying more intimately on computers than ever once we go "out there".
Which I kind of hope is pretty soon.
---
(* and while the research generally concludes that "the two are more or less completely different," trackbacks on the comments section read "How the Linux Operating System Is Like the E. Coli Virus". Sometimes people are more interested in talking about what is being talked about than getting their facts straight)
(** aside from a few dangerous ones!)
Pingback: Tweets that mention kallewoof.com » Organisms and software. And space travel! -- Topsy.com