Letters to the editor may be sent via email to cujed@cmp.com, or via the postal service to Letters to the Editor, C/C++ Users Journal, 1601 W. 23rd St., Ste 200, Lawrence, KS 66046-2700.
Dear Marc,
Your June 2000 editorial mentions the XP (Extreme Programming) development methodology. In it, you say you're concerned that rapid iteration and minimal up-front design will result in code that lacks overall structure.
XP, as explained in Kent Beck's book Extreme Programming Explained, has an answer to that concern: refactoring. Spaghetti code is caused by adding code to a program whose design didn't anticipate the change you made. The traditional solution to this is to either design for extreme flexibility up front, or to throw away the design every few years and start over again. The former demands omniscience, and the latter is wasteful.
The alternative is conscientious refactoring. In XP, programmers are always on the lookout for broken designs. This skill comes easily to all programmers: you just have to sensitize yourself to notice when you're fighting the design instead of writing useful code. When that happens, the XP philosophy says "stop right there and fix the design."
XP does demand good tools and good practices. You can't safely change the design of the program out from under the team without regression tests and version control. XP also won't tolerate lazy or incompetent programmers.
The walls of a honeycomb are thin and brittle, but as a whole, a honeycomb is one of the strongest structures in nature. XP is like that. It's easy to pick on the individual facets of the philosophy, yet the whole philosophy is tuned so that each weakness is supported by a strength.
My advice to your readers is to get Kent Beck's book. It's one of the slimmest programming books you'll ever buy, and it uses a large typeface and lots of white space on the pages. Each chapter is very short, so you can read through it a chapter at a time during recompiles.
Warren Young
You make some good points. I'm still skeptical, though. One question I have is: when you "stop right there and fix the design," are you fixing the design in terms of making it more flexible and robust overall, or are you just fixing it to better match the specific requirements of the project? How flexible will the design seem to the maintenance programmers, years after the XP team has moved on to something else? I think it is just too early to say, which is why I suggested we revisit the designs in a couple years to see how they fare. Thanks very much for the feedback. mb
Dear CUJ,
In reference to Stan Kelly-Bootle's June 2000 Post-Mortem Debunker column ("From MAW to MAM, More?"), I'd be very wary of getting involved with a vendor who says: "As in 1999 we'll be with you 7x24x365 hours throughout 2000." Surely 7x24x52 or 24x365 hours support in one year is enough for any customer...
Andrew Larcombe
Hi,
I very much liked the article by David Berry about the fast regular-expression search algorithm. However, I found and corrected two bugs. I also made some small changes to the interface to make it more convenient to use.
1. There is a bug when using '?' in the regular expression. '?' means "Match zero or one characters," but if there are zero characters to be matched by '?', the pattern is found but the match length reported by the algorithm is one too much. So, if the string to be searched is azerty, and the regular expression is e?r, the result is ert'. This can be corrected by adjusting the value of lead if necessary in the function Match:
if (lead==-1) { MatchStart = text; matchLen += i+creLength; } else { if (lead>i) lead = i; // adjust lead MatchStart = text+i-lead; matchLen += creLength+lead; }2. It's not possible to have ']' itself as one of the characters in [...] by escaping. It's best to be able to specify [a\]], meaning the character can be 'a' or ']'. This can be corrected in the function Compile:
int nrChars = 0; for (i=0; trePtr[i]!=_T('\0'); i++) { nrChars++; // allow escaping of ']' and '\' if ( (trePtr[i] == _T('\\')) && (trePtr[i+1] != _T('\0')) ) i++; else if (trePtr[i] == _T(']')) break; } ... (*crePtr).multiChar = new wchar_t[nrChars+1]; i=0; while (*trePtr!=_T(']')) { // allow escaping of ']' and '\' if (*trePtr==_T('\\')) trePtr++; (*crePtr).multiChar[i++] = (wchar_t)(*trePtr++); }3. One constructor and the function SetRegEx take LPTSTR as its first argument, but it can be easily made LPCTSTR. Likewise, Match also takes LPTSTR as its first argument, but could just as well take LPCTSTR. However, then it must return LPCTSTR, and in some cases one might want to change what the returned pointer points to, e.g. for a replacement. Therefore I made two versions, one with LPTSTR, one with LPCTSTR. The former calls the latter to avoid code duplication. Using LPCTSTR has the advantage that one can pass an MFC CString as an argument, or use the c_str function on an STL string.
LPCTSTR CRegEx::Match(LPCTSTR text, int textLen, int &matchLen) { int i,j; LPCTSTR MatchStart=NULL; ... } LPTSTR CRegEx::Match(LPTSTR text, int textLen, int &matchLen) { // call LPCTSTR overloaded function LPCTSTR rv = Match((LPCTSTR)text, textLen, matchLen); if (rv==NULL) return NULL; return text+(rv-(LPCTSTR)text); }I atached the two corrected files to this email.
Kind regards,
Mark Van Peteghem
David did submit a new version of his code to fix some bugs, but I don't know if they were the same ones you caught. We have forwarded your fixes to David and are awaiting his comments. In the meantime, we have uploaded your code to our source-code archives (www.cuj.com/code, look for letters.zip in the August 2000 archive) for readers who want to try it out. Thanks for writing. mb
Dear CUJ,
Thank you for publishing the articles on imaging, it is an area of computing that has always interested me. In the May 2000 issue, Dwayne Philips showed a rendering algorithm. Having written a ray-tracing program (not very dissimilar to the given rendering algorithm) myself a few years ago, I'd like to share an alternative implementation that does not require trigonometric functions to calculate the vector R from the vectors N and L (see Figure 1a). (First of all, I have to point out that Equation 6 was misprinted, the right-hand side of the equation should read a1a2 + b1b2 + c1c2).
The dot product of N and L is, as in Equation 4, n * l * cos(theta). Imagine a right triangle, with -L as the hypotenuse (i.e. the vector L in the opposite direction, as if it were coming away from the surface in Figure 1 instead of going towards it), and one side parallel to (indeed, along) N. Thus, the third side is parallel to the surface, going through the end of -L (see Figure 1b). The length of the side along N is l * cos(theta), from basic trigonometry. Thus, if you divide the vector N by n2, then multiply it by the dot product of N and -L, the result (call it N1) will be parallel to N and of length l * cos(theta), i.e. the side of the right triangle we've been talking about.
This becomes useful when you consider the right triangle with R as hypotenuse and one side along N. The opposite side is parallel to the surface, as before. This triangle is a mirror image of our first right triangle, reflected through N. The result is, the opposite side of the R-triangle is parallel to (in fact collinear with) and the same length as the opposite side of the L-triangle, and they touch at one point, at the end of N1. Thus the vector from -L to N1 (the vector N1 + L) is the same as the vector from N1 to R (R - N1), i.e. N1 + L = R - N1, i.e. R = L + (2 * N1).
Personally, I prefer this method, because you can find R by using the dot product (which can be implemented in scalar multiplication and addition using Equation 6), finding n2 (which can be implemented in scalar multiplication and addition, you don't even need to use square root!), and vector multiplication and addition (which can be implemented in scalar multiplication and addition). After you have the vector R, the dot product of V and R can be calculated with Equation 6 instead of Equation 4. Although trigonometric reasoning is used, no trigonometry calculations are necessary and I prefer to avoid trig functions where possible.
(I have since lost the ray-tracing program I wrote, and the details above are written from memory. I don't know if I got it all right above, but this is the kind of reasoning I used in my implementation, and it seemed to work perfectly.)
Hugh O'Byrne.
Dwayne Phillips replies:
First, thanks for catching the error in Equation 6 (oops). The source code corresponding to this equation is correct.
As for calculating the vector R, you have an excellent idea. I wish I had thought of it or seen it somewhere. Figure 1a shows the original situation. Part b shows what Hugh is describing. The vector N1 is the thin line running right up the middle of the thick line N. Part c shows another way to see that L + N1 = R - N1, so that R = L + 2N1. This allows a much easier and more accurate way to calculate R.
Many thanks,
Dwayne Phillips