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

I have a method of an ssh object that I have created and this method adds an account to a remote unix box via an ssh connection. Now, what I basically want is to deal with errors in the best way possible here, so I am asking for some assistance from my fellow monks on the matter.

I'm sorry if my wording here is not that great, but it is somewhat difficult for me to figure out how to word this question. So, I am just going to give some code, and basically an explanation of what I am trying to do...

This is the first method which I don't really like. Basically I just return success or failed with an error message into a scalar. I then interpret the scalar in my main script...
#!/usr/bin/perl my $add_user=$ssh->add_account(login=>"bob",uid=123); # I want output printed (fail or success). print "$add_user\n"; die if ($add_user !~ /success/); sub add_account { ..... if (condition) { return "success: user added"; } elsif (condition { return "failed: uid not unique"; } else { return "failed: unknown error"; }


This is a better version, but I still know that I am not handling success/failures correctly. I know there is a better way to do this, but just not sure how nor where to look to find out.
#!/usr/bin/perl $add_user=$ssh->add_account(login=>"bob",uid=123); $ssh->print_error and die if (!$add_user); print "$add_user\n"; sub add_account { ..... if (condition) { return "success: user added"; } elsif (condition { $ssh->{error} = "failed: uid not unique"; } else { $ssh->{error} = "failed: unknown error"; } return; }
Basically, I want to say that if ($ssh->error_exists) { print "$error\n" and die; } else { print "$add_user\n" }, or if $add_user is not defined then print error and die, else print $add_user and move on. I am having a difficult time figuring out how to deal with errors... and sometimes I don't want them to stop my code, so just print but do not die. For example, if I am adding 100 users I do not want a failure on user #2 to stop my code, so let's not croak.

Anyway, hope this makes sense... any help is greatly appreciated! Thank you in advance monks!

