How not to recommend a Perl module

The first comments in response to my Anatomy of ylastic-costagent article were recommendations to consider other modules than the ones I used. Frankly, these module recommendations sucked, and I'm annoyed enough to explain why.

If you recommend a module, make sure it solves my problem

The first recommendation was to consider Date::Simple instead of Time::Piece + Time::Piece::Month, but Date::Simple doesn't appear to solve my problem.

I was looking for a way to find the first day of the next month (without manually incrementing the month and potentially the year). Here's how I did it:

# returns a Time::Piece object
Time::Piece::Month->new( Time::Piece->new() )->next_month->start()

Note that Time::Piece::Month has a next_month() method. Does Date::Simple offer a way to do that? Not that I can see in the documentation, so the recommendation is useless to me.

If you recommend a module, make sure I can see how to use it

The second recommendation was to use Log::Any::App instead of Log::Dispatchouli. Leaving aside that I said the reason I was using Log::Dispatchouli was because I was explicitly trying to learn it, the recommendation suggested that I could have "1 instead of 6" lines of code.

Here's my "six" (actually about 8) lines:

local $ENV{DISPATCHOULI_PATH} = $opts->get_logpath
  if $opts->get_logpath;

my $logger = Log::Dispatchouli->new({
  ident     => basename($0),
  facility  => $opts->get_syslog ? $opts->get_syslog : undef,
  to_file   => $opts->get_logpath ? 1 : undef,
  log_pid   => 0,
  debug     => $opts->get_debug,
});

Note that I'm setting a name, optionally logging to syslogd, optionally logging to a file of a user-defined path (and potentially doing both), and toggling a debug mode.

Looking at Log::Any::App's usage synopsis, here's what I see:

use Log::Any::App qw($log);

Yes, that's one line. But does it provide the same options? Obviously not in just that line. If I read to the end of the documentation, I find there is an init() function and I can do something like this (N.B. untested):

require Log::Any::App;
Log::Any::App::init([
  '$log',
  ($opts=>get_logpath ? (-file => $opts->get_logpath) : () ),
  ($opts=>get_syslog ? (-syslog => { facility => $opts->get_syslog) }) : () ),
  ($opts=>get_debug ? (-level => 'debug') : () ),
]);

I don't know if that really does exactly the same thing -- it looks like it would get close, but it's certainly not "1 line versus 6".

In evaluating the two modules, I compare whether documentation makes it easy to figure out how to use each module. Here are the two synopses together for comparison:

# Log::Any::App
use Log::Any::App qw($log);

# Log::Dispatchouli
my $logger = Log::Dispatchouli->new({
    ident     => 'stuff-purger',
    facility  => 'daemon',
    to_stdout => $opt->{print},
    debug     => $opt->{verbose}
})

One of those shows me how to configure it to suit my needs. The other doesn't. Log::Dispatchouli also documents its new() method and all arguments within half a page after the synopsis. Log::Any::App buries its init() documentation towards the end of the documentation (and also doesn't mention that it will take a variable name to export to, just like on a "use" line).

If the recommendation had said, "if you want a logger that gives you decent defaults without configuration", I might have found the recommendation more useful, but that is not what it said. Unless it's obvious from the docs, a recommendation should tell me how to do the same thing easier/faster/better than what I've already done. If it's not obvious, write an example yourself, or don't bother with the recommendation.

Log::Any::App actually seems decent, but if I wasn't annoyed enough to write this article, I probably wouldn't have bothered to read past its terrible synopsis.

This entry was posted in perl programming and tagged , . Bookmark the permalink. Both comments and trackbacks are currently closed.

