4 ms·
Something related that's been on my mind recently: a lot of the advantage in building well-designed programs (documented, modular, built to be tested, free of s
by geofft 7y ago
Something related that's been on my mind recently: a lot of the advantage in building well-designed programs (documented, modular, built to be tested, free of smells, etc.) is less about getting the program right than about getting future changes right, either by someone else, or even yourself when you've forgotten how to do it. But future changes only matter if you want to use the existing program as a starting point.
McIlroy's pipeline is a little bit hard to read, but I would bet that most people with moderate experience in building shell pipelines could rebuild something equivalent from scratch, even if they'd have trouble explaining how the current pipeline works. (Or people with experience in Python, Perl, etc. could throw together an equivalent script from scratch quickly.)
An implication is that, if you're in a language where you can write a productive program to do a task from scratch within (say) 30 minutes, there's a lot less of a reason to think about good programming design than if you're in a language where doing that same task from scratch is likely to take you a day. In the second language, most of the value of writing documented and well-structured code is so that it takes you 30 minutes when someone asks you to modify it. But in the first language, you can throw away your code when you're done with it and still solve the modified problem within 30 minutes.
Another possible implication: it's better to build reusable components (libraries and utilities) than easily-modifiable code. Part of why McIlroy's pipeline works so well is that tools like "tr" and "uniq" exist - and most of us will never have reason to modify the source code of those tools. We need to know what their interfaces are, but we don't need to know their internals.
- jeffdavis 7y agoWhat you are describing may say more about the problem definition than the rate of change. If the software will only be used in certain limited situations, then you can make a bunch of assumptions and assemble the solution from spare parts. As long as your assumptions hold everything is fine. When the assumptions fail, someone will tell you, and you can rewrite it with the new assumptions in mind. But some software might be used in a lot of strange environments with strange inputs. A lot of effort needs to be spent trying to clarify and improve semantic and performance edge cases. Using libraries here is not necessarily helpful because sometimes you spend more time working around the edge cases in the library than you would just re-implementing it. I recently re-implemented a minheap even though there was already a library readily available, because the library assumed pointer-width types and I needed it to support longs. On my machine, a pointer and a long are the same length, but that's not guaranteed.
- geofft 7y agoRegardless of environment, good software engineering (where by "software engineering" I mean "the process of producing code that continues to do useful things over time") requires a feedback loop from how the software is being used in production back to the developers. If your assumptions are failing, and you know about it, then you can deliver new code to match the new assumptions, either by rewriting it or by modifying it as makes sense. If your assumptions are failing and you don't know about it, you're in a terrible position. You can make further assumptions about which of your assumptions might fail (e.g., "the software is being compiled on a machine where a long is longer than a pointer") and which won't, but those are again assumptions - you don't know what changes you haven't thought about might come down the pipe (e.g., "the software is being translated to JavaScript, which has no longs, only floating point" or even "there's now a requirement to use a stable sort, so a heap is out of the question"). It's not at all clear that building rigid, well-armored software and throwing over the wall can even work. If at all possible, effort is better spent closing the feedback loop.
- jackweirdy 7y agoThis comment reminds me of something of a thought-experiment-come-hacky-side-project that I can’t seem to find. The premise was, what if you could never edit a function? Instead, you had to delete it and recreate it entirely whenever changes were needed. The incentives are twofold - keep functions small, so you don’t lose too much investment if you have to delete code, and think before you start writing, else you waste time. As I remember, this project came with an AST manipulator that would helpfully delete your function if it’s unit tests failed.
- fasquoika 7y agohttps://github.com/munificent/vigil https://github.com/munificent/vigil
- jackweirdy 7y agoYes! This is it
- geofft 7y agoYou know, thanks to Hyrum's Law https://www.hyrumslaw.com/ https://www.hyrumslaw.com/ , there's a variant of this in many engineering contexts: you can never remove functionality directly, you can only add new functionality, deprecate (but maintain) the old ones, and kill it once everyone has moved. Put that way, it's surprising that people haven't felt incentivized to ship the smallest and most replaceable things.
- squiggleblaz 7y agoThey have and they called in npm.
- danhite 7y agoThe project you remembered but couldn't locate is likely Prune within Limbo in the TCR (test && commit || revert) experimental development style by Kent Beck et al which you can review here: https://increment.com/testing/testing-the-boundaries-of-collaboration/ https://increment.com/testing/testing-the-boundaries-of-coll...