/OV 3081 /TM 03060116Z /SK FEW /TB LGT /TP CODER
Looks like the weather's nice today. That is, the project, by and large, is progressing smoothly. Now that we're in the aftermath of Iteration 1, here are a couple remarks.
In short, I think McConnell would be proud. On day 0, my partner and I didn't promptly load Emacs and start implementing classes. In fact, we barely touched the code at our first meeting. Instead, we armed ourselves with paper and pencil and started diagramming the project architecture. This was probably the best single decision we made, and we got a nice return-on-investment. A good first examination, for example, allowed us to reorganize some of the provided code without ripple effects (e.g., reimplementing consumeWhiteSpaceAndComments as a static member method of Scanner). Additionally, the only code we wrote at first was either regular expressions or test cases. In fact, I estimate the first 40% or 50% of the project iteration was spent generating these parts. This did increase my confidence in the code we would implement later, as well as provide regular sanity checks on half-implemented functionality.
These regular sanity checks do, however, form my first negative observation: There were way too few of them, and they weren't distributed well enough. I mentioned that the test cases and the regular expressions were developed concurrently. At the time, this seemed like a good way to separate work, and it turned out well enough. But isn't it the case that we implemented some 70 regular expressions before testing them? McConnell doesn't look too pleased now.
Our main development issue was generating proper C++. I have plenty of examples of hiccups like this, but the following is the best one: Probably most groups implemented Scanner::scan as we did, with a for-loop iterating over an array of TokenMaker. In the beginning, we thought it was a tasty idea to store this array of TokenMaker as a constant public static member of the Scanner class. After all, they're just Token factories (this actually breaks one of McConnell's architectural rules; we considered integrating TokenMaker into Token with static methods, but ultimately decided against it) and they hold minimal internal state, all of which is static. We also wouldn't have to remake them. During the run of the program, they'd be tucked away in Scanner and not cluttering the global symbol table. But we'd forgotten a number of important things about constant static member initialization in C++, and this caused a lot of time-consuming horror and terror. A long story short, we ended up with a static global array of TokenMaker. Is that all that bad? No, but knowing the quirks of object initialization in C++ would have helped us a little. In terms of what delayed our progress the most, this sort of thing won.
As for the teamwork aspect of Iteration 1, it worked. It was a priority in our first meeting to clearly designate responsibilities, which we were largely able to fulfill invidually and by remote, thanks to SVN. I think our arrangement was successful enough to be carried over to round 2.
The greatest annoyance about Iteration 1 was not, however, any of these things. It was revising STYLE! I am one of one of those monstrous malefactors who, if left unsupervised, will write code like:
I understand the superiority of emulated pure block, but muscle memory and laziness say otherwise. It's an uphill battle. I might not win.
The things we did right we'll be carrying over to Iteration 2 and beyond. We did the test cases right. We delegated work right. We used the right color style in gedit (Cobalt blue). Our final code was well organized, well commented, and stylistically consistent, for the most part. It's also robust in the face of change, as we are finding out in the first stages of the next iteration. Our fledgling plan-first-code-later approach will also be given another chance, since it paid for itself this time around. We've also probably refreshed our C++ kung fu enough to avoid a second rash of stupid language mistakes, and the minor hitch with test cases mentioned above will be addressed in the next round.
Before I tear myself away from the keyboard, one last trick we'll be reusing: Enlisting preprocessor constants in adjusting the error verbosity of the program. In our Makefile, we have the line
FLAGS = -Wall -g #-DDEBUG
where we can just remove the # to engage code like this:
// useful diagnostic output to stderr
#endif /* DEBUG */
We had a lot of cases, for example, where the sameTerminals utility function would simply return false, with no indicator of where it failed. Some DEBUG-only output within sameTerminals saved us from a lot of hair pulling.
That's it for this PIREP. Advise on initial contact you have "Andy".