9 Comments

  1. Posted April 5, 2011 at 6:38 pm | Permalink

    Hi

    I'm sorry my recommendation for Date::Simple missed the mark. I didn't really intend it should replace your existing (clever) 1 line of code. I intended to bring it to your attention in case you'd overlooked it. This can happen easily for various reasons. See e.g. the odd-named module Marpa, which any casual CPAN search will fail to find. OK, so Date::Simple is well-named, but occasionally we all overlook things.

    Cheers
    Ron

    • Posted April 5, 2011 at 8:43 pm | Permalink

      I understand, but in this case, it just didn't address my problem. The number of date modules is overwhelming, so if you felt like doing an article comparing some of them for different tasks and throwing it up on a blog somewhere, I think that would be a valuable contribution to the community.

      -- David

  2. Posted April 7, 2011 at 5:53 am | Permalink

    Sorry for the sucky recommendation :) I've expanded the SYNOPSIS somewhat, please take a look if you've got some time: http://search.cpan.org/~sharyanto/Log-Any-App-0.29/lib/Log/Any/App.pm

    As for the '1 line' thing, Log::Any::App's conciseness comes from letting it guess the defaults instead of explicitly specifying outputs and levels. Which I admit is probably not what you wanted in this case.

    • Posted April 7, 2011 at 9:18 am | Permalink

      I think the Synopsis is much better, now. Thank you.

      The other thing that would help the documentation is a concise table of where logs go under different conditions, with -e, without -e, running as root, not as root, looks like a daemon vs not. It's described in the text, but you have to read all of it to puzzle it out. I also don't know what the interactions are -- does running as a daemon mean no log files are written and the only logging is syslogd?

      • Posted April 7, 2011 at 7:52 pm | Permalink

        I'll consider adding a summary table.

        File logging is not turned off when running as a daemon, you'll need to turn them off explicitly. File logging is only turned off by default under -e (and I'm considering under $ENV{HARNESS_ACTIVE} too).

        BTW, I'm open to changing the defaults (e.g. under what condition should an output be off/on by default, the default log file path, default pattern, etc) to what people expect them to be in most cases, as that's essentially the main goal of the module.

  3. chansen
    Posted April 13, 2011 at 3:10 pm | Permalink

    I mostly agree with your post, except for the usage of Time::Piece::Month, it's using strftime and strptime to get the dates, something that I would expect from a PHP script/module but not a Perl module.

    Anyway, TIMTOWTDI:

    Using DateTime:

    $d = DateTime->today->truncate(to => 'month')->add(months => 1);

    Using Time::Piece without Time::Piece::Month:

    $d = Time::Piece->new;
    $d = $d + (86000 * $d->month_last_day) - (86000 * ($d->mday - 1));

    Using Date::Simple:

    $d = Date::Simple->today;
    $d = $d + Date::Simple::days_in_month($d->year, $d->month) - $d->day + 1;

    All of above more or less sucks, it should be possible to using a construct like this:

    $d = Date::Simple->new($d->year, $d->month + 1, 1);

    Both Java and JavaScript provides date wrapping, but I couldn't find any Perl modules that DWIM (except POSIX::mktime on my os (darwin) but relying on that is unportable).


    use Date::Simple qw[days_in_month];

    sub mkdate ($$$) {
    my ($y, $m, $d) = @_;

    if ($m 12) {
    $y += $m / 12;
    $m %= 12;
    if ($m < 1) {
    $m += 12;
    $y--;
    }
    }
    if ($d < 1) {
    while ($d < 1) {
    $m--;
    if ($m 28) {
    my $dim = days_in_month($y, $m);
    while ($d > $dim) {
    $d -= $dim;
    $m++;
    if ($m > 12) {
    $m = 1;
    $y++;
    }
    $dim = days_in_month($y, $m);
    }
    }
    return $class->new($y, $m, $d);
    }

    # First day of next month
    $d = Date::Simple->new(mkdate($d->year, $d->month + 1, 1));

    # Last day of next month
    $d = Date::Simple->new(mkdate($d->year, $d->month + 2, 0));

    # First day of previous month
    $d = Date::Simple->new(mkdate($d->year, $d->month - 1, 1));

    # Last day of previous month
    $d = Date::Simple->new(mkdate($d->year, $d->month, 0));

    # First day of current month
    $d = Date::Simple->new(mkdate($d->year, $d->month, 1));

    # Last day of current month
    $d = Date::Simple->new(mkdate($d->year, $d->month + 1, 0));

    • chansen
      Posted April 13, 2011 at 4:48 pm | Permalink

      s/$class->new// in mkdate(). Not sure if it's my fault that the code wraps or if the blogs handles code tags badly.

    • chansen
      Posted April 13, 2011 at 5:14 pm | Permalink
    • Posted April 13, 2011 at 10:43 pm | Permalink

      I figured that DateTime had an answer, but I didn't know it well enough to grok its date math. Thanks for that tip!

© 2009-2014 David Golden All Rights Reserved