On Tue, 2005-07-12 at 20:34 +0100, Richard Hughes wrote:
One of the first things I do when debugging a problem with layout is to turn on this bit of code, so it's still very useful (and will become more so when I start adding features to flowtext again after the release). It is still debugging code, however, and will not be callable in the final build. How do you guys deal with such things? I have read of a disapproval of #iffing out code, and putting it in a separate source file might cause me to accidentally check in a change to the makefile. Also, I do sort of have a vision of using it in some sort of automated regression test. How does that affect your answer?
My personal two cents:
If it's really that useful for debugging, I would seriously consider making it part of the normal build and adding a runtime switch for it (perhaps via an environment variable, checked once).
Remember, there's no well-defined line between our users and developers, and there probably shouldn't be.
But, the big reason we don't like #ifdefs (or dead code in general) is that they add a tremendous burden both to refactoring and to code comprehension, and that such code inevitably gets broken when other people make changes.
I'm something of a worst-case in that regard; I semi-regularly turn over huge swaths of the codebase to advance long-term design goals. I physically _can't_ review all the code affected in the process.
So, I have to rely on a proof that my changes don't alter the semantics of affected code, double-checked by a fresh compile and a set of functional tests. If your code was part of the affected, but was #ifdefed out, chances are it got missed by my analysis and probably got broken. And either way I won't know because as far as I can tell everything still compiles and works (it's not like I can try every n^2 combination of #defines).
Compiling isn't a proof of correctness, of course, but the compiler can check _some_ things. #ifdefs limit its ability to do even that comprehensively.
[ I do try to take special pains for OS-specific code, but often that means replacing it with something cross-platform as a separate commit. ]
The other big refactorers (notably Peter, who's something of an unsung hero) are not quite as insane. But #ifdefs still make their work a lot harder than it needs to be, and some #ifdef rot is inevitable.
So, I'd say either make it part of the normal build, selectable at runtime, or at least write some trivial unit tests that use it so it gets compile-time exercise that way.
No matter what you do, code behind an interface boundary in a separate file (even if it's just one giant #ifdef) is generally better than having #ifdef blocks sprinkled in among other code, and is more rot-resistant.
Admittedly, sometimes granular #ifdefs are unavoidable. But try to minimize them as much as you can; generally the best way to do that is to do as much as you can in code that is part of the normal build (or unit tests, as a second-best).
Another strategy for some things is to use regular if () with a compile-time constant, instead of #ifdef. If false, the compiler will ensure that the omitted code still compiles, but will (in principle) not include it in its output. It also won't be invisible to analysis tools.
-mental