In #imports gone wild! we looked at the problems caused by having too many #import directives. But it’s also possible that you have too few, resulting in bad header files — especially if you don’t pay attention to #import order in your .m files.
Minimal and complete
When it comes to imports, header files should satisfy these two conditions:
- They should be minimal
- They should be complete
“Minimal” just means a header file should import no more than it needs.
“Complete” means the header file imports everything that needed to compile it. Consider
If removing foo.h (or changing the order) causes bar.h not to compile, then bar.h is not complete.
Poor #import order masks dependencies
One way to have an incomplete header is by relying on precompiled headers. In particular, just because the precompiled headers include a particular header doesn’t mean you can omit it elsewhere.
Another way to have an incomplete header is with a poor #import order that masks dependencies. In C-based languages, programmers often begin their implementation files by including the most general headers from the largest scope. They work down from there, until they reach the most specific headers:
- system headers
- other headers
- finally, the unit’s own header
This is backwards. Consider a header foo.h that depends on <QuartzCore/QuartzCore.h>. If foo.m imports QuartzCore first, then other stuff, then finally gets to its own header, you may not feel the need to import QuartzCore in foo.h.
…And this will break for the next programmer who comes along and just imports foo.h.
Good #import order
The solution is simple: reverse the order! Start from the most specific, then work towards the most general. Most importantly, include your own header first. Large-Scale C++ Software Design by John Lakos is the only book I know about “physical design” — how to arrange source code into files. The author states,
Latent usage errors can be avoided by ensuring that the .h file of a component parses by itself – without externally-provided declarations or definitions… Including the .h file as the very first line of the .c file ensures that no critical piece of information intrinsic to the physical interface of the component is missing from the .h file (or, if there is, that you will find out about it as soon as you try to compile the .c file).
In other words, by including your own header first… if the header is not complete, you will fail fast!
Here’s what I do. If I’m writing foo.m, I first import foo.h. I keep this separated from other imports by a blank line. All the others follow, in sorted order:
Sorting helps me find duplicates. It also puts angle brackets imports <> after quoted imports, so that the most general headers come last.