Tuesday, October 2, 2012

Compile Time

You might have heard that in Lisp, the whole language is there all the time.  You can read while compiling, eval while reading, and so on.  This isn't necessarily exclusive to Lisp—Perl offers BEGIN/CHECK/UNITCHECK—but it isn't exactly common in mainstream languages.

At first, it sounds brilliant.  "I can use my whole language to {read the configuration | filter some code on-the-fly | whatever} for super fast run-time performance!"  But there's a consequence that nobody seems to realize until they've gone far down that path: if you have a compile-test switch like perl -c, you can no longer guarantee that using it is safe if you wrote code that runs during compilation.

This is almost a trivial statement: compile testing has to compile the code; you're running code at compile time; ergo, your compile-time code will run.  But beware of the details:
  1. If you read your configuration files and exit if something's wrong, then you must now have a valid configuration to run a compile test.
  2. Generalizing the previous: if you pull anything from an external service, your compile test depends on that service being up.  It may also depend on having your credentials for that service available.
  3. If you do a ton of work to prepare a cache for runtime, you have to wait for that—then the compile test finishes and throws it all away.
  4. If you have an infinite loop in compile-time code, the compilation test never completes.  Not a problem for a human at the keyboard, but could be difficult in a script (e.g. VCS commit hooks).
  5. If the language allows you to define reader macros or source filters at compile time, then you can't even syntax-check the source without running the compile-time code; the lex phase now depends on the execution state that accumulates during compilation.
  6. If your code assumes the underlying platform is Unix because that's what the server is, you can't compile test on Windows.  Or, you have to write your whole compile phase cross-platform.
If you want to execute expensive code or do sanity checks before run time, consider carefully where they would best be placed.  Perl's INIT can give you the same "run once, before runtime" behavior without affecting a compile test.   Separate, automated tests can be configured to interrupt neither your compile checks, nor the production system on failure.  (Sometimes, a 90%-working production system is desirable, compared to 0%.)

No comments: