A common AntiPattern in C/C++: all the macros, constants, type definitions, global variables, function prototypes, inline functions, and so on for a codebase are in one big header file, usually called something like "common.h", "global.h", or "defs.h".
Proponents of this approach like it because it seems simple. Everything is in one file. Other source files need only include this one header file. This one header file can be precompiled, speeding up builds.
OneBigHeaderFile may work for small projects; but it does not scale well to medium- or large-sized projects. There are several problems with this approach:
Note that this is not a recommendation against having common header files; it is a recommendation against "bigness" and lack of modularity. Some good things to put in a common header that is #included by all source files are
Devil's Advocate:
Yes, analyzing dependencies is trivial if everything depends upon everything, but that is not a useful analysis. If the order of header file inclusion matters, then you should fix those header files instead of using OneBigHeaderFile as a workaround.
[Any set of header files (excluding things like assert.h) which have the property that changing the order of includes changes the result of the build, are broken. If header file A needs header file B, it should include it. Use caution when redefining things in different header files; that is a particularly obnoxious CodeSmell. And all header files should have IncludeGuards.]
One big header file doesn't make everything depend on everything. It makes everything depend on one thing, flattening the dependency tree. "Fixing" the headers won't prevent order problems. Each .c file is free to include the headers in any order.
Thinking about where things really belong is programming. If you neglect organization, you'll end up with a BigBallOfMud.
I'd rather be solving the customer's problems than mine. Time spent thinking about header file dependency management could be better spent thinking about how to solve the customer's problems.
As stated earlier, change one interface, recompile all files. That means fewer code-compile-test cycles per day, which means fewer customer problems solved. In my experience, this is the main reason to do header dependency management.
If OneBigHeaderFile is a good practice, why don't I see things like a single "kernel.h" in the GnuLinux sources, or a "browser.h" in MozillaBrowser? Even a relatively small system, like the RubyLanguage intepreter, doesn't have just a single "interpreter.h" header file. If reading existing source code is any guide, multiple header files is standard practice. --StevenNewton
Any set of header files (excluding things like assert.h) which have the property that changing the order of includes changes the result of the build, are broken. If header file A needs header file B, it should include it. Use caution when redefining things in different header files; that is a particularly obnoxious CodeSmell. And all header files should have IncludeGuards.
Then any header file that uses #define is broken. The author of foo.h has no way of knowing if he is redefining a symbol used in bar.h.
Sure he does. Read the documentation for whatever software provides bar.h if it's a third-party component. Or, read the source itself. Header files in C/C++ contain far too much stuff that a) pollutes the global namespace and/or b) shouldn't really be there; see LargeScaleCppSoftwareDesign for a set of techniques to fix this. However, given that header files define an interface to a component; if you are using that component with sufficiently tight coupling that you need to #include the header file; you ought to have some clue what is in that.
bar.h hasn't been written yet. When it is written, it's written on the other side of the globe. Someday a hapless programmer will need to include it in a file that already includes foo.h.
Failing that; configure his compiler to emit warnings if a preprocessor symbol is redefined. Any decent compiler will do that for you.
So the programmer knows a symbol was redefined, but how does he know what depends on that symbol and what the impact will be? And how does he fix the problem?
[Fixing the problem isn't easy; I've had to deal with with situations like this before. The usual solution is to "wrap" offending header files with something that attempts to repair the global namespace; and only include the wrapped versions from his application.]
[Use of OneBigHeaderFile, which this page addresses, doesn't do anything to fix the problem. Indeed, if you are using someone else's library; unless you CopyAndPaste their header file into yours (which would be truly EvilAndRude), you will have to #include their header file]
[OneBigHeaderFile may work for small projects; but it does not scale to medium- or large-sized projects at all.'
However, with modern C/C++ environments (supporting the latest ANSI standards of either language), the need to use #define diminishes. static const T replaces #define for declaring manifest constants; inline replaces #define for many macros (those that act like functions).
See also RefactoringCppToReduceDependencies, CeePreprocessorStatements
CategoryAntiPattern CategoryCee CategoryCpp CategoryRefactoring