In the comments to last month’s article, there were some agreeing with me and some not. In further discussions with most of them, they said that 5 to 20% of their code is product-specific. Size was a factor – the larger the code base, the more they split product-specific code into separate files.
Chris asked me if I’ve ever had code that outgrew the use of #define statements. For one new chip that had a variation of an existing block, I took the existing device driver and made changes as necessary without worrying about #ifdefs or compatibility with the previous chips. When I was done making changes, I used Unix’s diff command to see what percentage was different. I had told myself that if it was less than about 20% different, then I would use #ifdefs; if it was greater, I would make it its own device driver. (I picked 20% out of the air; it gave me something to use as a threshold level but it still would have depended on how the differences came out.) In this case, the code was less than 20% different, so I opted to use #ifdefs to maintain the differences in the existing device driver code. I used the -D option of diff to merge those two versions with #ifdef switches and then cleaned up the code by hand as necessary. Using diff -D was pretty slick.
In contrast, for a different block I supported, a new version was different enough that I had to create a separate device driver with its own set of files. I was able to use the same ioctl.h file so that the application would not know any difference. The makefile controlled which device driver file was compiled and linked in.
To clarify a few other points, the setting of #defines for features was not used for the whole codebase. It was used within each module and device driver. This allowed each one to #define and use its own feature switches. The build system specified the product to build, such as with “cc –D PRODUCT_VADER …”, and all modules and device drivers would use its own feature switches to direct the compiler to the appropriate code for that product.
If the amount of different code is significant enough that it merits separate source files as opposed to #ifdefs, it may be better to use feature-specific, rather than product-specific source files. Managing differences at the feature level allows multiple products to share the same feature code (reuse). And if a feature code needs changing, only one feature-specific file needs to be changed instead of multiple product-specific files. The build system could then be used to manage the mapping between products and features, and compile and link the appropriate feature modules for a given product.
Before I give this month’s best practice, let me emphasize again that each best practice should be used only if it applies in a given situation. Last month’s best practice recommended using #ifdefs to manage code differences. Since that advice does not always apply – as we have just seen – this month I give a complementary best practice that paraphrases Chris’s approach.
Until the next product-specific newsletter…