In the two decades or so that I've been writing a column, we've covered a lot of topics, ranging from the best implementation of abs(x) to CRC algorithms to logic theory, Karnaugh maps, and electronic gates, to vector and matrix calculus to Fourier and z-transforms to control theory to quaternions, and lots more. I even wrote about whether or not the ancient Egyptians really built the values of Π and Φ into the Great Pyramid (they did. And , too).
Almost all of the topics involved involved math, but they also required software, either for embedded systems or just to illustrate a given algorithm. But I've never talked much about the way I write software. I thought the issue of my own personal software development methodology would be, at best, a distraction from the topic at hand and, at worst, a subject of great hilarity.
I've always known that the way I write software is different from virtually everyone else on the planet. Is this true for everyone? I don't have a clue.
That's why I thought it would be fun to share with you the way I write software. If you do it the same way, that's great. It would be nice to know that there are other folks who share my approaches. On the other hand, if it's not the same way you do it, we have three possible outcomes. Either you'll learn from me, and decide that some of my approaches have merit; I'll learn from you, and decide that I've been doing it wrong all these years; or my revelations will serve as a subject of great hilarity. Any of those outcomes, I submit, would be good. It's a win-win-win situation.
The environment
Let's begin with the software development environment. In my time, I've used some really rotten development environments. I've worked with systems whose only I/O device was a Teletype ASR-33. We got so tired of emptying the chad-catcher on the paper tape punch, we just let it flutter to the floor. (At the end of the day, guess who got to sweep it up.)
I worked on one program where my only "compiler" was a line-by-line assembler (meaning that it didn't accept mnemonic names, only absolute addresses). The bulk storage device was an audio cassette. Just so we're clear, this wasn't a homebrew system I cobbled up out of old Intel 4004s. paper clips, and chewing gum. It was a system built up by serious, if boneheaded, managers in a very large corporation.
Perhaps because of these nightmare environments, I really appreciate a good one. I especially love using integrated development environments (IDEs), in which all the tools are interconnected, preferably by a lovely and fast GUI interface. I know, I know, real men don't use GUIs or IDEs. Some folks much prefer to use only command-line interfaces. and to write ever more complex and inscrutable makefiles.
If that's you, more power to you. Whatever works. But I can't hide my view that such an approach is an affectation, involved more with earning one's credentials as a software guru than on producing good code. I'm sure that there are good, valid reasons to write a makefile rather than let the IDE do it for you. I just can't think of one, at the moment.
So what do I do when I'm told that I must use the environment the project managers already picked out? First of all, I'd ask them (always discretely, of course, in my usual, ultra-tactful manner) why on earth they chose the development environment without discussing it with those of us who had to use it. Second, I'd campaign mightily for an environment more suited to my approaches.
As an aside, I used to teach a short course in the development process for embedded microprocessors. I told the management folks that they should plan to give their software folks every possible computer tool available—hang the expense. I pointed out that software development is very labour-intensive, so the front-end cost of good tools would be quickly made up by increased programmer productivity.
Needless to say, sometimes I'm successful in changing the managers' minds. More often, I'm not. So what do I do then? I make do as best I can, but I discreetly tweak the system and my approach to make the environment behave more like an IDE. For example, I might add script files, hot keys, etc., so that pressing a single button would invoke one tool from another.
Requirements
Crenshaw's Law #42 says: Before setting out to solve a problem, find out what the problem is. Law #43 says design it before you build it, not after.
In the software world, this means performing requirements analysis. Write down, in as much detail as you can, what problem you want the software to solve, how you want it to solve the problem, and how you want people to interface with it.
In the 1980s, the popular project plan was to start with an analysis of the system requirements. Then you decompose into software requirements, design requirements, etc. At the end of each phase, you hold a formal review before moving onto the next step. No fair skipping ahead.
They called this process the "waterfall" approach. If you were writing a formal proposal to NASA, DoD, or whoever, a clever picture of the waterfall could win you the contract. In the waterfall diagram, the "code and unit test" phase of the plan was usually the next-to-last step in the process (the last being integration and test). And woe be upon anyone who skipped ahead to writing code.
Today, the waterfall approach has been completely discredited and deemed ineffective. Personally, I think the only reason it seemed to work was that nobody actually followed it. Some of us practitioners broke the rules, and skipped ahead to try out our ideas on the side. The whole thing was a sham. We had slick-looking and impeccably dressed salespersons giving formal presentations to the customer, showing them how wonderfully the waterfall approach was working, and assuring the customer that we were 90% complete. Meanwhile, a few of us slaved away in a secret back room, using approaches that actually worked.
But those were the bad old days. Today, the hot keywords are spiral development, prototyping, incremental and agile approaches, and extreme programming (XP). I'm a big fan of XP, though I'm not sure that what I do fits the mold.
Today, no one in their right mind would still support the waterfall approach, right? Well, I don't know. In 1984 our software managers had a rule: you were not allowed, on penalty of—something bad—to even run the compiler, until your software had passed code review. Even then, the compiler was never to be used for developing test drivers and supporting unit testing. It was only to be used on the entire system build, after all integration had been done.
You should go back and reread that paragraph, to really get your arms around the concept. We had a nice, and relatively modern, timeshare system (Unix), with a nice compiler, programming editor, debugger, and other aids, but we weren't allowed to use the compiler. We were perfectly free to use the editor to write the code but only to document our designs for the code reviews. We were expressly forbidden to actually run the compiler. How brilliant was that?
But that was 25 years ago. We don't do anything so ridiculous now, right? Well, let me see ....
On a more recent project, I heard a manager berate a colleague for actually writing code before the requirements review. If he had been working in his old company, the manager said, he would have fired the guy for such a violation of the rules.
That was way back in . . . let me see . . . 2008.
I won't say much more about project planning or about requirements analysis here. In part, it's because using my approach, the requirements evolve with the code. Call it spiral development, if you like.
I start with code
The method I use to develop software may surprise you. In the worst possible tradition of the anti-waterfall plan, I jump right into code. I sit down at the keyboard, flex my fingers, crack my knuckles, and type:
void main(void){}
That's right: I write the null program. I do this on every new start. If I'm in a really adventurous mood, I'll add:
cout << "Hello, Jack!" << endl;
and maybe even:
x = 2; y = 3;
cout << x + y << endl;
This is not a joke. I really, really do this. Every time.
Why? I think it's just my way of assuring myself that the genie behind the glass screen is still awake, and still doing his job. And my computer and its operating system haven't gone berserk overnight (not all that unlikely, these days),
Mostly, I do it to get myself in the mode of expecting success, not failure.
Several years ago, educators came up with the notion of "programmed learning." The idea was to write the textbook very much like a computer program, complete with loops and goto's. After teaching a few facts (never more than three or so), the text asks a series of questions. If you give the right answers, you get to skip to the next section. Otherwise, you may be directed to loop back to the beginning of the section, and read it again. Programmatically, this is the familiar:
while(1){...}
infinite loop structure. If you're particularly dense, you could be stuck here forever.
Alternatively (and better), you might be directed to a separate section, which explains the facts in more detail. It's a sort of hypertext. Ideally, the writers of the text were smart enough to tell when a given student needed extra instruction.
The inventors of programmed learning were careful to point out that those writing such textbooks should give the information in very small steps. You don't write a whole chapter on, say, the quadratic formula, and save all the questions for the end of the chapter. You teach a very few simple concepts—two or three at most—and ask the questions immediately.
The reason, they say, is that people need lots of positive feedback. If they get too many answers wrong, they get discouraged and give up. By giving them easy questions often, you give them lots of warm fuzzies, and make the study seem a lot more fun than drudgery. Everybody knows that people like to succeed more than to fail, and they like the feeling that they can actually complete the task. The notion of programmed learning capitalizes on these natural tendencies.
I think that's why I start with the null program. I like my warm fuzzies early and often.
When I'm writing code, I want to expect success. I'm mildly surprised if a bit of code doesn't compile without error, the first time. I'm shocked to my core if it compiles, but doesn't run properly, or doesn't give the right answer.
Some programmers, I submit, have never experienced that feeling. Certainly not those poor souls that had to write and integrate the entire system before even trying to compile it. How certain of success would they be?
The biological analogy
Think of a human egg cell. Once fertilized, it starts dividing, first into two cells, then four, then sixteen, and so on. The egg has become a zygote. Early on, every part looks like every other one. Later, they will differentiate, some cells becoming skin cells, some nerve cells, some heart and muscle cells. But in the early stages, they're completely undifferentiated. The organism may ultimately become a lizard, a fish, a bird, or a fern, but at this point, all the cells are alike.
I think of my null program as the software equivalent of a zygote. At the early stages, they can be any program at all. That's why I don't sweat the requirements too much. The null program and its cousins will satisfy any set of requirements, to some degree of fidelity. The requirements will flesh out—differentiate—along with the program that satisfies them.
As I continue the software development process, I stick to the philosophy of programmed learning. I never write more than a few lines of code, before testing again. I'm never more than mildly surprised if they don't work. I guess you could call this approach "programmed programming."
[Continued at How I write software (Part 2)]
文章评论(0条评论)
登录后参与讨论