Tue 14 Nov 2006
I’ve recently taken over ownership of a new project at work, a suite of web-based tools for search engine optimization. In the course of patching Net:Google, the module we use for talking to Google’s SOAP API I found myself staring down a number of classes, some with AUTOLOAD-based accessor systems, some with accessors based on the tried-and-true Class::Accessor::CopyNPaste method. In neither case was I pleased. I needed a real accessor generator.
A CPAN search turned up Class::BuildMethods and Class::MethodMaker as promising options. My quick review follows:
-
Both share the same declarative style: “give me methods x, y and z, with these defaults, using these functions for validation”.
From Class::MethodMaker:
package Foo::Bar; use Class::MethodMaker [ scalar => [{default => 10}, 'x'], scalar => [{default => 6}, 'y'], ];From Class::BuildMethods:
package Foo::Bar; use Class::BuildMethods x => {default => 10}, y => {default => 6}; -
Where Class::BuildMethods can only generate accessors for scalars (requiring arrays and hashes to be passed in by reference), Class::MethodMaker can generate special accessors for array and hash properties:
use Class::MethodMaker [ array => [qw(x y)], hash => 'z', ];There’s a downside to this, though. When dealing with array accessors (and possibly hash accessors, too; I haven’t tested them), Class::MethodMaker gets too clever for my tastes: in scalar context, an array getter will return an arrayref, while in array context you get an array. The docs don’t make any mention this (that I was able to find), and I was seriously confused as to why
is_deeply($self->x, [], 'Correct default (x)')
kept complaining that an arrayref was not equal to “Correct default (x)”.
-
Class::MethodMaker offers considerably more flexibility and power than does Class::BuildMethods. While BuildMethods only allows you to validate the input to an accessor (i.e., accept or
die), MethodMaker permits more general input-processing capabilities.A feature I found more useful is MethodMaker’s ability to generate a proper
new()method for you. With this in hand, I was able to define an entire class like so:package Result; use Class::MethodMaker [ scalar => [qw/title URL snippet cachedSize/], scalar => [qw/hostName directoryCategory/], new => 'new' ]; -
Both modules (as of this writing) come with the same bug: neither allow you to give an accessor a default value of 0 or “”. While tracking down the cause of the bug in Class::BuildMethods was trivial, and a bug report has already been filed, fixing Class::MethodMaker, on the other hand,…
-
While Ovid’s code in Class::BuildMethods is very clear and easy to understand, Class::MethodMaker’s author seems to have picked the most complicated implementation possible. This is serious hate-the-end-user stuff. In what appears to be an effort to avoid writing any piece of code more than once, Class::MethodMaker’s internals are built-up by running several files through a custom-written preprocessor that makes absolutely no sense to anyone but its author and comes with zero documentation. It took me about 45 seconds to fix BuildMethods; after at least 30 minutes spent delving about in the core of Class::MethodMaker, I’m still no closer to coming up with a patch.
Conclusion: Class::MethodMaker comes across as overly powerful and complicated, with way more features, nooks and crannies than I’ll ever need, and an implementation that makes the Baby Jesus — not to mention my old CSCI 101 professor — cry. Class::BuildMethods is simple, elegant and gets the job done, though the lack of a new() emitter is a tick mark against it. Verdict: I’m going with Class::BuildMethods for now, plus submitting a patch for a new() method generator.
November 21st, 2006 at 00:12
[…] As I mentioned, I’ve recently taken over ownership of a project at work that makes heavy use of Google’s SOAP APIs, using Net::Google to handle the SOAP stuff. A major part of my initial marching orders were to fix one example of Net::Google’s curious definition of “error handling”. […]