From: demerphq Date: Fri, 11 May 2012 08:27:31 GMT Subject: Re: Git.pm Message-ID: In-Reply-To: <4FAC2B2E.7060101@pileofstuff.org> On 10 May 2012 22:55, Andrew Sayers wrote: > Try::Tiny is an increasingly standard part of Perl - for example, it's > used extensively in Moose.  There's a good list of arguments about why > you should use it instead of eval in the Try::Tiny documentation: > http://search.cpan.org/~nuffin/Try-Tiny-0.01/lib/Try/Tiny.pm > > Now I've got that talking point done, here's what I really think :) > > Try::Tiny is designed on the assumption that throwing and catching > objects is something people should do all the time, and it can cause > subtle errors that are only worth the hassle if you get a lot of benefit > from doing so.  It's easy enough to come up with ideas for where they > might be useful, but in the real world advanced uses for exceptions are > usually a sign you're doing it wrong.  Three of the most common reasons > for frequent/complex exceptions are handling errors further up the call > stack, recovering from operations that fail, and clever error-handling. > > > If you want exceptions to be caught by code further up the call stack > than the immediate caller, you're likely to be disappointed.  This is > one of the places where "separation of concerns" applies - if I use a > module that uses a module that uses your module, then catching > exceptions from your code will just cause my program to break when some > module in the middle obscures your error by adding its own layer of > error handling. > > > If you have an operation that really might fail, and you want to > encourage most people to handle it most of the time, it's better to have > a function with a meaningful name and good documentation.  This puts the > burden on the calling function to handle the error instead of letting > them think "oh well, if it dies someone else will handle it".  It also > forces you to split functions along boundaries that make your code > readable, instead of falling for the temptation to make something that > "just works"... until it doesn't, and the maintainer has to go > spelunking through code they don't know.  So instead of: > >   try { >       Foo::frobnicate( widgets => 3 ); >   } catch { >      if (ref($_) eq 'Error::Widget') { >          die "Could not add 3 widgets"; >      } >   } > > It's better to ask the people using your module to write: > >   my $foo = Foo->new; >   $foo->add_widgets(3) or die "could not add 3 widgets" >   $foo->frobnicate; > > This is easier to document, easier to write and easier to read. > > > If you have an operation where calling code is supposed to do something > more complicated than give up, it's better to use a callback.  This > gives you an opportunity to document what's needed, and to check that > the calling code is doing the right thing before it's too late.  So > instead of: > >    my $widgets = 3; >    while ( $widgets ) { > >        try { >            Foo::frobnicate($widgets); >            $widgets = 0; >        } catch { >            if ( $_->{remaining_widgets} < 2 ) { >                die $_->{error}; >            } elsif ( $_->{remaining_widgets} == 2 ) { >                $widgets = 0; >            } >        } > >    } > > It's better to ask people using your module to write: > >    Foo::Frobnicate( >        widgets => 3, >        error_handler => sub { >             my ( $remaining_widgets, $error ) = @_; >             die $error if $broken_widgets < 2; >             return "give up" if $remaining_widgets == 2; >             return "continue"; >        }, >    ); > > Again, this is more readable and easier to document. > > > Aside from the philosophical angle, Try::Tiny is particularly hard to > maintain because it looks like a language extension, but is actually > just an ordinary module.  The try {} and catch {} blocks are anonymous > subroutines, which lead to some wonderfully unintuitive behaviour.  See > what you think these do, then run the code to find out: > > >    sub foo { >        try { >            return 1; >        } >        return 0; >    } > >    sub bar { > >        our @args = @_; >        our @ret; > >        try { >            @ret = >                wantarray >                    ?   grep( /blah/, @args ) >                    : [ grep( /blah/, @args ) ] >        }; > >        return @ret; >    } > >    my $foo = "bar"; >    sub baz { >        my $foo = "baz"; >        try { >            print $foo; >        } >    } > >    sub qux { >        my $ret; >        try { >            $ret = "value"; >        } >        return $ret; >    } > >    print foo, "\n"; >    print bar( "blah", "blip" ), "\n"; >    baz; >    print qux, "\n"; > > > In short, Try::Tiny looks like a lot of gain for not much pain, but > actually it's the other way around. Total agreement. Yves -- perl -Mre=debug -e "/just|another|perl|hacker/"