I’m sitting in Portland 252 for my first tutorial of the day, Perl Worst Practices with Damian Conway. He’s started off by complimenting us on our intelligence and our ability to convince our bosses or significant others that paying for a worst practices course was a good idea.
Most of us are, of course, aware of the concept of best practice when coding. Writing code that’s maintainable, predictable, and follows the rules. Oh, and uses Java.
Worst practice is, by contrast, code that is obfuscated, unmaintainable, and breaks all of the rules. Today, we will be studying code that Damian has submitted to the Obfuscated Perl contest. This promises to be very, very scary.
Damian’s entry to this contest was SelfGOL, a program capable of self-replication, rewriting other Perl programs to themselves self-replicate, detecting un-rewritable programs, playing Conway’s “Game of Life,” and, as if that wasn’t enough, animating any text as a cycling marquee banner. The main constraint of the contest is that the entry must be under 1,000 bytes of code, so it shouldn’t be too difficult to understand. Obviously it doesn’t use any modules, because that would be too easy. Not only that, but it doesn’t use a single control structure. This is going to be great.
Following an amusing demonstration of SelfGOL, we moved into treating it as a case study for a set of principles. Principles that will focus on the very practices SelfGOL embodies, and why they should never, ever be used. As I intend to enjoy the discussion, I won’t spend much time writing about the discussion and examples accompanying these principles, but rather simply note the principles for my own benefit (documentation for the win). After all, sharing all my new tips and tricks would suck all the fun out of it.
Principle 1: Sane and consistent layout makes code more maintainable (but it isn’t a magic bullet if the code itself is beyond help).
Principle 2: Using built-in features isn’t necessarily smarter or cleaner (even though fellow developers’ futile struggles to recall those features can be highly amusing).
Principle 3: Obscure obsolete features are obscure and obsolete for a reason (and restasking them for even more obscure purposes is not helping).
Principle 4: Each statement should do one thing only (since that’s the upper limit most brains can comprehend).
Principle 5: Relying on default behavior makes code very slightly easier to write and vastly harder to read (because most readers can see better than they can think).
Principle 6: Randomly placed subroutine definitionss are static (in the radio interference sense).
Principle 7: Choose data structures that simplify your task (even if the task is to make those data structures incomprehensible).
Principle 8: Just because you use some operation frequently doesn’t mean it should be in a utility function (especially if it’s in a function merely to abbreviate its name).
Principle 9: Encapsulating the familiar can decrease maintainability (refactoring isn’t a substitute for sanity).
Principle 10: Treat any clever one-line solution as an alarm bell (or as an antipersonnel mine with a six-month delay fuse).
Principle 11: Familiarity breeds comprehension (it breeds contempt (but hey, what’ doesn’t?)).
Principle 12: Table-driven solutions are clean, efficient, and extensible (as long as you don’t mind losing a little comprehensibility).
Principle 13: Building a messy data structure and then cleaning it up is often easier than building it cleanly in the first place (and to hell with the purists).
Principle 14: Some code is better compiled at run-time (but the urge to use eval is Nature’s way of letting you know there’s not yet enough pain or misey in your life).
Principle 15: Parentheses are our friends (cos, if you can remember all 24 levels of Perl’s precedence, you gotta get a life, dude!).
Principle 16: Edge cases suck (and edge cases of familiar constructs suck worst of all).
Principle 17: Code should do what it seems to be doing (especially when it seems to be doing something subtle).
Principle 18: Conceptual elegance is no guarantee of actual maintainability (nor a good substitute for it).
Principle 19: If you’re going to have default values, define them near the place they may actually be used (or, at least, somewhere they have a slim chance of being discovered).
Principle 20: No matter how good you think your error messages are, they’re still too brief, too obscure, and too hard to decipher (even if you’ve already taken Principle 20 into account).
Principle 21: Avoid using obsolete and arcane magic punctuation variables with unfamiliar default values and unexpected global effects (even if you happen to enjoy a little self-inflicted pain in other recreational activities).
Principle 22: The fundamental complexity of any problem is irreducible (optimizations merely redistribute the pain differently).
Principle 23: Code that breaks when it’s reformatted is already broken (though on a much more profound and interesting level).
Principle 24: If it’s impossible to understand, it’ll be impossible to maintain (on the bright side, of course, such code is highly stable).
This last one should, but often doesn’t, go without saying.
Principle 25: Phenomimetic retrodeterministic nominativism generally does not improve code comprehension (then again, did it sound like it would?).
Principle 26: Don’t allow dynamic behavior to violate static expectations (and the easiest way to do that is reusing over-scoped variables for unrelated purposes).
Principle 27: Explicit behaviors are better than implicit behaviors (especially when the specification of the implicit behavior is syntactically baroque and hard-to-spot, and the behavior itself is unknown to the majority of developers).
At this late point of the tutorial, Brad pointed out to me that all of these principles are in the included materials. Now that I’ve already transcribed so much from the slides, I don’t have the heart to delete it all. Of course, since I haven’t been commenting on all of the black magic to this point, there would then be very little in the end to post. Brad also has a much better post about this tutorial, since he actually took real notes.
Principle 28: Code that pre-caches or precomputes its data is much easier to maintain than code that caches or computes on-the-fly (when you’re running at multiple gigahertz, acquiring your data a few thousand operations early is still plenty JIT enough).
Principle 29: Coding is an art, but code shouldn’t be art (evolution made programmers boring, pedestrian, and aesthetically challenged for good reasons).
It’s mesmerizing to listen to the thought process behind Damian’s obfuscated code. I can’t help but wonder if this well-organized, well-thought-out explanation is anything close to how Damian designed this program. Or, rather, if there are extremely convoluted, scary, and most importantly, evil gears grinding away inside his head. In fact, I suspect this entire tutorial may have been designed purely as a way of documenting SelfGOL so Damian himself can remember how it works. Clever.
This kind of programming is silly and fun, but it serves a real purpose. Pushing the limits of a language teaches about its dark places. The understanding that comes from it vastly improves the skills of the programmer, even if—especially if—the bad things are never, ever used. Perl, even more than other languages, encourages this kind of play, thanks to its rich diversity and culture.
Important safety tip: keep these tricks and contrivances for recreational purposes only.
I don’t know what’s more disturbing, how much of the tutorial I understood, or how much I already knew coming in.
[tags]oscon, oscon08, Perl, Damian Conway[/tags]