Thứ Bảy, 12 tháng 6, 2004

In praise of continue





Advanced C++ Programming Styles and IdiomsI've read some fairly daunting software engineering manuals that discourage -- or even ban altogether -- the use of a 'continue' statement. This, in my experienced but fallible opinion, is a complete mistake. I will illustrate to you, using actual code (no, seriously?), why I believe this is so.



I've abbreviated this code to make it easier to follow. It resides in the heart of the BadBlue scheduler, which mimics Outlook's recurring appointments feature. One of the neat things about the scheduler is that it allows recurring events, no matter how many times it occurs, to be stored in a single record. So if you have an 8:00am daily appointment with your therapist, Monday through Friday, the cost is a single database record.



This code is abridged from the method that finds tasks in a specific "window" of time (a begin-date and end-date). Virtual, recurring events are extrapolated from the physical records that serve as their instantiator. This code finds events (real and virtual) in an ordered list and, if they fall into the window, saves the event ID for the caller's use.





// Set up a search loop until we find something in...

// the find-window (or we run out of items).

//

while (m_posFind != NULL) {



// Get next item in ordered list...

//

if ((pstrKey = m_tplResultSet.GetNext(m_posFind)) == NULL) {

continue; // 1

}

strKey = *pstrKey;



// Does a delete record override this item? If so, skip it.

//

if (m_mapDeleteSet.Lookup(strKey, strJunk)) {

continue; // 2

}



// Find the real key: key contains date/time + taskID.

// ...content contains caption, duration, recursion-flag.

//

if (!m_mapResultSet.Lookup(strKey, strContent)) {

continue; // 3

}



// Decode key...

//

yr = mon = day = hr = min = sec = 0;

sscanf(strKey.GetBuffer(0), "%d-%d-%d %d:%d:%d %ld",

&yr, &mon, &day, &hr, &min, &sec, &dwTaskID);

CDateTime odtItemStart(yr, mon, day, hr, min, sec);



// Decode content...

//

bufferCaption[0] = '\0';

yr = mon = day = hr = min = sec = 0;

bufferIsRecurrence[0] = '\0';

sscanf(strContent.GetBuffer(0),

"%[^~]~%d-%d-%d %d:%d:%d~%[^~]~"

"%[^~]~%d-%d-%d %d:%d:%d~%ld~",

bufferCaption,

&yr, &mon, &day, &hr, &min, &sec,

bufferIsRecurrence,

bufferExtraInfo,

&yrx, &monx, &dayx, &hrx, &minx, &secx,

&dwExtraInfo

);

CDateTime odtItemEnd(yr, mon, day, hr, min, sec);



// Is item in find window? If not, continue...

//

if (InWindowNonRecurring(

(CDateTime) odtItemStart, (CDateTime) odtItemEnd,

m_dFindWindowStart, m_dFindWindowEnd, 0)) {

continue; // 4

}



// Store appropriate args for caller's edification...

//

dStartDate = (CDateTime) odtItemStart;

dEndDate = (CDateTime) odtItemEnd;

strSubject = bufferCaption;

bIsRecurrence = (!stricmp(SRECURS, bufferIsRecurrence));



// Found... quit.

//

bRet = TRUE;

break;



//

// ...end of loop through major window items...

//

}





Note the four continue statements, marked above.



1) Is the loop finished? If so, let the loop terminate gracefully (m_posFind == NULL)

2) Does a "delete" record override this item (e.g., has the appointment been cancelled or otherwise overriden)? If so, skip to the next item

3) Can we find more information about this event in the result set? If not, then it's been removed from the pool of valid events and we can skip to the next item

4) Lastly, is this item even in the window of interest (i.e., does it occur after the begin-date and before the end-date)?



Now imagine if this code was written without benefit of continue statements. In other words, we would use nested "if ... else ..." clauses... and nested rather deeply based upon my initial take.



Now note the relative clarity of the above logic compared to the hypothetical three or four levels of nested if constructs. To me, there's no comparison between the two approaches. I would encourage any developer to use continue constructs wherever clarity can be used to fight nested if statements.

Không có nhận xét nào:

Đăng nhận xét