[Catalyst] Re: Perl::Rails?

Brandon Black blblack at gmail.com
Fri Dec 9 15:02:00 CET 2005


On 12/9/05, Shlomi Fish <shlomif at iglu.org.il> wrote:
> I can also add poor debugging ability to it. One essentially cannot debug
> functions in the core Controller Catalyst components, due to what I was told
> were "nested eval"'s. I don't care if it's a bug of perl or Catalyst - I
> don't care - I want to debug it in style. What I restorted to was to extract
> code out of my application and debug that, but it's a kludge.
>

I would agree that debugging is a weakness in Catalyst at the moment. 
I run into this weakness in two ways primarily.  The first is that
while the failure of a Controller to have correct syntax results in
the whole app failing to start and showing you the parse error (a good
thing), if you make a similar syntax mistake in a Model class (using
the standard CDBI/DBIC autoloader models), you get no such warning. 
The first you hear of it is that some method you're trying to call
from that model doesn't exist at runtime.  This came up before and
someone had a solution for it, but I couldn't find the email earlier,
and in any case the real solution should just work for the
default/naive case.

The bigger issue, IMHO, is that exceptions provide no stack trace.  If
the exception occurs directly in your Controller classes, it's a
no-brainer.  But when the death message comes from deep in the bowels
of some other model you didn't write (say a death in a DateTime
constructor, or a death deep inside Catalyst::, etc), the error
message is far from helpful in tracking down where you likely made a
mistake in your application code.

I haven't investigated this enough yet to know whether there's some
larger issue that makes it more difficult than it seems, but it seems
like we should be able to add an exception-handling class to the
Catalyst infrastructure that does proper stack traces.  I rolled my
own for another part of my application that's based on the concepts in
Error.pm, but considerably simpler, shorter, and more efficient.  I'd
be more than willing to donate that to Catalyst, but I'm not sure that
most Catalyst devs would be willing to start using the try/catch
idiom, given the issues various people have pointed out in the past
(closures, return values).  Once you get used to the idiom it's a
no-brainer to use though, even with return vals:

sub myroutine {
   my $retval = try {
        ... some code which may My:Exception:Something->throw(...);
        ... or also some external module code which may die "foo";
        ... You can do just about anything in a try block - I
generally wrap entire long-running daemons in try blocks.
        return 3;
   }
   catch My::Exception::Foo with {
        ... we consider this exception nonfatal
        return 2;
   }
   catch My::Exception::Bar with {
        .... this one deserves a rethrow to some outer context
(eventually the outermost context is straight up perl death with stack
trace)
        $_->rethrow();
   }
   catch SYSERR with {
        .. SYSERR is a special exception class which matches non-OO
"die" statements (and they can be optionally rethrown with $_->rethrow
just like everything else)
   }
   catch All with {
         ... had this catch-all not been here, the default would have
been to rethrow anything not explicitly caught, as above.  You
shouldn't normally be using this much, although it's good at the
highest contextual layer of a webapp to use it to HTML-ify the
error+stacktrace that came from below, or if you're running some very
untrustable deadly code that you expect to fail in odd ways from time
to time and you wish to handle it gracefully in all cases.
         warn "Exception happened: $_";
         return 45;
   };
   return $retval;
}

My simplified version only defines try/catch clauses and the special
All and SYSERR exception classes (with different names at the moment
though, that's easy to change).  It skips the whole except/finally/etc
clauses as I didn't see much practical use for them (it seems you can
do those things in other ways).  User-defined exception class names
derive from the All class, as does SYSERR, and the "with" matching
pays attention to ->isa().  So catching My::Exception::Base will catch
My::Exception::DerivedFromBase.  And all exceptions that occur within
a try {} block stringify to include a stack trace, even plain old
"die".  Also, the stacktrace filters itself out, so that you don't see
all the extra lines in the stacktrace from the try/catch clauses and
internal subroutines of the exception module either, as if you weren't
using one.  I'll probably CPAN it as a generic module sometime in
January (once I'm past a couple of deadlines here at work and have
some time) anyways.

-- Brandon



More information about the Catalyst mailing list