Replies are listed 'Best First'.
Re: Dealing with errors in subroutines
by moritz (Cardinal) on Sep 09, 2009 at 07:02 UTC
    In my humble opinion it is a good idea to throw an exception when an error occurred. The caller is then free to either catch the exception (with eval BLOCK) and work with $@, or to not catch it have the error abort the program, printing out the error message.

    For example you could do something along these lines:

    for (@users) { my $success = eval { add_account($_); }; warn "Can't add user '$_': $@" unless $success; }

    This has the advantage over a plain return() that throwing the exception works the same if you call subs from add_account() and they detect an error.

    Perl 6 - links to (nearly) everything that is Perl 6.
      I agree.

      Moreover, the handling of thrown exceptions is far more scaleable if something like Exception::Class is used in favour of parsing fatal error strings - the likeliehood of breaking client code is far greater when the client code parses the error string than when the client code attempts to catch a known class of exception i.e. the interface is greatly simplified, which as outcomes go, is highly desirable.

      To my mind, the use of Error solely for the, slightly distasteful, syntactic sugar is a moot point : the intending user has to compare (for themselves) the benefit of tidier code with the drawback of a non-returning return in the try/catch blocks (as by perrin et al in this thread) - a well understood drawback that can be mitigated for by the simple expedient of a specific case (pun intended) in the coding standards coupled with code review and good unit test cases. The memory leakage problem, as noted in the above link, appears to have now been fixed.

      A user level that continues to overstate my experience :-))
      Somewhere along the way I picked up the habit of writing this idiom so:
      for (@users) { my $success = eval { add_account($_); }; if($@ or not $success) { warn "Can't add user '$_': $@" }
      But I don't remember why I thought it might be better. Certainly not from readability! What do others think of this kind of double-checking?


      - Boldra
        It's not paranoid but good practice, because (as ELISHEVA pointed out below) a DESTROY method can accidentally reset the global $@ variable, as this small piece of code demonstrates:
        use strict; use warnings; use Data::Dumper; sub DESTROY { eval { 1 } } eval { my $obj = bless {}; die "wrong"; }; print Dumper $@; __END__ $VAR1 = '';

        The correct solution is to localize $@ in the DESTROY sub, but you can't always rely on third-party modules to do such things correctly.

        Perl 6 - links to (nearly) everything that is Perl 6.
Re: Dealing with errors in subroutines
by ELISHEVA (Prior) on Sep 09, 2009 at 09:04 UTC

    Great question!

    I find myself debating over this one a lot. In theory, throwing exceptions should create cleaner code because callers don't have to worry about processing garbage if they ignore an error. Die will cause them to promptly exit. And if you don't want to promptly exit the subroutine, you can always stop the die request using an eval block.

    In practice, I find exceptions hard to use in Perl. Perl's syntax for handling exceptions is nowhere near as concise as its string processing. This can sometimes lead to a lot of noisy boiler plate code when you do want to handle exceptions. For example, you can't just check $@ to see if there is an error. $@ is a global variable. If code is attached to a DIE signal or a DESTROY method, dying will trigger the code. That code can easily reset $@ (see Devel::EvalError for an example and discussion). Instead you need to test the return value of the eval block, like this:

    # be nice to callers - don't wipe out $@ for them local $@; eval { # ... do some stuff ... # make sure we only return false if we die before # the end return 1; } or do { #handle errors here }

    Clean exception handling code needs to be able to pick out the exceptions it cares about with the least amount of verbage. When you process errors as return values, you can generally assume a very limited range of possible errors. However, if methods are allowed to ignore exceptions and just die, this will mean that your highest level code should expect that exceptions are coming from anywhere. To handle exceptions that may have been triggered 10 stack frames down, one needs a good way of classifying exceptions and selecting the exceptions you care about.

    Languages like Java do this using a combination of exception classes and try {...} catch (SomeClass e) {...} catch (SomeOtherClass e) {...}. Exceptions are organized into a standard hierarchy and there is provision to extend that class hierarchy with exceptions of your own. When you write a method, you document which class of exceptions it throws and callers can catch anything belonging to that class with a minumum of fuss. Selecting exceptions by class makes it easy to control whether one processes the specialized IO exception thrown by package FOO, or a general purpose IO exception.

    But in Perl there are no standard exception classes. There are several modules on CPAN that have tried to fill in that gap, but classes alone do not make exception handling easy. To make exception handling relatively clean one needs a combination of a well defined exception class hierarchy and a class oriented try {...} catch {...} syntax.

    Perl doesn't have native class based syntax for exception handling so you have to do something like this, if you want to pick out exceptions by classes:

    local $@; eval { # ... do something ... return 1; } or do { my $err=$@; #prevent reset while evaluating if conditions if (!$err) { die Exception::Unknown->new(); } elsif (!Scalar::Util::blessed($err)) { #ugly string regex processing here } elsif ($err->isa('BadParameterException')) { #handle bad parameters } elsif ($err->isa('IOException')) { #handle IO exceptions } elsif #... yada yada } else { die Exception::Unknown->new(); } }

    CPAN, of course, has modules that try to make this cleaner as well. Some of these modules create syntactic sugar using source filters (for example, Error::TryCatch). The written code looks prettier, but there is hell to pay come debugging time. The actual line numbers in the code no longer match up with the line numbers spit out by Perl error messages. Others, like Error try to emulate try {...} catch {...} with closures, but as Perrin points out in Re: Re2: Learning how to use the Error module by example, this can lead to unreliable results.

    Whether we code it out the long way or use some module to provide syntactic sugar, all of this boiler plate code is going to be for naught if $@ gets reset by code that is triggered while dying. Localizing $@ doesn't really solve the problem of $@ being reset. It helps our own callers, but doesn't protect us from the effects of things we call. If a third party object or function lower down in the stack forgets to localize their own copy of $@, our localized $@ might still get reset.

    There are always new things I'm learning about Perl, but based on what I know so far, there isn't a really clean and reliable way to know both the fact and kind of an exception passed up through several stack frame. Given that, I generally find myself handling problems close to the source via return values.

    But I don't really like doing that at all. I would much prefer using exceptions. In most cases it is very hard to know the meaning of an error in a low level API function or the best way to communicate it. If the low level subroutine is part of a back-end process, then English technical messages might be best. But if that very same low level API function is used in a GUI application, I might want a non-technical message in the user's native language. Those different messaging scenarios can be handled very easily if error data is stored in fields within an error object that is passed up the stack frame until it gets to an application level caller.

    Best, beth

      I agree that Perl 5's exception system is somewhat clumsy, and makes it hard to filter by exception type.

      On the other hand, in my mostly small, mostly fun projects I haven't needed that feature at all - either I just hand on exceptions, or drop them, or warn and continue - so far I always left the interpretation of the error to the user, which worked pretty well for me.

      That said in Perl 6 we will have something like an exception hierarchy (except that it won't be exactly tree like, because we don't just rely on inheritance but also on role composition). But the problem - as always - is that somebody needs to actually do it: define a structure, default error messages and so on, and spec that.

      So please take this as a call for volunteers. If you are (like me) interested in Perl 6 having a better exception system than Perl 5 (and one that makes translations to other languages easy without breaking any code), consider contributing.

      (I'm glad to help where I can, but I won't be the driving force - I have too many other Perl 6 related projects I'm working on).

      Perl 6 - links to (nearly) everything that is Perl 6.
Re: Dealing with errors in subroutines
by wfsp (Abbot) on Sep 09, 2009 at 08:14 UTC
    A flag can be useful in these situations. Now the method could return something useful without getting tangled up in success/failure issues.
    my $account_data = $ssh->add_account( login => q{bob}, uid => 123 ); if ($ssh->success){ print qq{success: user added\n}; # do something with $account_data } else{ printf qq{failed: %s\n}, $ssh->error_msg; # try next user } sub add_account{ my $account_data; # ... $ssh->{success} = 0; if (condition){ $ssh->{success} = 1; } elsif (condition){ $ssh->{error_msg} = qq{uid not unique}; } else { $ssh->{error_msg} = qq{unknown error}; } return $account_data; } sub success {shift->{success} }; sub error_msg {shift->{error_msg} };
Re: Dealing with errors in subroutines
by Sewi (Friar) on Sep 09, 2009 at 08:28 UTC
    There are many ways of doing what you want. Here is the quickest one, assuming that you don't need a return value on success:
    sub add_user { if (condition) { return 'Oops'; } else { return 'Argh'; } return undef; } my $Result = &add_user; if ($Result) { print "Something failed: $Result\n"; }
    This also works if you need a return value, making things a very little bit more complicated:
    sub add_user { if (condition) { return 0,'Oops'; } else { return 0,'Argh'; } return 1; } my ($Result, $ErrorText) = &add_user; if ( ! $Result) { print "Something failed: $Error_Text\n"; }
    Here is the multilanguage style which is also good whenever you want a program to parse your result (parsing text error messages is ugly):
    sub add_user { if (condition) { return 1; } else { return 2; } return 0; } my $Result = &add_user; if ($Result) { print "Something failed: ".$Texts{'Error_'.$Result}."\n"; }
    Finally, there is an OOP approch where you define a package and bless it on a hash where the error message and other things could be stored, but this is way too big for your problem...
Re: Dealing with errors in subroutines
by sierpinski (Chaplain) on Sep 09, 2009 at 17:02 UTC
    For what it's worth, I went the eval route, and it works perfectly. I have a script that uses Net::SSH::Expect to connect to a list of servers one at a time, and running various commands and then parsing the output (looking for errors, failures, etc). I was running into issues when the ssh connection would die suddenly (because the server had been turned off) and I got around that by doing this:
    my $rc = eval{( $ret =~ />\s*|$\s*\z/) or die "where's the remote prom +pt?";}; if($rc) { if($ret =~ m/[Pp]assword:/) { print("Couldn't connect to host\n"); } }
    Originally I tried to check the return code (the $ret) or the return code of the run_ssh() that actually spawns the ssh process, but since the ssh spawned successfully (it just died soon after) it never returned any useful value, and the above command would break if the ssh object was already gone, or if the server was asking for a password (meaning my SSH keys were not set up on that host)

    I can give credit to the Monks here for this solution, by the way!
    /\ Sierpinski
Re: Dealing with errors in subroutines
by Anonymous Monk on Sep 09, 2009 at 17:09 UTC

    I do stuff like this all the time. I use a list of errors, so () indicates no errors. Personally I don't like exception handling as it moves that code somewhere else, but I do often create an @errors array in most objects. If a method finds an error, a string is shoved errors in that list. At some point in the future I check that array to see if it is empty or not. It is simple enough to work in many situations. And the error handling logic is still fairly close to everything else.

    TIMTOWTDI, of course

    - doug