Perl異常處理方法總結
阿新 • • 發佈:2018-12-30
程式指令碼在執行過程中,總會碰到這樣那樣的問題,我們會預知一些問題併為其準備好處理程式碼,而有一些不能預知。好的程式要能儘可能多的處理可能出現的異常問題,本文就總結了一些方法來解決這些異常,當然perl在這個處理了不及其它同類語言,但也不會差到那裡。在開始前,我們先盤點一些關於perl的優缺點。
0. 歷史太悠久了。你可以在1997年的計算機上找到perl5.0。(只是吐槽一下,歷史悠久沒什麼不好,與時俱進才是關鍵)
1. 不回收迴圈垃圾(這是個硬傷,也許和Perl設計的初衷有關,小指令碼影響不大;但因為這個,perl與稍微大一點的程式就無緣了)
2. 容錯(如字串和數值的隱式轉換等),但換句話說就是對錯誤過於放縱,程式產生錯誤結果而不是報錯。
3 . 一件事有很多方法做(TIMTOWTDY),但換句話說就是做一件事沒有“一種明顯的做法”。比如陣列的長度是$#somearray+1。為什麼是加一呢?難道不是somearray.length或者len(somearray)嗎?
4. 語法比較難懂。語法還處在“指令碼語言”(指的是那些隨手寫隨手扔的批處理指令碼)的定位上,只是稍微比shell指令碼更像傳統的程式語言。比如:
4.1 大量使用$符號,以及各種用符號表示的特殊變數。
4.2 函式不帶形參列表。
4.3 面向物件支援不好:語法中沒有物件語法(沒有class等關鍵字),全靠hash和bless。繼承靠ISA,也沒有語法支援。
4.4 只有基本的異常處理(perl的eval和die,類似java的try和throw)。但是異常只是一條字串資訊,少了基於物件的異常系統,異常處理總是不如其它語言。
以上這些都會影響可讀性和可寫性。需要注意的是,人的大腦同時可以關注的資訊是有限的。如果寫業務邏輯,卻偏偏要不斷關注實現細節(比如使用package, hash, ISA, bless來實現面向物件),程式設計師的思路就會不斷地被這些細節打亂,急劇降低程式設計速度,還會不斷犯錯誤,甚至為了簡單性而犧牲效率甚至正確性。
一個高階的語言就是對高階的概念的抽象,使得程式設計師可以將思維從下層的細節中脫離出來(Separation of Concern)。Perl作為文字批處理用的指令碼語言是夠的,可以做很多shell不擅長的字串處理(正則表示式很贊)和計算,這在當時也是好的。但是對於稍微大一些的程式,甚至需要面向物件的時候,Perl就不合適了。
Perl可以寫出很簡短的程式碼(谷歌一下“code golf”),在這一點上Ruby更像Perl。下面就介紹一些讓Perl指令碼編寫的更加規範,甚至在出現錯誤時能得到很好的處理的方法技巧。
開啟約束指令,讓編碼更規範
如果你使用perl5.10 或更高的版本,可以顯示的指定當前Perl版本號來自動開啟約束指令。
use 5.012; #自動啟用use strict 指令
use v5.10; #自動布嚴格模式
之前的版本通過新增下邊的指令:
use strict;
通過啟用約束指令,程式設計時常見的錯誤很容易暴露出來。而啟用warnings指令的話,還能捕獲一些其他不是很緊要的問題。
如果擔心原先的程式啟用strict後程序不正常執行,則可以在實際修改程式碼前,先在命令列上試著啟用strict看看:
perl -Mstrict freeoa_program.pl
Perl的約束集包括vars(變數),subs(子程式)和refs(引用)這三部分,通常這三種約束同時使用。
在很多情況下,系統呼叫可能會失敗;例如,嘗試開啟不存在的檔案,或者刪除某個仍含有檔案的目錄,或者嘗試讀取沒有讀許可權的檔案。在前面的示例中,我們已經用到了die函式,詳細討論有關錯誤處理和錯誤處理函式的相關內容。這些函式包括die函式、warn函式和eval函式。
die函式用於在命令或檔案控制代碼失敗時退出Perl指令碼。
warn函式類似於die函式,但它不會退出指令碼。
eval函式具有多種用途,但它主要還是用於異常處理。
讀者想必還記得短路運算子&&和||,這兩個運算子首先會求其左側運算元的值,然後才會求其右側運算元的值。如果&&左側運算元值為true,則求其右側的運算元。如果||左側運算元的值為false,這才求其右側的運算元。
Perl 5提供的Carp模組擴充套件了die和warn的功能
對一段程式碼中如果有一句出現異常,但事先並不知道是哪一句,怎樣進行異常的捕獲?
eval{.........};#捕獲執行時的錯誤
if([email protected]){........};#進行錯誤處理
通過使用 Perl 的 eval 語句來分析錯誤,使用標準方法來處理 Perl 錯誤,請使用以下語法:
eval {enter statements you want to monitor};
在執行時,如果 Perl 引擎在 eval 塊內的語句中遇到錯誤,那麼它會跳過 eval 塊的剩餘部分,併為對應的錯誤文字設定 [email protected]。例如:
eval{$objectName->MethodName();};
if ([email protected]){
print "Error using MethodName method. Error: [email protected]\n";
}
else{
# continue without error ...
}
某些預期通常失敗的函式不包含在此範圍內。尤其是驗證和設定欄位函式返回錯誤說明,而不是丟擲異常。這樣可攔截內部中斷訊號後對其進行處理,而不是簡單地讓程式對異常按預設方法進行處理。
The recommended way to do some things, like timeouts, is through an eval/die pair. However, I've noticed that if you set $SIG{__DIE__} to do some custom reporting, like in:
$SIG{__WARN__} = sub { ... }; # custom error report
$SIG{__DIE__} = sub { &{$SIG{__WARN__}; exit(1); };
# custom error report and terminate
eval {
$SIG{ALRM} = sub { die "timeout\n"; };
alarm 20;
...
alarm 0;
}
"die" isn't caught! It remains fatal!
The only way around this (that I found), is to clear $SIG{__DIE__} in the eval blok:
eval{
local($SIG{__DIE__}); #back to standard Perl die
...
}
Question: is there another way to terminate a __DIE__ sub, that is NOT fatal in eval? "die" doesn't die in eval, but ONLY if you didn't set $SIG{__DIE__} yourself (even outside of the eval block). Surely, this can't be the way it's supposed to be?
你最好要使用Try::Tiny模組來處理這些問題,這將幫助你避免一些老的版本里的陷阱。
use Try::Tiny;
try{
die "foo";
}catch{
warn "caught error: $_";
};
有時我們常常寫多個 open ,然後還要寫上多次 die 像下面,讀一個檔案,然後寫一個檔案,有時我寫的 open 會超過 4 個,要寫很多次 die 這時非常麻煩。我們如果直接 use autodie 就直接能解決所有問題,所有的 die 的地方都能自動實現。
open my $fh, '<', $in or die "$!";
open my $fh, '>', $out or die "$!";
用autodie簡化錯誤處理
autodie編譯指令(從5.10.1起開始自帶,也可以直接從CPAN安裝)
預設情況下,autodie會對它能起作用的所有函式生效。如果只是希望對某些特定函式起作用,可以將各個函式的名字或一組函式的組名列出來告訴autodie:
use autodie qw(open close); #只對特定函式生效
use autodie qw(:filesys); #只對 某組函式生效
在autodie捕獲錯誤時,它會把[email protected]設定為autodie::exception物件,而[email protected]就是表示eval錯誤變數
加上 autodie 以後就成了
use autodie
open my $fh, '<', $fin;
open my $fh, '>', $fout;
有了這個方便多了,有時我們常常不想讓程式 die 了後退出,想抓到原因,得使用 eval 然後來檢查 [email protected],像下面這樣。
use autodie;
eval{
open my $fh, '<', $in;
# .....
}
if([email protected]){
#TODO
}
eval{
open my $fh, '>', $out;
}
if([email protected]){
#TODO
}
還好上次只有二個 open 。是不是感覺很痛苦,要是 open 之類會 die 的更加多點的話。另外,有沒有發現,只要第一個 open 失敗,第二個 if ([email protected]) 永遠會失敗,因為 [email protected] 的標記下面也檢查同樣的變數。這時使用 Try::Tiny 中的 try catch 吧。
與Try::Tiny模組配合使用
Perl 沒有內建的異常處理機制,所以最合適的方法就是使用Try::Tiny模組。雖然CPAN中處理異常的模組很多,但是這個模組最為輕巧,使用起來也沒有過多的依賴關係。
use autodie;
use Try::Tiny;
# handle errors with a catch handler
try{
die "foo";
}catch{
warn "caught error: $_"; # not [email protected]
};
注意:catch 程式碼以分號結尾的,是一個表示式。另外它會將出錯的資訊儲存在變數$_而不是[email protected]中。
這時使用很方便
use autodie;
use Try::Tiny;
try {
open my $fh, '<', $in;
# .....
open my $fh, '>', $out;
# .....
}
catch {
print "Error $_\n";
}
有時我們還可以在這個地方使用'try catch'做程式流程控制
use autodie;
use Try::Tiny;
try {
&sub1();
&sub2();
&sub3();
}
catch {
&suba();
&subb();
&subc();
}
像上面,只要在 sub1, sub2, sub3 任意一個子函式 die 出來,這個流程就認為出錯,就做其它的另一種方案的流程 suba,subb,subc。
原文來自:http://www.freeoa.net/development/perl/perl-exception-handling-methods_2679.html