Sixtease has asked for the wisdom of the Perl Monks concerning the following question:

Friends,

I was refactoring my code. I had a subroutine that did

begin(); stuff(); if (something went wrong) { return } more_stuff(); end();

Now I refactored stuff into another subroutine:

begin(); do_stuff(); end(); sub do_stuff { stuff(); if (something went wrong) { return } more_stuff(); }

But now, if (something went wrong) { return } won't exit the calling subroutine. How do I exit from the caller? Is there a better way than, say, returning a special value or setting an $ERRNO variable?

use strict; use warnings; print "Just Another Perl Hacker\n";

Replies are listed 'Best First'.
Re: exit calling subroutine
by moritz (Cardinal) on Jan 10, 2008 at 08:20 UTC
    If something goes wrong, throw and exception with die, and you can catch it at an arbitrary level with eval (in the BLOCK form).

    That's what exceptions where made for, after all ;-)

Re: exit calling subroutine
by jbert (Priest) on Jan 10, 2008 at 09:14 UTC
    Whenever you create a function, whether through initial design or refactoring, you need to give some thought to it's error handling.

    Error handling is typically done either via return values (e.g. empty return - gives undef in scalar context or empty list in list context) or by exceptions (with raw eval/die or with a module which provides some syntactic sugar, such as Exception::Class).

    There are also bizarre various ways to do this in perl, like the subroutine goto above, or you could stick a loop around the whole thing and call next in the subroutine instead of return. But imho these sorts of things don't aid readability or maintainability - which is the purpose of refactoring in the first place.

Re: exit calling subroutine
by Zaxo (Archbishop) on Jan 10, 2008 at 08:31 UTC

    By convention, perl uses an undefined return as you have to signal an error. You need to test that return value to make the convention effective. partial_success() if not defined do_stuff();

    It seems that your end logic makes the return value irrelevant. Setting $ERRNO or $! is always useful, since even the shell on end will honor and report that value.

    After Compline,
    Zaxo

Re: exit calling subroutine
by jwkrahn (Abbot) on Jan 10, 2008 at 09:02 UTC
    You can do this to get the effect that you want:
    begin(); goto &do_stuff; end(); sub do_stuff { stuff(); if (something went wrong) { return } more_stuff(); }
      I don't think "never call end" is the desired effect.
Re: exit calling subroutine
by tweetiepooh (Hermit) on Jan 10, 2008 at 15:44 UTC
    Maybe it's my age but I don't like dropping out of routines like this preferring a one way in, one way out approach.
    begin(); do_stuff(); end(); sub do_stuff { my status = stuff(); if (status is ok) { more_stuff(); } return status; }
    then taking this a little further, make sure that stuff() and more_stuff() return status values
    begin(); do_stuff(); end(); sub do_stuff { return stuff() and more_stuff(); }
    Real code tends to be more complex than this and sometimes it is cleaner to take the short cut.
Re: exit calling subroutine
by Sixtease (Friar) on Jan 10, 2008 at 15:00 UTC

    Thank you all for the replies.

    you could stick a loop around the whole thing and call next in the subroutine instead of return

    I tried exactly that but it didn't work, complaining that it couldn't find the label.

    LABEL: { sub outer { inner(); } sub inner { next LABEL; } }
    use strict; use warnings; print "Just Another Perl Hacker\n";
      C:\@Work\Perl>perl -wMstrict -e "LABEL: { outer(); } sub outer { print qq(in outer() before inner() \n); inner(); print qq(in outer() after inner() \n); } sub inner { print qq(in inner() \n); no warnings; next; }" in outer() before inner() in inner() C:\@Work\Perl>perl -wMstrict -e "LABEL: { outer(); } sub outer { print qq(in outer() before inner() \n); inner(); print qq(in outer() after inner() \n); } sub inner { print qq(in inner() \n); no warnings; ; }" in outer() before inner() in inner() in outer() after inner()
Re: exit calling subroutine
by Sixtease (Friar) on Jan 10, 2008 at 18:43 UTC

    OK I went the seemingly cleanest route of exceptions. Now what has been one line of calling a function, became this bloat:

    undef $@; my @val = eval { do_stuff() }; if ($@) { if (ref $@ and $@->isa('SoftError')) { warn ${$@}; # SoftError object is a scalar ref with the mes +sage return } else { die $@ # as if we had never caught it } } push @rv, @val;

    What do you think?

    use strict; use warnings; print "Just Another Perl Hacker\n";