Selasa, 20 Oktober 2009

HTTP-style sub return

In a subroutine, we often want to return the status of operation (success/failure/error code) as well as the result of the operation.

When a function does not need to return any result, it can just return the status, usually as an integer scalar. In C and Unix the convention is to return 0 for success and non-zero for the error code. In Perl and many other languages, it is the other way around: zero/false/undef for failure and true values for success. You can also return the result as well right there and then as long as the result evaluates to true. This can be a problem if there is a possibility that the result be zero/false/undef.

Alternatively, you can return the result instead. How do we return the error code then? Usually via some global variable like $? and $! in Perl. This has drawbacks of its own, like in multithreaded/reentrant code.

It is safer thus, to return both the status and the result separately and explicitly, e.g. using a 2-element array:

($status, $result) = foo(...);

For roughly a year now, I have been adopting something like the above, with what I call HTTP-style return convention. Instead of 2, I return a 3-element array in my subs:

return ($status, $extra_info, $result);

$status is a 3-digit integer values, with values taken as much as possible from the HTTP spec: 200 means success, 4xx means generic "client side" (i.e. caller side) error like missing or invalid arguments, 404 means not found, 403 means forbidden, 5xx means error in the "server side" (our side, the sub side), 501 means unimplemented, and so on.

$extra_info is a hashref which contains, well... extra info, like error string, debugging messages, or intermediate results. This is the equivalent of HTTP response headers. But it can be an undef too if the sub does not offer extra information. So it will avoid creating unnecessary hashref.

$result is the actual result.

An example code:

my @resp = process_stuff(@stuffs);
if ($resp[0] != 200) {
die "Failed ($resp[1]{errstr})";
} else {
print "Number of stuffs input: ", scalar(@stuffs);
print "Number of stuffs processed: ", $resp[2];

Another example:

my @resp = search_stuff($stuff);
if ($resp[0] == 404) {
die "Stuff $stuff not found";
} elsif ($resp[0] != 200) {
die "Failed";
} else {
print "Stuff $stuff found in $resp[2]";

It is a bit confusing for readers not familiar with this style, but I find this clear as it is (i.e., without declaring/using constants for 200, 404, etc).

The advantages of this return style:

  • You can return the status as well as the result as well as extra info.

  • The convention for the status codes is already familiar to many. HTTP has been so popular for most of the lifetime of Perl, that most Perl programmers who have dabbed in CGI or Internet programming should be familiar with it. Even many nonprogrammers know what 404 or 500 mean since they often see this while browsing. The 2xx, 4xx, 5xx convention is also used in other protocols like SMTP.

    If you are not, you can always use a comment or a constant to explain the meaning of the numbers.

  • You can easily wrap your sub later in a REST API or web service. Just pass $status as HTTP status code, (selected) pairs in $extra_info into HTTP response headers, and $result (possibly encoded in JSON/YAML/whatever).

  • A bonus, because Perl has contexts, you can also do this:

    if (wantarray) {
    return ($status, $extra_info, $result);
    } else {
    return $result;

    so that you can fallback to a very simple style when all the other stuffs are not needed:

    my $result = foo(...); # don't care about status, assume always success

I find this return style somewhat relevant in the light of PSGI's deservedly speedy upshoot to popularity.

Tidak ada komentar:

Poskan Komentar