Superfluous Curly Braces as Semantic Guides?
Working today I noticed that I tend to use use curly-braces to rank different levels of semantic operations. Never before having consciously thought about the rules applied, I thought it fit to discuss it with you, gentle readers, and see whether I'm not alone.I'm currently writing/modifying several C-family source-code analysis tools, to support my current commercial activities. (I'm acting as an expert witness on a software intellectual property dispute case. It's very interesting stuff, and has peculiar logical and physical limitations that bring to light many programming issues one tends to forget in the practically unconstrained resource environment of much desktop software development, so you can expect a steady stream of posts covering a wide range of topics in the coming months.)
Anyway, in making some modifications yesterday I noticed that I'd chosen one bracing style over another, and I wondered why I'd done so?
Here's one possible layout of the code:
{ for(string_set_t::const_iterator i = exemptions.begin(); i != exemptions.end(); ++i)
{
string_t s = *i;
stlsoft::trim_all(s);
if(s.empty())
{
continue; // Skip empty
}
else if('#' == s[0])
{
continue; // Skip comment
}
else if(NULL != ::strpbrk(s.c_str(), "?*"))
{
Pattern_ptr_t pattern(new shwild::Pattern(s.c_str(), shwildPatternFlags));
exemptionsPatterns.push_back(pattern);
}
else
{
exemptionNames.insert(s);
}
}}
Yet I didn't do it that way. Without pause I wrote the following instead:
{ for(string_set_t::const_iterator i = exemptions.begin(); i != exemptions.end(); ++i)
{
string_t s = *i;
stlsoft::trim_all(s);
if(s.empty())
{
continue; // Skip empty
}
else if('#' == s[0])
{
continue; // Skip comment
}
else
{
if(NULL != ::strpbrk(s.c_str(), "?*"))
{
Pattern_ptr_t pattern(new shwild::Pattern(s.c_str(), shwildPatternFlags));
exemptionsPatterns.push_back(pattern);
}
else
{
exemptionNames.insert(s);
}
}
}}
The issue that intrigues me is why I've gone to the trouble of creating an extra level of block indentation (and consumed an extra line of source into the bargain)?
As far as I can determine there are two reasons: one worthy, one prosaic.
Dealing with the less glamorous first, I should observe that this is a heavily file-system and input dependent program, so I've eschewed unit-testing for the application (though not for the constituent libraries!) in favour of the debugger. By selecting the second layout I can apply a single breakpoint (on the if + strpbrk statement), rather than two on the latter two blocks.
This, in turn, is a hint to the more worthy reason. The first two conditional blocks are both dealing with "lines outside our interest". The program in question performs analysis of #include statements in C/C++ source, and the code sample shows some of the logic to the filtering out from the results set those includes designated by the user as being not-of-interest. As is clearly shown, it filters by name (the exemptionNames set) or by pattern (the exemptionsPatterns sequence of shwild pattern instances). So, by using the extra block, I've highlighted/separated the "important" logic from the mundane.
Granted, nothing about this is rocket surgery. But I've been writing code for more than two decades and have never noticed before that I do this. (As you'd expect, I've now looked back at lots of other code, and I've been doing it for a long time.)
I'd be interested to hear from other readers what they think of this, and what other ways in which they do (semantically unnecessary) things that aid in code transparency. I'm especially interested to hear about such things in other languages, even those outside the C-family.
Before I go, I'd like to comment on a couple of other issues that might have the ardent C++ programmers amongst you scratching your heads. First, the use of continue. I've met plenty of chap(ette)s who've suggested that continue is a design smell. In this case, due to the use of else the two continues are actually superfluous. The reason I use them is (i) a continue; statement is clearer than an empty block, a ;, or combining the two if conditional expressions, and (ii) I believe it's more resistant to accidental damage during maintenance: one would only have to leave off an else and things would go rather badly.
Second, my use of the enclosing braces around the for statement. As explained in Imperfect C++, I use this technique to avoid any clash between old and new for-statement initialiser semantics. Putting it in a block unambiguously defines its scope, and makes it compatible with any (version of any) C++ compiler.
Finally, my use of strpbrk(). This one's arguably less defensible. The fact is, I just don't like the std::basic_string class interface, and think it has way too many methods. No doubt I could have used find_first_of(), but then I'd have to be testing against std::string::npos, which doesn't make the code clearer to me. (Or, it could be that I'm really an old C programmer at heart, and am just an inconsistent hypocrit!)

