2013-08-19 49 views
0

我的問題類似於: Is it possible for a Perl subroutine to force its caller to return? 但我需要程序方法。Perl程序返回兩個堆棧調用級別

我想用編程回一些消息處理過程,例如必需的代碼:

sub PrintMessage { 
    #this function can print to the screen and both to logfile 
    print "Script message: $_[0]\n"; 
} 

sub ReturnMessage { 
    PrintMessage($_[0]); 
    return $_[2]; # <-- we thinking about *this* return 
} 

sub WorkingProc { 
    PrintMessage("Job is started now"); 
    #some code 
    PrintMessage("processed 5 items"); 

    # this should return from WorkingProc with given exitcode 
    ReturnMessage("too many items!",5) if $items>100; 

    #another code 
    ReturnMessage("time exceded!",6) if $timespent>3600; 
    PrintMessage("All processed succesfully"); 
    return 0; 
} 

my $ExitCode=WorkingProc(); 
#finish something 
exit $ExitCode 

理念是,如何使用ReturnMessage函數內部回報率從WorkingProc功能指定的代碼退出?注意,ReturnMessage函數是從很多地方調用的。

回答

3

這是不可能的。但是,可以明確地返回:

sub WorkingProc { 
    PrintMessage("Job is started now"); 
    ... 
    PrintMessage("processed 5 items"); 

    # this returns from WorkingProc with given exitcode 
    return ReturnMessage("to much items!", 5) if $items > 100; 

    ... 
    return ReturnMessage("time exceded!", 6) if $timespent > 3600; 
    PrintMessage("All processed succesfully"); 
    return 0; 
} 

一個子可以有任意數量return語句,所以這不是一個問題。

這樣的解決方案比通過調用堆棧進行黑客攻擊更可取,因爲控制流對讀者來說更爲明顯。你夢想的是一種GOTO,大多數不寫C或BASIC等的人已經放棄了45年前。

您的代碼依賴退出代碼來確定子例程中的錯誤。 *嘆*。 Perl有一個相當倒退的異常系統,但仍比那個更先進,即

拋出致命錯誤die "Reason"use Carpcroak "Reason"。使用Try::TinyTryCatch模塊捕獲錯誤。

sub WorkingProc { 
    PrintMessage("Job is started now"); 
    ... 
    PrintMessage("processed 5 items"); 

    # this should return from WorkingProc with given exitcode 
    die "Too much items!" if $items > 100; 

    ... 
    die "Time exceeded" if $timespent > 3600; 
    PrintMessage("All processed succesfully"); 
    return 0; 
} 

WorkingProc(); 

如果發生錯誤,將以非零狀態退出。

+0

感謝您的回答,第一種方法我是你現在唱歌,用exitcode路由(使用ReturnMessage作爲返回參數)。我會嘗試按照你的建議嘗試:Tiny/TryCatch。我必須小心,因爲多次調用WorkingProc。不要緊,以前呼叫乾淨或不乾淨。主程序決定是否有意義下一個循環,它依賴於退出代碼。 – Znik

+0

我發現了另一個異常捕獲器:Try :: Tiny :: SmartCatch。 TryCatch很差,Try :: Tiny很有趣。我會用這個。但是,我發現非常新鮮的Try :: Tiny :: SmartCatch,在'try'塊中由'throw'生成的這個'catch'過濾器攔截棧名爲異常。非常好,類似於java :)你可以在這裏找到它:https://github.com/mysz/try-tiny-smartcatch – Znik

+0

@znik可以安裝[Try :: Tiny :: SmartCatch] [來自CPAN]( https://開頭metacpan。組織/模塊/ TRY ::微型:: SmartCatch)。這比從github複製代碼更好。不過,我強烈建議你使用vanilla'Try :: Tiny':這是事實上的標準,它解決了所有傳統的錯誤處理方法'eval {...};如果($ @){...}'有。無論你最終使用什麼,我認爲你想使用適當的異常處理是很好的。 – amon

2

對於非本地返回而言,想到的方法是從最內層的函數中拋出一個異常(死)。

然後你需要有一些包裝代碼來處理它在頂層。你可以設計一套實用程序來自動設置。

2

使用與Exception::Class結合Log::AnyLog::Any::Adapter讓你把所有的作品以最小的大驚小怪和最大的靈活性在一起:

#!/usr/bin/env perl 

package My::Worker; 
use strict; use warnings; 

use Const::Fast; 
use Log::Any qw($log); 

use Exception::Class (
    JobException => { fields => [qw(exit_code)] }, 
     TooManyItemsException => { 
      isa => 'JobException', 
      description => 'The worker was given too many items to process', 
     }, 
     TimeExceededException => { 
      isa => 'JobException', 
      description => 'The worker spent too much time processing items', 
     }, 
); 

sub work { 
    my $jobid = shift; 
    my $items = shift; 

    const my $ITEM_LIMIT => 100; 
    const my $TIME_LIMIT => 10; 

    $log->infof('Job %s started', $jobid); 

    shift @$items for 1 .. 5; 
    $log->info('Processed 5 items'); 

    if (0.25 > rand) { 
     # throw this one with 25% probability 
     if (@$items > $ITEM_LIMIT) { 
      TooManyItemsException->throw(
       error => sprintf(
        '%d items remain. Limit is %d.', 
        scalar @$items, $ITEM_LIMIT, 
       ), 
       exit_code => 5, 
      ); 
     } 
    } 

    { # simulate some work that might take more than 10 seconds 
     local $| = 1; 
     for (1 .. 40) { 
      sleep 1 if 0.3 > rand; 
      print '.'; 
     } 
     print "\n"; 
    } 
    my $time_spent = time - $^T; 
    ($time_spent > $TIME_LIMIT) and 
     TimeExceededException->throw(
      error => sprintf (
       'Spent %d seconds. Limit is %d.', 
       $time_spent, $TIME_LIMIT, 
      ), 
      exit_code => 6); 
    $log->info('All processed succesfully'); 
    return; 
} 

package main; 

use strict; use warnings; 
use Log::Any qw($log); 
use Log::Any::Adapter ('Stderr'); 

eval { My::Worker::work(exceptional_job => [1 .. 200]) }; 
if (my $x = JobException->caught) { 
    $log->error($x->description); 
    $log->error($x->error); 
    exit $x->exit_code; 
} 

輸出示例:

Job exceptional_job started 
Processed 5 items 
........................................ 
The worker spent too much time processing items 
Spent 12 seconds. Limit is 10.

Job exceptional_job started 
Processed 5 items 
The worker was given too many items to process 
195 items remain. Limit is 100.
+0

非常有趣的方法。我認爲它適用於我的問題,但我會使用簡單的異常處理Try :: Tiny。要解決的一個問題是,下一步需要依靠previos步驟。當致命錯誤返回時,執行下一步就是廢話(例如磁盤已滿)。當「平均」錯誤返回時,通常可以進行下一步。我相信你的方法可以適應該算法。 – Znik